# Runtime dependency
uv add requests
# Dev-only dependency
uv add --group dev httpx
# Optional group (e.g., UI)
uv add --group ui plotlyDependencies are declared in pyproject.toml and locked in uv.lock.
For pip users: pip install -e ".[dev,ui]" works but uv sync is canonical.
- 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/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."""
...- 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- 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/.
While uv is the canonical tool, pip still works:
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,ui]"
pytestThe pyproject.toml is fully PEP 621 compliant.