JavaRush /Курсы /Модуль 4: FastAPI /Валидация ответов API через Pydantic

Валидация ответов API через Pydantic

Модуль 4: FastAPI
3 уровень , 6 лекция
Открыта

Сегодня мы поговорим о том, как мы можем использовать Pydantic для валидации данных не только на входе (в запросах), но и на выходе — в том, что наше API отправляет пользователю в виде ответа.

Эта тема не менее важна, чем валидация входных данных. Если API возвращает неожиданные или некорректные данные, это может привести к сломанному интерфейсу, разрыву автоматизации, гневным комментариям от коллег — и, чего уж там, к утрате доверия к вашему API. С таким вы, конечно, сталкиваться не хотите.

Представьте, что вы построили сложную систему, где ваш API взаимодействует с фронтендом или другими сервисами. Что произойдёт, если вы случайно вернёте объект не в том формате, который ожидают ваши клиенты? Например:

  1. Фронтенд может перестать отображать данные из-за неправильного формата.
  2. Партнёры, которые используют ваше API, могут начать бить тревогу (и разрывать контракты).
  3. Ошибки сложнее отлаживать (особенно ночью, когда единственная поддержка — это ваша кружка кофе).
Важно помнить:

корректный и ожидаемый формат ответа — это лицо вашего API. Именно поэтому валидация ответов так важна.


Как FastAPI помогает с валидацией выходных данных?

FastAPI интегрирован с Pydantic настолько глубоко, что готов автоматически валидировать данные, которые возвращаются из вашего эндпоинта. Чтобы включить эту магию, достаточно указать, какой Pydantic-моделью должен быть представлен ответ.

Создадим первый пример.

Давайте начнём с простого API, которое возвращает информацию о пользователе:


from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Определяем Pydantic-модель
class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    # Данные из базы данных (или другого источника)
    user = {"id": user_id, "name": "Иван Иванов", "email": "ivan@example.com"}
    return user

Что происходит в этом коде?

  1. Мы создали модель UserResponse, которая описывает, как должны выглядеть возвращаемые данные: id (целое число), name (строка) и email (строка).
  2. В эндпоинте /user/{user_id} мы указали response_model=UserResponse. Это говорит FastAPI: "Все данные, которые возвращаются из этого эндпоинта, должны соответствовать модели UserResponse".
  3. FastAPI автоматически проверит данные перед тем, как отправить их пользователю.

Проверка валидации

Что произойдёт, если мы вернём данные, не соответствующие модели? Например, удалим поле email:


@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = {"id": user_id, "name": "Иван Иванов"}  # Email отсутствует
    return user

FastAPI выбросит ошибку ValueError, потому что поле email является обязательным, но его нет в возвращаемых данных.


Преобразование данных через Pydantic

Pydantic не только валидирует, но и автоматически преобразует данные. Например, если вы вернёте строку для поля id, но оно ожидает целое число, Pydantic постарается "исправить" это.

Пример преобразования


@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = {"id": str(user_id), "name": "Иван Иванов", "email": "ivan@example.com"}  # id как строка
    return user

Несмотря на то, что id здесь представлен строкой, Pydantic преобразует его в целое число, потому что модель ожидает int. Это удобно, когда данные из разных источников имеют разные типы.


Частичная валидация (поле с типом Any)

Иногда структура ответа может быть сложной или динамической, и вы не хотите прописывать все поля модели. Для таких случаев есть поле с типом Any.


from typing import Any

class FlexibleResponse(BaseModel):
    id: int    # Важное поле типа int
    data: Any  # А здесь может быть что угодно

Поля типа Any пропустят любые данные без дополнительной проверки.


Вложенные модели в ответах

Иногда в ответах могут быть сложные вложенные структуры данных. Например, пользователь может иметь список ролей, каждая из которых описана своей моделью.

Пример вложенной модели


from typing import List

class Role(BaseModel):
    id: int
    name: str

class UserResponseWithRoles(BaseModel):
    id: int
    name: str
    email: str
    roles: List[Role]  # Список ролей

@app.get("/user/{user_id}/roles", response_model=UserResponseWithRoles)
async def get_user_with_roles(user_id: int):
    user = {
        "id": user_id,
        "name": "Иван Иванов",
        "email": "ivan@example.com",
        "roles": [{"id": 1, "name": "Администратор"}, {"id": 2, "name": "Пользователь"}],
    }
    return user

FastAPI проверит не только структуру основного объекта, но и каждого элемента списка roles. Если хоть одна роль окажется некорректной, весь ответ будет признан недействительным.


Дополнительные параметры: response_model_exclude и response_model_include

Иногда вы не хотите возвращать все поля модели (например, исключить конфиденциальную информацию). Для этого существуют параметры response_model_exclude и response_model_include.

Пример с исключением полей


@app.get("/user/{user_id}", response_model=UserResponse, response_model_exclude={"email"})
async def get_user_without_email(user_id: int):
    user = {"id": user_id, "name": "Иван Иванов", "email": "ivan@example.com"}
    return user

В данном случае поле email не будет включено в ответ.


Отладка ошибок валидации (углубляемся!)

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

Ошибка может выглядеть так:


{
  "detail": [
    {
      "loc": ["response", "email"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

В данном примере FastAPI говорит, что в возвращаемых данных отсутствует поле email. Это поможет вам быстро найти и исправить проблему.


Практическое задание

  1. Создайте эндпоинт /product/{product_id}, который возвращает описание продукта.
  2. Используйте вложенные модели: продукт должен содержать поля id, name и attributes, где attributes — сложный объект с полями color, size и in_stock (булево значение).
  3. Напишите API, которое возвращает список продуктов (используйте список List).

Теперь вы вооружены знаниями, чтобы ваши ответы были такими же идеальными, как архитектура FastAPI! Не забывайте, что корректные ответы — это залог доверия, успешной интеграции и, конечно же, вашего спокойствия. Удачи и меньше багов!

1
Задача
Модуль 4: FastAPI, 3 уровень, 6 лекция
Недоступна
Валидация вложенных объектов в ответе
Валидация вложенных объектов в ответе
1
Задача
Модуль 4: FastAPI, 3 уровень, 6 лекция
Недоступна
Исключение полей из ответа
Исключение полей из ответа
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ