Skip to content

Repository files navigation

fastapi-easy-versioning

English · Русский

PyPI - Version PyPI - Python Version PyPI - Status PyPI - Downloads FastAPI

GitHub Release GitHub Repo stars GitHub last commit Tests Coverage Status Docs

Versioned APIs for FastAPI — one sub-application per version, automatic inheritance of endpoints from older versions into newer ones, and an up-to-date OpenAPI schema for every version.

Once an API lives in several versions, routes have to be copied between them by hand: /v2 must serve everything /v1 served, minus what was deliberately dropped. The copies drift apart, /v1/docs and /v2/docs start lying, and "which versions is this endpoint even in?" becomes a question for git blame. This package makes the availability range a property of the endpoint: declare it once, mark it with a dependency, and it shows up in every later version by itself.

app = FastAPI()
app.add_middleware(VersioningMiddleware)
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)


@app_v1.get("/items", dependencies=[Depends(versioning())])
def items() -> list[Item]:
    return get_items()

Declared once in v1 — served at /v1/items and /v2/items, and present in both versions' Swagger. No duplicated route, no hand-maintained schema.

Install

pip install fastapi-easy-versioning

Requires Python 3.10+ and FastAPI ≥ 0.95 (0.137.0 and 0.137.1 are excluded). fastapi is the only runtime dependency.

How to use it

Versioning is built from two pieces that work only together.

1. Mount one sub-application per version and add the middleware. The middleware goes on the aggregating application — the one that directly mounts the versions, never on the versions themselves.

from fastapi import Depends, FastAPI
from fastapi_easy_versioning import VersioningMiddleware, versioning

app = FastAPI()
app_v1 = FastAPI(api_version=1)  # the version number is an int; 0 is valid
app_v2 = FastAPI(api_version=2)

app.mount("/v1", app_v1)
app.mount("/v2", app_v2)
app.add_middleware(VersioningMiddleware)

2. Mark the endpoints. Marking is opt-in: an endpoint without the dependency stays in its own version only.

@app_v1.get("/only-v1", dependencies=[Depends(versioning(until=1))])
def only_v1() -> str:
    return "Available only in version v1"


@app_v1.get("/all-versions", dependencies=[Depends(versioning())])
def all_versions() -> str:
    return "Available in all versions starting from v1"


@app_v2.get("/from-v2", dependencies=[Depends(versioning())])
def from_v2() -> str:
    return "Available starting from v2 and in all future versions"

versioning() without until means "through the latest version", not "in this one only" — a common source of surprise. The dependency is accepted anywhere FastAPI accepts one, including APIRouter(dependencies=[...]) to version a whole router at once.

3. Read the version metadata where you need it. Injected into the signature, the same dependency yields the resolved range:

@app_v1.get("/where-am-i")
def where_am_i(version: Annotated[VersionInfo, Depends(versioning())]) -> str:
    return f"Available from v{version.origin} through v{version.until}"

The result: /v1/only-v1 responds while /v2/only-v1 is a 404; /all-versions responds in both; /v2/from-v2 responds while /v1/from-v2 is a 404 — and each version's /docs shows exactly what that version serves.

Why it's cool

  • Declare once, inherit forward — an endpoint declared in v1 is served by every later version, each with its own copy of the route.
  • An exact availability rangeuntil caps the last version an endpoint lives in; several markers on one route resolve to the smallest.
  • Redefinition shadows — an endpoint of your own on the same path in a newer version replaces the inherited one, at runtime and in OpenAPI alike.
  • Honest per-version docs — every version's OpenAPI schema is regenerated after inheritance, so Swagger never drifts from what is actually served.
  • HTTP and WebSocketAPIRoute and APIWebSocketRoute are versioned with identical semantics, and shadowing is kind-aware.
  • Several independent APIs — public and private APIs in one application version separately, one middleware per aggregating app.
  • Nothing but FastAPI — a single runtime dependency, fully typed (py.typed), tested against FastAPI 0.95 through the latest on Python 3.10–3.14.

Documentation

📖 Full documentation — quickstart, the guide (dependency, middleware, recipes, limitations), runnable examples and an auto-generated API reference. Available in English and Russian.

License

MIT.


Pre-commit uv Ruff ty pytest Material for MkDocs Conventional Commits Semantic Versions

GitHub License

About

Versioned APIs for FastAPI: one sub-app per version, automatic endpoint inheritance and a regenerated OpenAPI schema per version.

Topics

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages