Middleware¶
VersioningMiddleware does the core work of versioning: it finds the version sub-applications, inherits the marked endpoints from older versions into newer ones and rebuilds each version's OpenAPI schema.
Where to add it¶
The middleware is added only to the application that directly mounts the version sub-applications — never to the versions themselves.
from fastapi import FastAPI
from fastapi_easy_versioning import VersioningMiddleware
app = FastAPI()
app_v1 = FastAPI(api_version=1)
app_v2 = FastAPI(api_version=2)
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)
app.add_middleware(VersioningMiddleware)
For two or more isolated versioned APIs, add a separate VersioningMiddleware to each aggregating application — every instance versions only the sub-applications mounted directly under its own app:
from fastapi import FastAPI, middleware
from fastapi_easy_versioning import VersioningMiddleware
app = FastAPI()
public_app = FastAPI(middleware=[middleware.Middleware(VersioningMiddleware)])
public_v1 = FastAPI(api_version=1)
public_v2 = FastAPI(api_version=2)
private_app = FastAPI(middleware=[middleware.Middleware(VersioningMiddleware)])
private_v1 = FastAPI(api_version=1)
private_v2 = FastAPI(api_version=2)
app.mount("/api/public", public_app)
public_app.mount("/v1", public_v1)
public_app.mount("/v2", public_v2)
app.mount("/api/private", private_app)
private_app.mount("/v1", private_v1)
private_app.mount("/v2", private_v2)
Version numbering of such APIs is independent: public_v1 and private_v1 are different sets and never inherit from each other. A full walkthrough is in the example.
Configuring the version applications¶
The middleware decides which FastAPI applications take part in versioning by the api_version extra (the API_VERSION_KEY constant):
api_versionmust be an integer; version0is valid.- A sub-application without
api_versionis ignored: endpoints are neither inherited into it nor taken from it, even when marked with theversioning()dependency. - An
api_versionof the wrong type ("1",True,1.0) makes the sub-application ignored, and aUserWarningis emitted so the typo does not go unnoticed.
bool is not int
Even though True == 1 in Python, api_version=True counts as a type error: it is far more likely to be a typo than an intentional version number.
How inheritance works¶
Inheritance is built once — on the first ASGI event. Under a real server (uvicorn) that is the lifespan startup event; when the middleware sits on an application mounted inside another one, or in tests, it is the first request. Subsequent requests do no extra work.
graph LR
A["First ASGI event"] --> B["Discover versions<br/>by api_version"]
B --> C["Select routes<br/>with versioning()"]
C --> D["Resolve until"]
D --> E["Copy routes into<br/>versions origin+1..until"]
E --> F["Regenerate<br/>OpenAPI schemas"]
The rules:
- Only endpoints marked with
versioning()are inherited, over the range from the declaring version throughuntil, inclusive. - Every inheriting version receives its own copy of the route: changing a route in one version does not affect the others, and
dependency_overridesare resolved by the application of the version serving the request. - If a newer version declares its own endpoint with the same path and methods, inheritance into it is skipped — the newer version shadows the older one, at runtime and in the OpenAPI schema alike.
- Both HTTP endpoints (
APIRoute) and WebSocket endpoints (APIWebSocketRoute) are versioned, with identical semantics. Shadowing is kind-aware: an HTTP endpoint and a WebSocket on the same path do not interfere.
A fastapi 0.95 caveat
WebSocket routes have no route-level dependencies parameter there, so the only way to mark them is a dependency in the endpoint signature. See Limitations.
OpenAPI¶
After inheritance the middleware rebuilds each version's OpenAPI schema, so every version's /docs shows both its own and its inherited endpoints.
The rebuild can be switched off with the rebuild_openapi parameter. Endpoints are still inherited and served, but the inherited ones do not appear in that version's schema and /docs:
from fastapi import FastAPI, middleware
from fastapi_easy_versioning import VersioningMiddleware
app = FastAPI(
middleware=[middleware.Middleware(VersioningMiddleware, rebuild_openapi=False)]
)
# or
app = FastAPI()
app.add_middleware(VersioningMiddleware, rebuild_openapi=False)
Adding endpoints at runtime¶
A versioned endpoint or a new version added after the first request is not picked up automatically. That is what the public rebuild_versioning function is for: it rebuilds the inheritance and refreshes the versions' OpenAPI schemas. The call is idempotent, and a default until is re-resolved against the new latest version.
from fastapi_easy_versioning import rebuild_versioning
# after adding routes or mounting a new version at runtime
rebuild_versioning(app) # app is the application that mounts the versions
FastAPI compatibility¶
In short: 0.95 and newer are supported, except 0.137.0 and 0.137.1. The reasoning and the matrix of routing regimes are in Limitations.
Full reference
Signatures and detailed descriptions live in the API reference.