Extending the Template
Add a Dependency
# Runtime dependency uv add requests # Dev-only dependency uv add --group dev httpx # Optional group (e.g., UI) uv add --group ui plotly
Dependencies are declared in pyproject.toml and locked in uv.lock.
For pip users: pip install -e ".[dev,ui]" works but uv sync is canonical.
Add a New Service
- Create the directory:
mkdir -p src/myapp/services/billing/{storage,tests}- Create the required files (copy from
example/):
services/billing/
├── __init__.py # """Billing service."""
├── schemas.py # define your models (extend BaseRecord)
├── api.py # BillingService class (public interface)
├── cli.py # Click group named `commands`
├── storage/
│ ├── __init__.py
│ ├── json_adapter.py
│ └── sqlite_adapter.py
└── tests/
├── __init__.py
├── test_schemas.py
├── test_storage.py
└── test_api.py
- Define your schema in
schemas.py:
from myapp.shared.schemas import BaseRecord from pydantic import Field class Invoice(BaseRecord): customer_id: str amount_cents: int = Field(ge=0) status: str = "draft"
- Create your CLI in
cli.py:
import click @click.group("billing") def commands(): """Billing service commands.""" @commands.command("list") def list_invoices(): """List all invoices.""" from .api import BillingService svc = BillingService() ...
- That's it. The CLI auto-discovers the service:
project svc billing list # works immediately- Optional — add justfile recipes:
svc-billing-run: uv run project svc billing list svc-billing-test: uv run pytest src/myapp/services/billing/tests/
Add a New CLI Command
To add a command to an existing service, add it to that service's cli.py:
@commands.command("status") def show_status(): """Show billing status.""" click.echo("All clear")
To add a top-level command, edit src/myapp/cli/main.py:
@cli.command() def my_command(): """A new top-level command.""" ...
Add a New Schema / Model
- Define the model in the service's
schemas.py(extendBaseRecord) - Include
schema_version(inherited fromBaseRecord) - Add tests for serialization/deserialization in
tests/test_schemas.py - Create storage adapters if the model needs persistence
from myapp.shared.schemas import BaseRecord class MyModel(BaseRecord): field_a: str field_b: int = 0
Add a New Streamlit Page
- Create a new file in
ui/(e.g.,ui/pages/billing.py) - Import and use the service API:
import streamlit as st from myapp.services.billing.api import BillingService st.title("Billing") svc = BillingService() resp = svc.list_invoices() # ... render data
- Streamlit auto-discovers pages in
ui/pages/.
pip Compatibility
While uv is the canonical tool, pip still works:
python -m venv .venv source .venv/bin/activate pip install -e ".[dev,ui]" pytest
The pyproject.toml is fully PEP 621 compliant.