Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Response model
response_model

Response model

Форматирование ответов, скрытие полей, статус-коды

Открыть лабораториюv1.0Запускается локально из публичного репозитория

Response Model — форматирование ответов в FastAPI

Response model определяет, как FastAPI форматирует ответ клиенту. В этой теме вы научитесь скрывать поля, настраивать статус-коды и создавать разные представления одних и тех же данных.

#Зачем нужен response_model?

По умолчанию FastAPI возвращает всё, что вернула функция:

@app.post('/users') def create_user(user: UserCreate): return {'id': 1, **user.model_dump(), 'password': 'secret123'}

Проблема: В ответ попадёт пароль!

Решение: Используйте response_model:

@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): return {'id': 1, **user.model_dump(), 'password': 'secret123'}

FastAPI отфильтрует ответ по схеме UserResponse и исключит поле password.

#Базовое использование

from fastapi import FastAPI from pydantic import BaseModel, EmailStr, ConfigDict from datetime import datetime app = FastAPI() class UserCreate(BaseModel): username: str email: EmailStr password: str class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str email: str created_at: datetime @app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): # В реальности — сохранение в БД return { 'id': 1, 'username': user.username, 'email': user.email, 'created_at': datetime.now(), 'password': 'secret123' # Не попадёт в ответ! }

Ответ клиенту:

{ "id": 1, "username": "john", "email": "john@example.com", "created_at": "2024-01-01T12:00:00" }

Поле password исключено автоматически.

#Скрытие полей: exclude

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

class User(BaseModel): username: str email: str password: str token: str @app.get('/users/me', response_model=User, response_model_exclude={'password', 'token'}) def get_current_user(): return { 'username': 'john', 'email': 'john@example.com', 'password': 'secret', 'token': 'abc123' }

Ответ:

{ "username": "john", "email": "john@example.com" }

#Исключение полей

ПараметрОписаниеПример
response_model_excludeИсключить поля{'password', 'token'}
response_model_includeВключить только эти{'username', 'email'}
exclude_none=TrueИсключить None-значенияresponse_model_exclude_none=True

#exclude_none — скрытие пустых полей

class Item(BaseModel): id: int name: str description: str | None = None price: float | None = None @app.get('/items/1', response_model=Item, response_model_exclude_none=True) def get_item(): return { 'id': 1, 'name': 'Laptop', 'description': None, 'price': None }

Ответ:

{ "id": 1, "name": "Laptop" }

None-поля исключены из ответа.

#Статус-коды

FastAPI автоматически устанавливает статус-коды:

  • 200 OK — GET, PUT, PATCH
  • 201 Created — POST (нужно указать явно)
  • 204 No Content — DELETE (нужно указать явно)

#Как вернуть 201 Created?

from fastapi import FastAPI, status app = FastAPI() @app.post('/items', status_code=status.HTTP_201_CREATED) def create_item(item: ItemCreate): return {'id': 1, **item.model_dump()}

Или числом:

@app.post('/items', status_code=201) def create_item(item: ItemCreate): return {'id': 1, **item.model_dump()}

#Предопределённые статус-коды

status модуль содержит все HTTP-статусы:

from fastapi import status status.HTTP_200_OK # 200 status.HTTP_201_CREATED # 201 status.HTTP_204_NO_CONTENT # 204 status.HTTP_400_BAD_REQUEST # 400 status.HTTP_404_NOT_FOUND # 404 status.HTTP_500_INTERNAL_SERVER_ERROR # 500

#Изменение статуса в функции

Иногда статус зависит от логики:

from fastapi import Response @app.put('/items/{item_id}') def update_item(item_id: int, item: ItemUpdate, response: Response): # Проверяем, существует ли элемент existing = get_item_from_db(item_id) if existing: # Обновляем существующий return {'id': item_id, **item.model_dump()} else: # Создаём новый, меняем статус на 201 response.status_code = status.HTTP_201_CREATED return {'id': item_id, **item.model_dump()}

#Response-модели для разных операций

Хорошая практика — разные модели для разных операций:

class UserBase(BaseModel): username: str email: EmailStr class UserCreate(UserBase): password: str = Field(..., min_length=8) class UserUpdate(BaseModel): username: str | None = None email: EmailStr | None = None password: str | None = None class UserResponse(UserBase): model_config = ConfigDict(from_attributes=True) id: int created_at: datetime class UserInDB(UserResponse): password_hash: str is_active: bool

#Endpoints с разными моделями

@app.post('/users', response_model=UserResponse, status_code=201) def create_user(user: UserCreate): # Создаём пользователя return {'id': 1, 'username': user.username, 'email': user.email, 'created_at': datetime.now()} @app.get('/users/{user_id}', response_model=UserResponse) def get_user(user_id: int): # Получаем пользователя return {'id': user_id, 'username': 'john', 'email': 'john@example.com', 'created_at': datetime.now()} @app.put('/users/{user_id}', response_model=UserResponse) def update_user(user_id: int, user: UserUpdate): # Обновляем пользователя return {'id': user_id, 'username': 'john_updated', 'email': 'john@example.com', 'created_at': datetime.now()}

#Вложенные response-модели

class Address(BaseModel): city: str street: str class Post(BaseModel): id: int title: str content: str class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str address: Address posts: list[Post] @app.get('/users/{user_id}', response_model=UserResponse) def get_user(user_id: int): return { 'id': user_id, 'username': 'john', 'address': {'city': 'Moscow', 'street': 'Tverskaya'}, 'posts': [ {'id': 1, 'title': 'Post 1', 'content': '...'}, {'id': 2, 'title': 'Post 2', 'content': '...'} ] }

FastAPI рекурсивно применит модели к вложенным структурам.

#from_attributes для работы с ORM

При работе с SQLAlchemy или другими ORM объекты — не словари:

# SQLAlchemy модель class UserDB: def __init__(self, id, username, email): self.id = id self.username = username self.email = email db_user = UserDB(id=1, username='john', email='john@example.com')

Без from_attributes Pydantic не сможет прочитать атрибуты:

class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str email: str

Теперь можно вернуть ORM-объект напрямую:

@app.get('/users/{user_id}', response_model=UserResponse) def get_user(user_id: int): user = db.query(User).filter(User.id == user_id).first() return user # FastAPI сам извлечёт атрибуты

#Пример: Полноценное API с response models

from fastapi import FastAPI, status, HTTPException from pydantic import BaseModel, EmailStr, Field, ConfigDict from datetime import datetime from typing import Literal app = FastAPI() # Модели class PostBase(BaseModel): title: str = Field(..., min_length=5, max_length=200) content: str = Field(..., min_length=10) class PostCreate(PostBase): tags: list[str] = [] status: Literal['draft', 'published'] = 'draft' class PostUpdate(BaseModel): title: str | None = Field(None, min_length=5, max_length=200) content: str | None = Field(None, min_length=10) status: Literal['draft', 'published'] | None = None class PostResponse(PostBase): model_config = ConfigDict(from_attributes=True) id: int tags: list[str] status: str created_at: datetime updated_at: datetime | None = None class PostPublic(PostResponse): """Публичная модель — без служебных полей""" author_id: int views_count: int = 0 # Имитация БД posts_db = [] @app.post('/posts', response_model=PostResponse, status_code=status.HTTP_201_CREATED) def create_post(post: PostCreate): """Создать пост (доступно авторам)""" post_id = len(posts_db) + 1 new_post = { 'id': post_id, **post.model_dump(), 'created_at': datetime.now(), 'updated_at': None, 'author_id': 1, # В реальности — из аутентификации 'views_count': 0 } posts_db.append(new_post) return new_post @app.get('/posts/{post_id}', response_model=PostPublic) def get_post(post_id: int): """Получить пост (публичный endpoint)""" for post in posts_db: if post['id'] == post_id: return post raise HTTPException(status_code=404, detail="Post not found") @app.put('/posts/{post_id}', response_model=PostResponse) def update_post(post_id: int, post_update: PostUpdate): """Обновить пост""" for post in posts_db: if post['id'] == post_id: # Обновляем только переданные поля update_data = post_update.model_dump(exclude_unset=True) post.update(update_data) post['updated_at'] = datetime.now() return post raise HTTPException(status_code=404, detail="Post not found") @app.delete('/posts/{post_id}', status_code=status.HTTP_204_NO_CONTENT) def delete_post(post_id: int): """Удалить пост""" for i, post in enumerate(posts_db): if post['id'] == post_id: posts_db.pop(i) return Response(status_code=status.HTTP_204_NO_CONTENT) raise HTTPException(status_code=404, detail="Post not found") @app.get('/posts', response_model=list[PostPublic]) def list_posts( status_filter: Literal['draft', 'published'] | None = None, limit: int = 10, offset: int = 0 ): """Список постов с фильтрацией""" posts = posts_db if status_filter: posts = [p for p in posts if p['status'] == status_filter] return posts[offset:offset + limit]

#Частые ошибки

#Ошибка 1: Возврат данных, не соответствующих модели

class UserResponse(BaseModel): id: int email: str @app.get('/users/1', response_model=UserResponse) def get_user(): return {'id': 1, 'name': 'John'} # Ошибка: нет email!

Проблема: Ответ не соответствует response_model. FastAPI попытается валидировать и может вернуть ошибку.

Решение: Убедитесь, что возвращаемые данные соответствуют модели.

#Ошибка 2: Забыли from_attributes для ORM

class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str @app.get('/users/{id}', response_model=UserResponse) def get_user(id: int): user = db.query(User).first() # ORM-объект return user # Ошибка без from_attributes!

Решение:

class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str

#Ошибка 3: Response model в GET с параметрами

@app.get('/items') def get_items(q: str): # Ошибка: q будет прочитан из body, а не query! ...

Проблема: Если нет response_model и функция принимает параметры, FastAPI может неправильно определить источник данных.

Решение: Явно укажите источник:

from fastapi import Query @app.get('/items') def get_items(q: str = Query(...)): ...

#Ошибка 4: Излишняя сложность

@app.get('/simple', response_model=dict, response_model_exclude_none=True) def get_simple(): return {'a': 1, 'b': None}

Проблема: response_model=dict отключает валидацию и документирование.

Решение: Используйте Pydantic-модель или упростите:

@app.get('/simple') def get_simple(): return {'a': 1} # Просто исключите None в коде

#Исполняемая лаборатория

Лаборатория «API статей с фильтрацией полей» доступна в публичном GitLab. Она проверяет response_model для скрытия внутренних полей, response_model_exclude, response_model_exclude_none, from_attributes для ORM и разные модели для разных операций.

Склонируйте репозиторий и запустите тесты из его корня:

git clone --branch v1.0 --depth 1 https://gitlab.potapov.me/courses/fastapi_pro.git cd fastapi_pro uv sync --group test uv run --group test pytest response_model/tests

Далее: Обработка ошибок