# Starlette source notes Repo: `encode/starlette` Local checkout: `/home/ubuntu/repos/rodin-sources/starlette` ## Why this repo was chosen - FastAPI inherits key runtime behavior from Starlette. Starlette is the right source for conventions around app assembly, middleware ordering, lifespan, and request state. - Valuable here because it distinguishes framework guarantees from habits that only appear in sample apps. ## Repeated patterns ### 1) App construction keeps routing, middleware, exception handlers, and lifespan as separate inputs - `starlette/applications.py:22-55` defines the constructor with distinct parameters for routes, middleware, exception handlers, and lifespan. - `tests/test_applications.py:139-157` builds one app by explicitly passing routes, exception handlers, middleware, and lifespan together. Why chosen: - This separation exists both in the public constructor and in the main integration-style test app. Implication for synthesis: - Treat app assembly concerns as separate layers; do not blur middleware, routing, error handling, and startup resources into one module-level setup blob. ### 2) Middleware ordering is intentional and framework-enforced - `starlette/applications.py:35-40` documents that `ServerErrorMiddleware` is outermost and `ExceptionMiddleware` innermost. - `starlette/applications.py:68-77` shows the actual stack construction: framework error middleware, then user middleware, then framework exception middleware around the router. - `starlette/applications.py:98-101` forbids adding middleware after the application has started. Why chosen: - This is stronger than a docs recommendation; it is constructor/runtime behavior. Caveat / counterexample: - User middleware is not the whole stack. If a synthesized convention says "middleware runs in the order you add it," that is incomplete because Starlette wraps user middleware with framework-owned layers. Implication for synthesis: - When discussing middleware order, mention Starlette's outer/inner framework layers explicitly. - Avoid recommendations that rely on mutating middleware after startup. ### 3) Lifespan is the preferred resource boundary, and `asynccontextmanager` is the clearest shape - `starlette/applications.py:46-48` says lifespan replaces `on_startup`/`on_shutdown`; use one style or the other, not both. - `tests/test_applications.py:394-409` shows the preferred shape: an `@asynccontextmanager` lifespan marks startup before `yield` and cleanup after `yield`, with both verified by entering/exiting the client context. - `tests/test_applications.py:108-114` defines lifespan state by yielding `{"count": 1}`. - `tests/test_applications.py:241-244` verifies request handlers can consume that state via `request.state`. Why chosen: - This combines constructor guidance with working tests for startup/cleanup and state propagation. Caveat / counterexample: - `tests/test_applications.py:421-459` still supports async/sync generator lifespan forms, but those tests are under a deprecation-warning filter. For future-facing synthesis, prefer `@asynccontextmanager`, not raw generator lifespans. Implication for synthesis: - Recommend lifespan for DB pools, clients, caches, and other app resources. - Note that lifespan can yield structured state for handlers instead of relying on import-time globals. ## Strong citation candidates - Constructor-level separation of app assembly concerns: `starlette/applications.py:22-55` - Middleware stack ordering is framework-defined: `starlette/applications.py:68-77` - Cannot add middleware after startup: `starlette/applications.py:98-101` - Lifespan replaces startup/shutdown hooks: `starlette/applications.py:46-48` - Preferred `@asynccontextmanager` lifespan behavior: `tests/test_applications.py:394-409` ## Pattern candidates supported by this repo - separate routes, middleware, exception handlers, and lifespan at app assembly time - preserve middleware ordering intentionally, including framework-owned outer/inner layers - prefer lifespan over startup/shutdown hooks for resource management - use lifespan-yielded state instead of import-time globals for app-scoped data