Flagship Use Case: Order Platform¶
One application that uses every pico library doing its natural job — not a feature tour, but the shape a real service takes on this ecosystem. Every snippet below is executed against the real modules before each release; the behaviors described (retry counts, cache hits, hot reload) are asserted, not aspirational.
The domain: a coffee-shop order platform. An order comes in over HTTP, the price is looked up in a cached catalog, an external payment gateway is charged (it flakes), the order is persisted, an event fans out to projections, a record lands on the analytics stream, and an invoice is generated by a worker. At 03:00 a reconciliation job sweeps the day.
| Concern | Library | Piece below |
|---|---|---|
| Container + auto-discovery | pico-ioc + pico-boot | create_app() |
| HTTP API | pico-fastapi | OrderController |
| Persistence + transactions | pico-sqlalchemy | Order, OrderRepository |
| Migrations on startup | pico-sqlalchemy[migrations] | database.migrations_path |
| Input validation | pico-pydantic | @validate on the stream consumer |
| External HTTP | pico-httpx | PaymentsApi |
| Retry + circuit breaker | pico-resilience | stacked on charge() |
| Caching (distributed) | pico-caching + pico-data-redis | Catalog.price_cents |
| Events / fan-out | pico-rabbitmq | OrderEvents, StockProjection |
| Streaming / consumer groups | pico-kafka | OrderAnalytics, AnalyticsProjection |
| Background tasks | pico-celery | InvoiceTasks |
| Cron / intervals | pico-scheduling | Reconciliation |
| Health + metrics + refresh | pico-actuator | /actuator/* |
| Tracing | pico-otel | zero code — install and configure |
| AuthN/AuthZ | pico-server-auth + pico-client-auth | JWKS + @requires_role |
| Testing | pico-testing | make_container |
The application¶
One package, one module (split it as it grows — discovery is recursive):
"""orders/app.py — every pico lib doing its natural job."""
import asyncio
import httpx
from pydantic import BaseModel
from sqlalchemy import String
from pico_caching import cacheable
from pico_celery import task
from pico_fastapi import controller, get, post
from pico_httpx import http_client
from pico_httpx import post as http_post
from pico_ioc import component
from pico_kafka import kafka_consumer, kafka_producer, produce
from pico_pydantic import validate
from pico_rabbitmq import consumer, publish, publisher
from pico_resilience import circuit_breaker, retryable
from pico_scheduling import scheduled
from pico_sqlalchemy import (
AppBase,
Mapped,
SessionManager,
get_session,
mapped_column,
query,
repository,
transactional,
)
# ── Domain (pico-sqlalchemy) ─────────────────────────────────────
class Order(AppBase):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
sku: Mapped[str] = mapped_column(String(50))
amount_cents: Mapped[int] = mapped_column()
status: Mapped[str] = mapped_column(String(20))
class OrderRequest(BaseModel):
sku: str
quantity: int = 1
@repository(entity=Order)
class OrderRepository:
def __init__(self, session_manager: SessionManager):
self.sm = session_manager
async def save(self, sku: str, amount_cents: int) -> Order:
session = get_session(self.sm)
order = Order(sku=sku, amount_cents=amount_cents, status="paid")
session.add(order)
await session.flush()
return order
@query(expr="status = :status")
async def find_by_status(self, status: str) -> list[Order]: ...
@component
class SchemaSetup:
"""DatabaseConfigurer for dev; production sets database.migrations_path
and lets Alembic run on startup instead."""
priority = 0
def configure_database(self, engine) -> None:
async def _create():
async with engine.begin() as conn:
await conn.run_sync(AppBase.metadata.create_all)
# asyncpg binds pooled connections to the loop that created
# them; drop the DDL loop's pool so request loops start fresh.
await engine.dispose()
asyncio.run(_create())
# ── External payments (pico-httpx + pico-resilience) ─────────────
@http_client(name="payments") # base_url from http.clients.payments.base_url
class PaymentsApi:
@retryable(max_attempts=3, backoff_seconds=0.05, retry_on=(httpx.HTTPError,))
@circuit_breaker(failure_threshold=5, reset_timeout_seconds=30)
@http_post("/charges")
async def charge(self, json: dict) -> dict: ...
# ── Catalog with distributed cache (pico-caching + pico-data-redis) ──
@component
class Catalog:
@cacheable(ttl_seconds=300) # Redis-backed the moment pico-data-redis is installed
def price_cents(self, sku: str) -> int:
return {"ESPRESSO": 250, "LATTE": 390}.get(sku, 999)
# ── Events (pico-rabbitmq) and analytics stream (pico-kafka) ─────
@publisher
class OrderEvents:
@publish(exchange="events", routing_key="orders.created")
def order_created(self, message): ...
@kafka_producer
class OrderAnalytics:
@produce("orders")
def order_placed(self, message): ...
@component
class AnalyticsProjection:
@kafka_consumer("orders", group_id="analytics")
@validate
def on_order(self, message: dict): ...
@component
class StockProjection:
@consumer("stock-projection", exchange="events", routing_key="orders.*")
async def on_order_event(self, message: dict): ...
# ── Invoicing worker (pico-celery) ───────────────────────────────
@component(scope="prototype")
class InvoiceTasks:
@task(name="invoices.generate")
async def generate(self, order_id: int) -> dict:
return {"order_id": order_id, "pdf": f"invoice-{order_id}.pdf"}
# ── Business service ─────────────────────────────────────────────
@component
class OrderService:
def __init__(self, repo: OrderRepository, payments: PaymentsApi, catalog: Catalog,
events: OrderEvents, analytics: OrderAnalytics):
self._repo = repo
self._payments = payments
self._catalog = catalog
self._events = events
self._analytics = analytics
@transactional
async def place(self, request: OrderRequest) -> dict:
amount = self._catalog.price_cents(request.sku) * request.quantity
await self._payments.charge(json={"amount_cents": amount})
order = await self._repo.save(request.sku, amount)
payload = {"id": order.id, "sku": order.sku, "amount_cents": amount}
self._events.order_created(payload)
self._analytics.order_placed(payload)
return {**payload, "status": order.status}
# ── HTTP API (pico-fastapi) ──────────────────────────────────────
@controller(prefix="/api/v1/orders", tags=["Orders"])
class OrderController:
def __init__(self, service: OrderService, repo: OrderRepository):
self._service = service
self._repo = repo
@post("")
async def place_order(self, request: OrderRequest):
return await self._service.place(request)
@get("/paid")
async def paid_orders(self):
return [{"id": o.id, "sku": o.sku} for o in await self._repo.find_by_status("paid")]
# ── Housekeeping (pico-scheduling) ───────────────────────────────
@component
class Reconciliation:
def __init__(self, repo: OrderRepository):
self._repo = repo
@scheduled(cron="0 3 * * *")
async def reconcile(self):
await self._repo.find_by_status("paid")
Boot¶
main.py stays import-safe (factory pattern):
from fastapi import FastAPI
from pico_boot import init
def create_app() -> FastAPI:
container = init(modules=["orders"]) # installed pico plugins auto-discovered
return container.get(FastAPI)
Configuration¶
One YAML, one prefix per module — this is the whole ops surface:
fastapi: {title: orders, version: 1.0.0}
database:
url: postgresql+asyncpg://orders:secret@db/orders
migrations_path: alembic # upgrade head on startup
http:
clients:
payments: {base_url: https://payments.internal}
resilience: {enabled: true} # hot-reloadable (see below)
caching: {enabled: true}
redis: {url: redis://cache.internal:6379/0}
celery: {broker_url: amqp://rabbit.internal/, backend_url: redis://cache.internal/1}
rabbitmq: {url: amqp://rabbit.internal/}
kafka: {bootstrap_servers: kafka.internal:9092, group_id: orders}
scheduling: {enabled: true}
otel: {endpoint: http://collector:4317}
auth_client: {issuer: https://auth.internal, audience: orders}
server_auth: {issuer: https://auth.internal, audience: orders}
What the verification asserts¶
The e2e harness boots this app with all 15 modules in one container (infra boundaries stubbed: payments on httpx.MockTransport, fakeredis, recording broker registrars) and asserts, in order:
/actuator/healthanswersUP./api/v1/auth/jwksserves the embedded issuer's keys.POST /api/v1/orderssurvives a gateway that fails twice:@retryablemakes exactly 3 attempts and the order persists aspaid.- The rabbit event (
events/orders.created) and the kafka record (orders) both leave the service. GET /api/v1/orders/paidreturns the order through the derived query.- A second order hits the Redis-backed catalog cache — zero extra lookups.
invoices.generateis registered on the Celery app for the worker.- The 03:00 reconciliation job is armed on the scheduler.
- Hot reload over HTTP:
POST /actuator/refreshreports{"changed": ["resilience"]}and turns retries off live — the same request now fails on the first attempt — and flipping it back restores them. - Alembic for real (level 2):
database.migrations_pathupgrades a real Postgres tohead; the revision is asserted inalembic_version. - A real Celery worker (level 2):
invoices.generateis dispatched through the real broker and its result collected from the backend. - Real JWT auth (level 2, uvicorn): the protected endpoint answers 401 without a token and 200 with an operator token minted by the embedded issuer — the pairing that uncovered pico-fastapi 0.3.1 and pico-client-auth 0.4.3.
Level 2: against real infrastructure¶
The same application also runs against real Postgres, Redis, RabbitMQ and Kafka (docker compose): the rabbit event round-trips through a real topic exchange into StockProjection, the kafka record through a real consumer group into AnalyticsProjection, the catalog entry is visible as a pico:cache:* key in Redis, and hot reload flips retries live against the real gateway path. Only the payments gateway stays mocked — it is a third party.
Testing it (pico-testing)¶
pico-testing isolates every test from venv plugin auto-discovery and tears containers down for you.
Per-module deep dives¶
Each library's own docs cover its corner of this application in depth: pico-ioc · pico-fastapi · pico-sqlalchemy · pico-pydantic · pico-httpx · pico-resilience · pico-caching · pico-data-redis · pico-rabbitmq · pico-kafka · pico-celery · pico-scheduling · pico-actuator · pico-otel · pico-server-auth · pico-client-auth · pico-testing