No history yet

Продвинутая маршрутизация

Продвинутая маршрутизация

В сложных приложениях FastAPI стандартной маршрутизации может быть недостаточно. Для поддержания чистоты кода, масштабируемости и производительности необходимо использовать более продвинутые техники. Рассмотрим организацию маршрутов с помощью APIRouter, сложную валидацию динамических путей и создание собственных классов маршрутизаторов.

Вложенные маршруты с APIRouter

Когда приложение разрастается, хранить все пути в одном файле становится непрактично. APIRouter позволяет группировать связанные маршруты в отдельные модули, которые затем можно подключить к основному приложению. Это похоже на использование Blueprint во Flask или Router в Express.

Каждый APIRouter может иметь собственный префикс пути, теги для документации OpenAPI и специфичные для группы маршрутов зависимости.

Рассмотрим структуру, где маршруты для пользователей и продуктов разделены.

# users/router.py
from fastapi import APIRouter

router = APIRouter(
    prefix="/users",
    tags=["users"],
)

@router.get("/")
def read_users():
    return [{"username": "Alice"}, {"username": "Bob"}]

@router.get("/{user_id}")
def read_user(user_id: int):
    return {"username": f"user_{user_id}"}

# main.py
from fastapi import FastAPI
from users.router import router as users_router

app = FastAPI()

app.include_router(users_router)

В этом примере мы создали маршрутизатор в users/router.py с префиксом /users. Все пути, определенные в этом маршрутизаторе, будут автоматически начинаться с этого префикса. Например, @router.get("/{user_id}") становится доступным по пути /users/{user_id}.

Метод app.include_router() регистрирует все маршруты из users_router в основном приложении app. Такой подход позволяет декомпозировать большое API на логические, легко поддерживаемые модули.

Динамические пути и валидация

FastAPI использует аннотации типов для базовой валидации параметров пути. Однако для более сложных сценариев, таких как ограничение диапазона чисел или проверка по регулярному выражению, можно использовать Path из модуля fastapi.

from fastapi import FastAPI, Path
from typing_extensions import Annotated

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(
    item_id: Annotated[str, Path(
        title="The ID of the item to get", 
        min_length=3,
        max_length=50,
        regex="^item-\d+$"
    )]
):
    return {"item_id": item_id}

Здесь мы используем Annotated для добавления метаданных к параметру item_id. Path позволяет нам задать правила валидации:

  • title: Описание для документации OpenAPI.
  • min_length/max_length: Ограничения на длину строки.
  • regex: Регулярное выражение, которому должен соответствовать параметр. В данном случае item_id должен начинаться с item-, за которым следуют цифры.

Если входящий запрос не соответствует этим правилам, FastAPI автоматически вернет ошибку 422 Unprocessable Entity с подробным описанием проблемы.

Создание настраиваемых маршрутизаторов

Иногда требуется применить ко всем маршрутам определенную логику, например, добавить специфический заголовок ответа или обернуть каждый ответ в стандартную структуру. Вместо использования зависимостей или декораторов для каждого пути, можно создать собственный класс маршрутизатора, унаследовав его от APIRouter.

from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.routing import APIRoute
from typing import Callable

class TimedRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_handler = super().get_route_handler()

        async def custom_handler(request: Request) -> Response:
            import time
            before = time.time()
            response: Response = await original_handler(request)
            duration = time.time() - before
            response.headers["X-Response-Time"] = str(duration)
            print(f"route {request.url.path} took {duration:.4f}s")
            return response

        return custom_handler

# Используем наш кастомный класс в маршрутизаторе
router = APIRouter(route_class=TimedRoute)

@router.get("/items/")
async def get_items():
    # Имитация долгой операции
    import asyncio
    await asyncio.sleep(0.5)
    return {"message": "items retrieved"}

app = FastAPI()
app.include_router(router)

В этом примере мы создали класс TimedRoute, который наследуется от APIRoute. Мы переопределили метод get_route_handler, чтобы обернуть оригинальный обработчик маршрута. Новая функция custom_handler измеряет время выполнения запроса и добавляет заголовок X-Response-Time в ответ.

Затем мы передаем route_class=TimedRoute в конструктор APIRouter. Теперь все маршруты, определенные с помощью этого экземпляра router, будут автоматически использовать нашу кастомную логику. Это мощный способ внедрения сквозной функциональности без дублирования кода.

Теперь, когда вы знакомы с этими техниками, проверьте свои знания.

Quiz Questions 1/5

Какова основная цель использования APIRouter в приложении FastAPI?

Quiz Questions 2/5

Какой метод основного экземпляра приложения FastAPI используется для регистрации маршрутов из APIRouter?

Освоение этих продвинутых методов маршрутизации позволяет создавать более структурированные, надежные и поддерживаемые API с помощью FastAPI.