Updates Claude on FastAPI changes from 0.112 to 0.135.3 including native SSE via fastapi.sse, yield streaming, strict_content_type default, Pydantic v1 dropped, Starlette 1.0, dependency scopes, security 401 fix.
npx claudepluginhub nevaberry/nevaberry-plugins --plugin fastapi-knowledge-patchThis skill uses the workspace's default tool permissions.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Claude's baseline knowledge covers FastAPI through 0.111, Pydantic v2.7, Starlette 0.37, Uvicorn 0.29. This skill covers changes from 0.112 through 0.135.3 (2024-09 to 2026-04).
| Dependency | Minimum | Notes |
|---|---|---|
| Python | 3.10 | Dropped 3.8 in 0.125.0, 3.9 in 0.129.0. Python 3.14 supported |
| Pydantic | 2.9.0 | v1 fully dropped (0.126.0-0.128.0). No pydantic.v1 compat |
| Starlette | >=0.46.0 | Supports Starlette 1.0.0+ |
fastapi-slim | -- | Dropped in 0.129.2. Use fastapi or fastapi[standard] |
| Version | Change | Impact |
|---|---|---|
| 0.135.0 | Native SSE via fastapi.sse | New API |
| 0.134.0 | yield streaming (JSON lines, binary) | New pattern |
| 0.133.0 | Starlette 1.0 support | on_event removed |
| 0.132.0 | strict_content_type=True default | Breaking |
| 0.131.0 | ORJSONResponse/UJSONResponse deprecated | Deprecation |
| 0.130.0 | Pydantic Rust JSON serializer auto-used | Performance |
| 0.129.1 | bytes JSON Schema: contentMediaType | Breaking |
| 0.126.0 | Pydantic v1 support fully dropped | Breaking |
| 0.122.0 | Security classes return 401, not 403 | Breaking |
| 0.121.0 | Dependency scope="request" | New API |
| 0.117.0 | -> None return type for no-body responses | New pattern |
Native SSE via fastapi.sse. No third-party packages needed:
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items() -> AsyncIterable[Item]:
for item in items:
yield item # auto-serialized as JSON in data: field
For SSE fields (event, id, retry, comment), yield ServerSentEvent objects:
yield ServerSentEvent(data=item, event="item_update", id=str(i), retry=5000)
yield ServerSentEvent(raw_data="[DONE]", event="done") # raw_data skips JSON encoding
yield ServerSentEvent(comment="keep-alive") # comment-only event
data and raw_data are mutually exclusive on ServerSentEventdef (sync) too -- use Iterable[T] return typeCache-Control: no-cache, X-Accel-Buffering: no set by defaultSee sse.md for Last-Event-ID resumption pattern and advanced usage.
FastAPI now checks Content-Type header on JSON requests by default. Requests without application/json are rejected with 422. Disable per-route:
@app.post("/legacy", strict_content_type=False)
async def legacy_endpoint(data: MyModel):
...
scope="request" makes yield dependency exit code run before the response is sent:
@app.get("/items/")
async def read_items(db: Annotated[Session, Depends(get_db, scope="request")]):
...
Without scope="request" (default), exit code runs after the response is fully sent -- correct for StreamingResponse where the dep must stay alive during streaming.
See dependency-injection.md for functools.partial() support and Response as dependency annotation.
HTTPBearer, OAuth2, HTTPBasic etc. now raise 401 (not 403) when credentials are missing. Tests asserting status_code == 403 will break.
No longer needed. FastAPI 0.130.0+ uses Pydantic's Rust-based JSON serializer automatically when a Pydantic return type annotation or response_model is declared. Without either, falls back to jsonable_encoder.
MISSING sentinel -- canonical solution for PATCH endpoints (distinguish "not provided" from None):
from pydantic.experimental.missing_sentinel import MISSING
class UpdateItem(BaseModel):
name: str | None | MISSING = MISSING
price: float | None | MISSING = MISSING
item = UpdateItem()
item.model_dump() # {} -- MISSING fields excluded
exclude_if -- conditional field exclusion:
value: int = Field(ge=0, exclude_if=lambda v: v == 0)
@model_validator(mode='after') must now be an instance method (not @classmethod). See pydantic-updates.md for all Pydantic 2.9-2.12 changes.
Hard removals (not just deprecated -- will raise errors):
| Removed | Replacement |
|---|---|
@app.on_event("startup"/"shutdown") | lifespan= context manager |
on_startup/on_shutdown params | lifespan= context manager |
@app.route() | routes= parameter |
@app.exception_handler() | exception_handlers= parameter |
@app.middleware() | middleware= parameter |
TemplateResponse(name, context) | TemplateResponse(request, name, ...) |
Jinja2Templates: autoescape enabled by default. jinja2 must be installed to import.
# BROKEN in Starlette 1.0:
@app.on_event("startup") # AttributeError -- hard removed
async def startup():
...
# CORRECT:
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup
yield
# shutdown
app = FastAPI(lifespan=lifespan)
Access lifespan state with type safety via TypedDict:
from typing import AsyncIterator, TypedDict
from contextlib import asynccontextmanager
import httpx
class AppState(TypedDict):
http_client: httpx.AsyncClient
@asynccontextmanager
async def lifespan(app) -> AsyncIterator[AppState]:
async with httpx.AsyncClient() as client:
yield {"http_client": client}
# In route handler -- typed access:
async def handler(request: Request[AppState]):
client = request.state["http_client"] # typed as httpx.AsyncClient
See starlette-changes.md for CORSMiddleware, FileResponse range, ClientDisconnect.
None Return Type (0.117.0)Valid return annotation for endpoints with no body (204, 304):
@app.delete("/items/{item_id}", status_code=204)
async def delete_item(item_id: int) -> None:
...
Path operations can yield to stream responses directly -- no manual StreamingResponse needed. Requires Starlette >=0.46.0.
Dependencies now fully support functools.partial() and functools.wraps():
from functools import partial
def get_db(engine: Engine, read_only: bool = False):
...
get_prod_db = partial(get_db, engine=prod_engine)
@app.get("/items")
async def read_items(db=Depends(get_prod_db)):
...
See dependency-injection.md for full details.
| File | Contents |
|---|---|
sse.md | SSE Last-Event-ID resumption, advanced ServerSentEvent usage |
breaking-changes.md | All breaking changes: strict_content_type, 401 security, bytes schema, Starlette 1.0 removals |
pydantic-updates.md | Pydantic 2.9-2.12: MISSING sentinel, exclude_if, temporal config, model_validator changes |
dependency-injection.md | Scopes, functools.partial/wraps, Response as dep, PEP 695 TypeAliasType |
starlette-changes.md | Starlette 0.39-1.0: typed lifespan state, CORSMiddleware, FileResponse range, ClientDisconnect |
ecosystem.md | SQLModel 0.0.25-0.0.36, Uvicorn changes, FastAPI Cloud CLI |