Перейти к содержанию

fastapi-easy-versioning

Версионированные API на FastAPI. Одно субприложение на версию, автоматическое наследование эндпоинтов из старых версий в новые и своя актуальная OpenAPI-схема у каждой версии.


Зачем это нужно

Когда API живёт в нескольких версиях, между ними приходится вручную копировать роуты: /v2 должен отдавать всё, что отдавал /v1, кроме того, что осознанно выпилили. Копии расползаются, /v1/docs и /v2/docs начинают врать, а «в какой версии этот эндпоинт вообще есть» становится вопросом к git blame.

fastapi-easy-versioning делает диапазон доступности свойством эндпоинта: объявляете его один раз, помечаете зависимостью — и он сам оказывается во всех последующих версиях.

app_v1 = FastAPI()
app_v2 = FastAPI()


@app_v1.get("/items")
def items_v1() -> list[Item]:  # (1)!
    return get_items()


@app_v2.get("/items")
def items_v2() -> list[Item]:
    return get_items()
  1. Один и тот же эндпоинт, продублированный руками. С каждой новой версией копий становится больше, и однажды одну из них забудут обновить.
app = FastAPI()
app.add_middleware(VersioningMiddleware)
app.mount("/v1", app_v1)
app.mount("/v2", app_v2)


@app_v1.get("/items", dependencies=[Depends(versioning())])  # (1)!
def items() -> list[Item]:
    return get_items()
  1. Объявлен один раз в v1 — и доступен в v2 и во всех будущих версиях. Схема /v2/docs собирается сама.
@app_v1.get("/legacy", dependencies=[Depends(versioning(until=2))])  # (1)!
def legacy() -> str:
    return "уйдёт после v2"
  1. Доступен в v1 и v2, в v3 его уже нет — ни в рантайме, ни в схеме. Подробнее в семантике until.

Каждая версия остаётся обычным FastAPI-приложением: свой роутинг, свой /docs, свои dependency_overrides.

graph TD
    A[Доступность по версиям]
    A --> B[v1]
    A --> C[v2]

    B --> B1["/only-v1 ✓"]:::available
    B --> B2["/all-versions ✓"]:::available
    B --> B3["/from-v2 ✗"]:::missing

    C --> C1["/only-v1 ✗"]:::missing
    C --> C2["/all-versions ✓"]:::available
    C --> C3["/from-v2 ✓"]:::available

    classDef available fill:#90EE90,stroke:#333,color:#1b1b1b
    classDef missing fill:#FFB6C1,stroke:#333,color:#1b1b1b

Возможности

  • Наследование между версиями


    Эндпоинт объявляется один раз и сам оказывается во всех последующих версиях. Каждая версия получает собственную копию роута.

    Middleware

  • Точный диапазон доступности


    until задаёт последнюю версию, в которой эндпоинт доступен. Без него — «до последней версии», включая ещё не созданные.

    Семантика until

  • Переопределение затеняет


    Свой эндпоинт на том же пути в новой версии перекрывает унаследованный — и в рантайме, и в OpenAPI.

    Правила наследования

  • Честный /docs у каждой версии


    После наследования OpenAPI-схема каждой версии перестраивается, поэтому Swagger показывает ровно её состав.

    OpenAPI

  • HTTP и WebSocket


    APIRoute и APIWebSocketRoute версионируются одинаково; метаданные версии читаются прямо в эндпоинте.

    Зависимость

  • Несколько независимых API


    Public и private API в одном приложении версионируются раздельно — по одному middleware на каждое агрегирующее приложение.

    Пример

Установка

uv add fastapi-easy-versioning
pip install fastapi-easy-versioning

Требования

  • Python 3.10+
  • FastAPI ≥ 0.95 (версии 0.137.0 и 0.137.1 исключены — см. Ограничения)
  • Больше ничего: fastapi — единственная рантайм-зависимость

Дальше