Starlette
Starlette is the ASGI toolkit under FastAPI: routing, middleware, websockets, background tasks and a test client. No validation layer, no dependency injection - FastAPI supplies those on top.
Its governance, ownership and the 1.0 milestone are covered on Python Web Frameworks. This page is what bites when you use it.
Checked against Starlette 1.9.0.
BaseHTTPMiddleware breaks contextvars
The most expensive gotcha here, because the symptom appears somewhere other than the cause.
Middleware is evaluated top to bottom, wrapped by ServerErrorMiddleware outermost and
ExceptionMiddleware innermost. Printing the built stack confirms it:
ServerErrorMiddleware -> <your middleware, in declaration order> -> ExceptionMiddleware -> Router
That part is predictable. What is not: “Using BaseHTTPMiddleware will prevent changes to
contextvars.ContextVars from propagating upwards.”
Reproduced with an endpoint doing cv.set("set-in-endpoint") and middleware reading cv.get()
after the call:
| stack | what the outer middleware reads |
|---|---|
| one pure ASGI middleware | set-in-endpoint |
pure ASGI middleware above a BaseHTTPMiddleware | unset |
So a ContextVar set inside an endpoint is not readable from middleware sitting above a
BaseHTTPMiddleware. The damage is not local: one convenient middleware silently disables
correlation IDs, request-scoped tenancy or structured-logging context for every middleware above it.
A pure ASGI middleware declared after it, so sitting below it, still reads set-in-endpoint. The
docs word the same warning the other way round (“positioned earlier in the middleware stack, it will
disrupt contextvars propagation for any subsequent Pure ASGI Middleware”); the run above is what
happens. Nothing raises, the values simply come back at their defaults.
The documented answer is to write pure ASGI middleware instead. BaseHTTPMiddleware is the
comfortable subclass; it is also the one with the caveat.
Request-parsing hardening, and what pinning costs
Most of the 1.3.x and 1.4.x work has been denial-of-service hardening on request parsing:
max_fields and max_part_size enforcement in FormParser, absolute-path rejection in
StaticFiles, suffix-range clamping in FileResponse, and GZip offloading.
That is the argument against pinning Starlette low to keep an old FastAPI: the fixes are in the
layer that touches untrusted input first. FastAPI depends on starlette>=0.46.0 with no upper
bound, so it accepts the 1.x line.
TestClient
Importing the test client with plain httpx installed emits, on 1.6.0:
StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated;
install `httpx2` instead.
That is the visible edge of a larger shift: httpx stalled and was superseded by
pydantic/httpx2 (2.15.1 at the time of writing). Installing httpx2 silences the warning. See
Python Web Frameworks.
Notes
-
Ships
py.typed. It does not do type-hint-driven validation or dependency injection; that is FastAPI’s layer. -
encode/uvicornmoved toKludex/uvicornalongside Starlette, and both are active. What stayed at encode did not:databases,orm,typesystemandbroadcasterare archived.