Files
2026-06-01 21:42:05 +00:00

4.1 KiB

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