Starlette

raw JSON →
1.0.0 verified Tue May 12 auth: no python install: verified quickstart: verified

Lightweight ASGI framework and toolkit. Foundation for FastAPI. Released version 1.0.0 on Mar 22, 2026 — first stable release after 8 years on ZeroVer (0.x). Now follows SemVer. 1.0 removed all features deprecated during the 0.x era. Directly used by the Python MCP SDK.

pip install starlette
breaking on_startup= and on_shutdown= parameters removed in 1.0. add_event_handler() also removed. All three were the primary patterns in older tutorials and LLM-generated code.
fix Use lifespan context manager: @asynccontextmanager async def lifespan(app): yield — pass as app = Starlette(lifespan=lifespan).
breaking @app.route() and @app.websocket_route() decorators removed in 1.0. Were deprecated since 0.23.0 but widely used in tutorials for years.
fix Use declarative routing: Starlette(routes=[Route('/', handler), WebSocketRoute('/ws', ws_handler)]).
breaking Jinja2Templates now raises ImportError at import time if jinja2 is not installed. Previously only raised when instantiating the class.
fix pip install jinja2 or pip install 'starlette[full]'
breaking Jinja2Templates now enables autoescape by default. Templates that relied on unescaped output (e.g. rendering HTML strings) will now escape them.
fix Mark trusted HTML strings with Markup() from markupsafe, or pass autoescape=False to Jinja2Templates() to restore old behavior.
breaking TestClient now uses httpx instead of requests (changed in 0.20.0). Test suites using requests-specific APIs on TestClient responses will break.
fix pip install httpx. Use httpx response API — most methods are compatible. Use the bump-testclient migration tool for larger test suites.
breaking Python 3.8 and 3.9 support dropped. Starlette 1.0 requires Python >=3.10.
fix Upgrade Python to 3.10+. Pin starlette<1.0 to remain on Python 3.8/3.9.
gotcha Starlette is typically a transitive dependency via FastAPI — do not pin starlette version directly. FastAPI manages its own compatible Starlette range. Pinning starlette independently can cause conflicts.
fix Let FastAPI manage the starlette version. Only pin starlette directly if using it standalone without FastAPI.
pip install "starlette[full]"
python os / libc variant status wheel install import disk
3.10 alpine (musl) full - - 0.00s 26.2M
3.10 alpine (musl) starlette - - 0.00s 20.7M
3.10 slim (glibc) full - - 0.00s 27M
3.10 slim (glibc) starlette - - 0.00s 21M
3.11 alpine (musl) full - - 0.00s 29.1M
3.11 alpine (musl) starlette - - 0.00s 22.7M
3.11 slim (glibc) full - - 0.00s 30M
3.11 slim (glibc) starlette - - 0.00s 23M
3.12 alpine (musl) full - - 0.00s 20.8M
3.12 alpine (musl) starlette - - 0.00s 14.5M
3.12 slim (glibc) full - - 0.00s 22M
3.12 slim (glibc) starlette - - 0.00s 15M
3.13 alpine (musl) full - - 0.00s 20.1M
3.13 alpine (musl) starlette - - 0.00s 13.8M
3.13 slim (glibc) full - - 0.00s 21M
3.13 slim (glibc) starlette - - 0.00s 14M
3.9 alpine (musl) full - - 0.00s 25.5M
3.9 alpine (musl) starlette - - 0.00s 20.0M
3.9 slim (glibc) full - - 0.00s 27M
3.9 slim (glibc) starlette - - 0.00s 21M

Minimal Starlette 1.0 app with lifespan and declarative routing.

from contextlib import asynccontextmanager
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route

@asynccontextmanager
async def lifespan(app):
    # startup logic
    yield
    # shutdown logic

async def homepage(request: Request):
    return JSONResponse({'hello': 'world'})

app = Starlette(
    routes=[Route('/', homepage)],
    lifespan=lifespan,
)

# Run: uvicorn main:app