streamlit-uv-development
Use this skill when developing Streamlit applications with uv package manager. Covers project setup, running apps, testing (unit/e2e), and development workflows. Trigger when user mentions "streamlit", "uv run streamlit", "streamlit testing", or asks about building data apps with Python.
$ Installieren
git clone https://github.com/bossjones/streamlit-replicate-boss /tmp/streamlit-replicate-boss && cp -r /tmp/streamlit-replicate-boss/.claude/skills/streamlit-uv-development ~/.claude/skills/streamlit-replicate-boss// tip: Run this command in your terminal to install the skill
name: streamlit-uv-development description: Use this skill when developing Streamlit applications with uv package manager. Covers project setup, running apps, testing (unit/e2e), and development workflows. Trigger when user mentions "streamlit", "uv run streamlit", "streamlit testing", or asks about building data apps with Python.
Streamlit Development with uv
Overview
This skill enables rapid Streamlit application development using uv as the package manager. It covers project initialization, running apps, testing strategies, and production-ready development patterns.
Quick Start
Create New Streamlit Project
# Create project directory
mkdir my-streamlit-app && cd my-streamlit-app
# Initialize with uv
uv init --name my-streamlit-app
# Add streamlit dependency
uv add streamlit
# Create main app file
cat > app.py << 'EOF'
import streamlit as st
st.set_page_config(page_title="My App", page_icon="đ", layout="wide")
st.title("Hello, Streamlit!")
st.write("Welcome to your new app.")
EOF
# Run the app
uv run streamlit run app.py
Run Existing Streamlit App
# With uv (preferred)
uv run streamlit run app.py
# With options
uv run streamlit run app.py --server.port 8501 --server.headless true
# With browser disabled (for CI/testing)
uv run streamlit run app.py --server.headless true --browser.serverAddress localhost
Project Structure
Recommended Layout
my-streamlit-app/
âââ pyproject.toml # Project config with uv
âââ uv.lock # Lock file (commit this)
âââ .python-version # Python version pin
âââ app.py # Main entry point (or src/app.py)
âââ pages/ # Multi-page app pages
â âââ 1_đ_Dashboard.py
â âââ 2_đ_Analytics.py
â âââ 3_âïž_Settings.py
âââ components/ # Custom components
â âââ sidebar.py
âââ utils/ # Helper functions
â âââ __init__.py
â âââ data.py
â âââ charts.py
âââ tests/ # Test files
â âââ conftest.py
â âââ test_utils.py
â âââ e2e/
â âââ test_app.py
âââ .streamlit/ # Streamlit config
â âââ config.toml
â âââ secrets.toml # (gitignored)
âââ data/ # Static data files
pyproject.toml Example
[project]
name = "my-streamlit-app"
version = "0.1.0"
description = "A Streamlit data application"
requires-python = ">=3.10"
dependencies = [
"streamlit>=1.40.0",
"pandas>=2.0.0",
"plotly>=5.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-playwright>=0.5.0",
"ruff>=0.8.0",
]
[tool.uv]
dev-dependencies = [
"pytest>=8.0.0",
"pytest-playwright>=0.5.0",
"ruff>=0.8.0",
]
[tool.ruff]
line-length = 100
target-version = "py310"
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
Testing Strategies
Unit Testing (Logic & Utils)
Test business logic without Streamlit context:
# tests/test_utils.py
import pytest
from utils.data import process_data, validate_input
def test_process_data_with_valid_input():
data = {"value": 10, "multiplier": 2}
result = process_data(data)
assert result == 20
def test_validate_input_rejects_negative():
with pytest.raises(ValueError, match="must be positive"):
validate_input(-5)
Run unit tests:
uv run pytest tests/test_utils.py -v
Testing Streamlit Components with AppTest
Use Streamlit's built-in AppTest for headless testing:
# tests/test_app.py
import pytest
from streamlit.testing.v1 import AppTest
def test_app_loads():
"""Test that the app loads without errors."""
at = AppTest.from_file("app.py")
at.run()
assert not at.exception
def test_sidebar_selection():
"""Test sidebar widget interactions."""
at = AppTest.from_file("app.py")
at.run()
# Interact with selectbox
at.selectbox[0].select("Option B").run()
assert at.session_state["selected"] == "Option B"
def test_button_click_updates_state():
"""Test button click behavior."""
at = AppTest.from_file("app.py")
at.run()
# Click button
at.button[0].click().run()
assert "result" in at.session_state
def test_form_submission():
"""Test form with multiple inputs."""
at = AppTest.from_file("app.py")
at.run()
# Fill form fields
at.text_input[0].input("John Doe").run()
at.number_input[0].set_value(25).run()
at.button("Submit").click().run()
# Check success message appears
assert len(at.success) > 0
E2E Testing with Playwright
For full browser-based testing:
# tests/e2e/test_app.py
import pytest
from playwright.sync_api import Page, expect
import subprocess
import time
@pytest.fixture(scope="module")
def streamlit_app():
"""Start Streamlit app for testing."""
proc = subprocess.Popen(
["uv", "run", "streamlit", "run", "app.py",
"--server.headless", "true", "--server.port", "8599"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
time.sleep(5) # Wait for startup
yield "http://localhost:8599"
proc.terminate()
proc.wait()
def test_homepage_loads(page: Page, streamlit_app: str):
"""Test that homepage renders correctly."""
page.goto(streamlit_app)
expect(page.locator("h1")).to_contain_text("Hello")
def test_sidebar_navigation(page: Page, streamlit_app: str):
"""Test sidebar navigation works."""
page.goto(streamlit_app)
page.click("text=Dashboard")
expect(page).to_have_url(f"{streamlit_app}/Dashboard")
def test_data_input_and_display(page: Page, streamlit_app: str):
"""Test data entry workflow."""
page.goto(streamlit_app)
page.fill("input[aria-label='Enter value']", "42")
page.click("button:has-text('Calculate')")
expect(page.locator(".stSuccess")).to_be_visible()
Run E2E tests:
# Install playwright browsers first
uv run playwright install chromium
# Run E2E tests
uv run pytest tests/e2e/ -v
Test Fixtures for Streamlit
# tests/conftest.py
import pytest
import pandas as pd
from streamlit.testing.v1 import AppTest
@pytest.fixture
def sample_dataframe():
"""Provide sample data for tests."""
return pd.DataFrame({
"name": ["Alice", "Bob", "Charlie"],
"value": [100, 200, 300],
})
@pytest.fixture
def app_test():
"""Provide fresh AppTest instance."""
at = AppTest.from_file("app.py")
at.run()
return at
@pytest.fixture
def app_with_session_state():
"""Provide AppTest with preset session state."""
at = AppTest.from_file("app.py")
at.session_state["user_authenticated"] = True
at.session_state["username"] = "testuser"
at.run()
return at
Development Workflows
Live Development with Hot Reload
# Start with auto-reload (default)
uv run streamlit run app.py
# Disable for production testing
uv run streamlit run app.py --server.runOnSave false
Multi-Page App Development
# pages/1_đ_Dashboard.py
import streamlit as st
st.set_page_config(page_title="Dashboard", page_icon="đ")
st.title("Dashboard")
# Access shared session state
if "data" not in st.session_state:
st.session_state.data = None
# Page-specific content
col1, col2 = st.columns(2)
with col1:
st.metric("Total Users", 1234, delta=56)
with col2:
st.metric("Revenue", "$45,678", delta="12%")
Environment & Secrets Management
# .streamlit/secrets.toml (gitignored)
[database]
host = "localhost"
port = 5432
username = "admin"
password = "secret123"
[api]
key = "sk-..."
# Access in app
import streamlit as st
db_host = st.secrets["database"]["host"]
api_key = st.secrets["api"]["key"]
Docker Deployment
# Dockerfile
FROM python:3.11-slim
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev
# Copy app code
COPY . .
# Expose port
EXPOSE 8501
# Health check
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
# Run app
CMD ["uv", "run", "streamlit", "run", "app.py", \
"--server.port=8501", "--server.address=0.0.0.0", \
"--server.headless=true"]
Build and run:
docker build -t my-streamlit-app .
docker run -p 8501:8501 my-streamlit-app
Common Patterns
Session State Management
import streamlit as st
# Initialize state
if "counter" not in st.session_state:
st.session_state.counter = 0
# Update with callback
def increment():
st.session_state.counter += 1
st.button("Increment", on_click=increment)
st.write(f"Count: {st.session_state.counter}")
Caching Data and Resources
import streamlit as st
import pandas as pd
@st.cache_data(ttl=3600) # Cache for 1 hour
def load_data(url: str) -> pd.DataFrame:
return pd.read_csv(url)
@st.cache_resource # Cache across sessions
def get_database_connection():
return create_connection()
# Use cached data
df = load_data("https://example.com/data.csv")
Error Handling in Apps
import streamlit as st
try:
result = risky_operation()
st.success(f"Operation completed: {result}")
except ValueError as e:
st.error(f"Invalid input: {e}")
except ConnectionError:
st.warning("Connection failed. Please try again.")
if st.button("Retry"):
st.rerun()
except Exception as e:
st.exception(e) # Shows full traceback
Forms for Batch Input
import streamlit as st
with st.form("user_form"):
name = st.text_input("Name")
age = st.number_input("Age", min_value=0, max_value=120)
email = st.text_input("Email")
submitted = st.form_submit_button("Submit")
if submitted:
if not name or not email:
st.error("Name and email are required")
else:
st.success(f"Welcome, {name}!")
# Process form data
Troubleshooting
Common Issues
Port already in use:
# Find and kill process
lsof -i :8501 | grep LISTEN
kill -9 <PID>
# Or use different port
uv run streamlit run app.py --server.port 8502
Import errors with uv:
# Ensure dependencies are synced
uv sync
# Check installed packages
uv pip list
Widget state issues:
# Use key parameter for dynamic widgets
for i, item in enumerate(items):
st.text_input(f"Item {i}", key=f"input_{i}")
Slow app reload:
# Move expensive operations outside main flow
@st.cache_data
def expensive_computation():
# This runs once and is cached
return compute_result()
Resources
Repository
