Ограничения и внутреннее устройство¶
Честный список того, что осталось за рамками, и немного о том, как всё устроено внутри.
Версии FastAPI¶
Ограничение зависимости — fastapi >=0.95.0,!=0.137.0,!=0.137.1.
| Версия FastAPI | Статус | Как обходятся роуты |
|---|---|---|
0.95 – 0.136 |
Поддерживается | Плоский список router.routes |
0.137.0, 0.137.1 |
Исключены | Дерево роутов уже есть, публичного обходчика ещё нет |
0.137.2 и новее |
Поддерживается | Публичный iter_route_contexts |
В 0.137.0 произошёл рефакторинг роутинга: include_router перестал копировать роуты, а router.routes стал деревом. Поддерживаемый способ обойти это дерево — iter_route_contexts — появился только в 0.137.2, поэтому промежуток из двух релизов исключён на уровне зависимости.
Нужный режим выбирается автоматически: обходчик ищется через getattr при импорте, при его отсутствии используется плоский обход.
Что проверяет CI
Тесты гоняются на минимальной поддерживаемой (0.95), последней до рефакторинга (0.136) и новейшей версии FastAPI — на каждом из питонов 3.10–3.14.
Версионирование только по пути¶
Библиотека версионирует смонтированные субприложения, то есть версия всегда выражена префиксом пути (/v1, /api/public/v2). Версионирование по заголовку (Accept: application/vnd.api+json; version=2), по query-параметру или по поддомену не поддерживается: версия определяется маршрутизацией ASGI-mount'ов ещё до того, как в дело вступает эта библиотека.
Пометка обязательна¶
Эндпоинт без Depends(versioning()) невидим для версионирования. Это осознанное решение — неявное наследование всех подряд роутов слишком легко приводит к тому, что в новую версию просачивается то, что там не планировалось. Обратная сторона: забытая пометка выглядит как «эндпоинт почему-то не наследуется».
Момент сборки¶
Наследование строится один раз — при первом ASGI-событии:
- под реальным сервером это событие запуска lifespan;
- в тестах через ASGI-транспорт (lifespan не выполняется) и для middleware на смонтированном приложении — первый запрос.
До этого момента ничего не версионировано: копирование роутов происходит в рантайме, а не на этапе импорта или декорирования. Всё, что добавлено после, требует явного rebuild_versioning.
Типы api_version¶
Только int, причём bool отвергается отдельно, несмотря на то что он подкласс int. Значения "1", 1.0, True приводят к тому, что субприложение игнорируется — с UserWarning, чтобы это не выглядело как молчаливая пропажа версии. Субприложение вовсе без api_version игнорируется молча: это штатный способ смонтировать рядом что-то неверсионированное.
Затенение и совпадающие пути¶
Наследование в версию пропускается, если там уже есть роут с тем же путём и теми же методами. Отсюда два следствия:
GET /itemsв v2 не затеняет унаследованныйPOST /items— это разные наборы методов;- HTTP-роут и WebSocket-роут на одном пути не конфликтуют вовсе: затенение учитывает вид роута.
WebSocket на fastapi 0.95¶
В 0.95 у websocket() ещё нет параметра dependencies, поэтому пометить WS-роут можно только зависимостью в сигнатуре эндпоинта:
@app_v1.websocket("/ws")
async def ws(
websocket: WebSocket,
version: Annotated[VersionInfo, Depends(versioning())],
) -> None: ...
На более новых FastAPI работают оба способа. Рецепт с табами — в Рецептах.
Копии роутов, а не общий объект¶
Каждая наследующая версия получает собственную копию роута. Это делает версии независимыми (dependency_overrides резолвятся приложением той версии, которая обслуживает запрос), но означает, что модификация объекта роута в одной версии не отразится на других — менять нужно исходный роут и вызывать rebuild_versioning.
Роуты, пришедшие из include_router, не копируются, а пересобираются заново из их эффективного контекста: иначе потерялись бы include-префикс и зависимости, добавленные при включении.