Программирование

FastAPI: как повысить читаемость кода с Depends

Лучшие практики FastAPI для Depends: именование параметров, возврат Pydantic-моделей TokenData или username, композиция зависимостей. Примеры для обновления токена, архитектура как нормализация БД, безопасность и тесты.

Как повысить читаемость кода при использовании Depends в FastAPI? На примере:

python
@app.post('/refresh')
@limiter.limit("10/minute")
async def refresh(request: Request, token: str = Depends(check_refresh_token)):
 access_token = create_access_token({"sub": token})
 pass

Зависимость:

python
def check_refresh_token(token: str = Depends(oauth2_scheme)):
 *логика функции*
 return payload.get("sub")

Функция check_refresh_token возвращает username (payload.get("sub")), но в эндпоинте этот параметр назван token, из‑за чего возникает путаница. Конечно, можно в эндпоинте писать username = token, но хотелось бы понять лучшие практики:

  1. Как правильно именовать параметр в эндпоинте — token или username?
  2. Что лучше возвращать из функции, используемой в Depends: только username, весь payload, Pydantic‑модель или специальный объект (например TokenData)?
  3. Какие подходы и правила повышают читаемость и поддерживаемость кода при работе с зависимостями в FastAPI? Есть ли архитектурные признаки (аналогии с нормализацией БД), которые подскажут, когда стоит возвращать минимальные данные, а когда — полные объекты?

Какие рекомендации по именованию и структуре возвращаемых значений вы бы посоветовали в таких ситуациях?

Лучше именовать параметр в эндпоинте так, чтобы он отражал именно то, что возвращает зависимость — например username или current_user; в FastAPI при использовании Depends это значительно повышает читаемость кода. Возвращайте из зависимости минимально достаточную и типизированную структуру (строка для одного поля или Pydantic‑модель TokenData/User), а сложную логику разделяйте на мелкие зависимости — проверка токена, декодирование payload, загрузка пользователя. Такие правила (именование, явные аннотации типов, композиция зависимостей) делают код модульным, безопасным и удобным для тестов и автодополнения в IDE.


Содержание


Именование параметров в FastAPI и Depends

Имена параметров — маленькая, но очень важная вещь. Если ваша зависимость check_refresh_token возвращает username (строку), то в эндпоинте параметр должен называться username или current_username, а не token. Это исключает путаницу и делает код само‑документируемым.

Почему это важно:

  • Имя параметра сообщает сразу, что именно вы используете в теле функции (не нужно читать реализацию зависимости).
  • IDE и статические анализаторы лучше подсказывают типы, особенно при явных аннотациях.
  • OpenAPI‑документация и читаемость для коллег улучшаются.

Примеры имен:

  • dependency возвращает username (str) → endpoint: username: str = Depends(check_refresh_token) или current_username: str = Depends(check_refresh_token).
  • dependency возвращает Pydantic модель TokenData → endpoint: token_data: TokenData = Depends(check_refresh_token).
  • dependency возвращает объект пользователя → endpoint: current_user: User = Depends(get_current_user).

См. также рекомендации по именованию параметров в официальной документации FastAPI: Tutorial — Path Parameters.


Что возвращать из зависимости — строка, payload или Pydantic‑модель?

Есть несколько практик, и выбор зависит от контекста. Главный принцип — возвращать минимально достаточные и типизированные данные.

Варианты и когда их использовать:

  • Простая строка (str) — когда вам нужен только username. Простой, лёгкий, очевидный.
  • Словарь/raw payload — удобно быстро, но плохо для читаемости и автодополнения; риск опечаток.
  • Pydantic‑модель (например TokenData) — рекомендуемая практика: валидация, автодополнение, документация. Хорошо, если токен содержит несколько значимых полей (username, scopes, exp).
  • Полный User (Pydantic модель) — возвращать, если endpoint реально работает с объектом пользователя (поля профиля, роли и т.п.). Чаще получают пользователя отдельной зависимостью, чтобы разделить обязанности.

FastAPI сама поощряет использование Pydantic для структуры входных/выходных данных — см. Request Body and Fields и пример работы с OAuth2/JWT: OAuth2 with JWT.

Рекомендация:

  • Для проверки токена и извлечения идентификатора — возвращайте TokenData с явно объявленными полями.
  • Для доступа к данным из БД — создавайте отдельную зависимость get_current_user(token_data: TokenData = Depends(...)), которая возвращает User из БД.

Архитектурные принципы и аналогия с нормализацией БД

Подход «разделяй и властвуй» здесь работает отлично. Представьте зависимости как уровни нормализации:

  • Первый уровень (как первичный ключ): проверка подписи токена и извлечение минимального идентификатора (например, username или user_id).
  • Второй уровень (как внешние ключи и связи): загрузка дополнительной информации (пользователя, прав) по этому идентификатору.

Преимущества композиции:

  • Меньше дублирования логики.
  • Легче тестировать отдельные шаги.
  • Можно переиспользовать мелкие зависимости в разных цепочках.

Когда возвращать минимальные данные:

  • Если большая часть эндпоинтов использует лишь идентификатор (например, только для логирования или простого создания токена) — возвращайте username или id.
    Когда возвращать полный объект:
  • Если большинство эндпоинтов сразу нуждаются в полном User (поля профиля, роли и т.д.), имеет смысл, чтобы верхний слой зависимости сразу возвращал User (чтобы не делать N запросов к БД).

Оптимизация: если и проверка токена, и загрузка пользователя выполняются часто вместе, объединённая зависимость уменьшит количество вызовов БД; но это снижает гибкость. Балансируйте читаемость/производительность по потребностям проекта.

Дополнительные возможности описаны в разделе про продвинутые зависимости: Advanced Dependencies.


Практические шаблоны и примеры кода

  1. Минимально: зависимость возвращает строку (username)
python
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def check_refresh_token(token: str = Depends(oauth2_scheme)) -> str:
 payload = decode_jwt(token) # ваша логика декодирования
 username = payload.get("sub")
 if not username:
 raise HTTPException(status_code=401, detail="Invalid refresh token")
 return username

@app.post("/refresh")
@limiter.limit("10/minute")
async def refresh(username: str = Depends(check_refresh_token)):
 access_token = create_access_token({"sub": username})
 return {"access_token": access_token}
  1. Возврат Pydantic‑модели TokenData (рекомендуется, если нужны дополнительные поля)
python
from pydantic import BaseModel

class TokenData(BaseModel):
 username: str
 scopes: list[str] = []
 exp: int | None = None

def check_refresh_token(token: str = Depends(oauth2_scheme)) -> TokenData:
 payload = decode_jwt(token)
 return TokenData(username=payload.get("sub"), scopes=payload.get("scopes", []))

@app.post("/refresh")
async def refresh(token_data: TokenData = Depends(check_refresh_token)):
 access_token = create_access_token({"sub": token_data.username})
 return {"access_token": access_token}
  1. Композиция: проверка токена → загрузка пользователя из БД
python
async def get_token_data(token: str = Depends(oauth2_scheme)) -> TokenData:
 return TokenData(...)

async def get_current_user(token_data: TokenData = Depends(get_token_data), db=Depends(get_db)) -> User:
 user = await db.users.get_by_username(token_data.username)
 if not user:
 raise HTTPException(status_code=404, detail="User not found")
 return User.from_orm(user)

@app.post("/refresh")
async def refresh(current_user: User = Depends(get_current_user)):
 access_token = create_access_token({"sub": current_user.username})
 return {"access_token": access_token}

Эти шаблоны соответствуют рекомендациям по безопасности и модульности, приведённым в OAuth2 с JWT.


Безопасность, обработка ошибок и тестируемость

Несколько практических пометок:

  • Всегда проверяйте подпись токена, срок действия и тип (access vs refresh).
  • Не возвращайте из зависимости чувствительные поля (password_hash, секреты).
  • Для ошибок используйте HTTPException с корректным кодом и заголовком WWW-Authenticate, если нужно.
  • Пишите юнит‑тесты для зависимости как для обычной функции — их удобно тестировать отдельно, без запуска приложения.
  • Если в нескольких местах вы загружаете пользователя, подумайте о кэшировании на время запроса (request-scoped cache) или использовании Depends для единого вызова.

Документация FastAPI по безопасности содержит полезные примеры: Security — FastAPI.


Контрольный список: рекомендации по именованию и структуре

  • Именуйте параметр в эндпоинте согласно возвращаемому значению: username, token_data, current_user.
  • Давайте зависимостям говорящие имена: verify_refresh_token, get_token_data, get_current_user.
  • Аннотируйте возвращаемый тип (строка, TokenData, User) — это помогает IDE и тестам.
  • Возвращайте Pydantic‑модель вместо сырых dict, если структура не тривиальна.
  • Разделяйте обязанности: валидация токена ≠ загрузка пользователя.
  • Используйте классовые зависимости или yield для управления ресурсами, если нужно (см. Advanced Dependencies).
  • Не возвращайте лишние или чувствительные данные.
  • Документируйте зависимость кратким docstring — быстро помогает понять, что она делает.
  • Покройте зависимости тестами: отдельно проверяйте декодирование, отдельный тест для загрузки пользователя.
  • При многократных запросах к БД внутри цепочки зависимостей — продумайте кэширование запроса по идентификатору.

Источники


Заключение

Итог: в FastAPI при использовании Depends именуйте параметры так, чтобы они отражали возвращаемое значение (username, token_data, current_user), возвращайте минимально достаточную и типизированную структуру (строка или Pydantic‑модель TokenData/User) и разделяйте обязанности между зависимостями (валидировать токен отдельно, загружать пользователя отдельно). Такой подход улучшает читаемость кода, упрощает тестирование и поддерживает безопасность при работе с токенами и обновлением токенов.

Авторы
Проверено модерацией
Модерация