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

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

@potapov_me

Платформа

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

Контент

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

Компания

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

Аккаунт

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

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

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

Request body

Отправка данных в теле запроса, Pydantic модели, валидация

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

Request Body — отправка данных в FastAPI

Когда нужно создать или обновить ресурс, данные отправляются в теле запроса. В этой теме вы научитесь использовать Pydantic-модели для валидации, работать с вложенными структурами и обрабатывать разные форматы данных.

#GET vs POST: когда использовать body?

GET-запросы используют query-параметры:

GET /items?skip=0&limit=10

Тело запроса пустое, данные в URL.

POST/PUT/PATCH-запросы используют тело запроса:

POST /items Content-Type: application/json { "name": "Laptop", "price": 999.99, "in_stock": true }

#Когда использовать body?

  • Создание ресурса (POST) — много данных, сложная структура
  • Обновление ресурса (PUT/PATCH) — изменяемые поля
  • Сложная фильтрация — когда query-параметров недостаточно
  • Чувствительные данные — пароли, токены (не попадают в логи URL)

#Pydantic-модели

FastAPI использует Pydantic для описания структуры данных:

from pydantic import BaseModel class ItemCreate(BaseModel): name: str price: float in_stock: bool = True

#Что делает Pydantic?

  1. Валидация типов — проверяет, что name — строка, price — число
  2. Преобразование типов — строку "999.99" преобразует в float
  3. Значения по умолчанию — in_stock будет True, если не передано
  4. Сериализация — модель можно превратить в словарь через .model_dump()

#Базовый endpoint с body

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ItemCreate(BaseModel): name: str price: float description: str | None = None in_stock: bool = True @app.post('/items') def create_item(item: ItemCreate): # item — это экземпляр ItemCreate с валидированными данными return { 'id': 1, **item.model_dump() }

#Как это работает?

  1. Клиент отправляет POST с JSON-телом
  2. FastAPI парсит JSON
  3. Создаёт экземпляр ItemCreate
  4. Валидирует данные
  5. Если валидация не прошла → 422 ошибка
  6. Если прошла → вызывает функцию с item

#Пример запроса

POST /items Content-Type: application/json { "name": "Laptop", "price": 999.99, "description": "Gaming laptop" }

Ответ:

{ "id": 1, "name": "Laptop", "price": 999.99, "description": "Gaming laptop", "in_stock": true }

#Пример ошибки валидации

Запрос с неверным типом:

{ "name": "Laptop", "price": "not_a_number" }

Ответ 422:

{ "detail": [ { "type": "float_parsing", "loc": ["body", "price"], "msg": "Input should be a valid number", "input": "not_a_number" } ] }

#Обязательные и необязательные поля

class ItemCreate(BaseModel): # Обязательное поле name: str # Обязательное поле price: float # Необязательное поле (может быть None) description: str | None = None # Необязательное поле со значением по умолчанию in_stock: bool = True # Необязательное поле со значением по умолчанию quantity: int = 0

#Правила

ОбъявлениеОбязательно?Может быть None?Значение по умолчанию
name: strДаНет—
name: str | NoneДаДа—
name: str | None = NoneНетДаNone
name: str = "default"НетНет"default"

#Примеры запросов

Минимальный (только обязательные):

{"name": "Laptop", "price": 999.99}

Полный:

{ "name": "Laptop", "price": 999.99, "description": "Gaming", "in_stock": false, "quantity": 5 }

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

Модели могут содержать другие модели:

from pydantic import BaseModel class Address(BaseModel): city: str street: str zip_code: str class UserCreate(BaseModel): name: str email: str address: Address

Пример запроса:

{ "name": "John Doe", "email": "john@example.com", "address": { "city": "Moscow", "street": "Tverskaya 1", "zip_code": "123456" } }

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

#Списки и словари

from pydantic import BaseModel from typing import Literal class ProductCreate(BaseModel): name: str tags: list[str] = [] # Список строк prices: dict[str, float] = {} # Словарь {регион: цена} status: Literal['draft', 'published', 'archived'] = 'draft'

Пример запроса:

{ "name": "Laptop", "tags": ["electronics", "computers", "gaming"], "prices": { "US": 999.99, "EU": 899.99, "RU": 79999.99 }, "status": "published" }

#Literal для фиксированных значений

Literal ограничивает набор допустимых значений:

status: Literal['draft', 'published', 'archived']

Запрос со status: "invalid" вернёт 422 ошибку.

#Валидация с Field()

Для валидации внутри модели используется Field():

from pydantic import BaseModel, Field, EmailStr class UserCreate(BaseModel): username: str = Field(..., min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$') email: EmailStr # Встроенный тип для email age: int = Field(..., ge=18, le=120) password: str = Field(..., min_length=8) bio: str | None = Field(None, max_length=500)

#Ограничения Field()

ПараметрДля типаОписание
ge, le, gt, ltint, floatДиапазон чисел
min_length, max_lengthstr, listДлина строки/списка
patternstrRegex-шаблон
...любойОбязательное поле

#EmailStr

EmailStr автоматически проверяет формат email:

email: EmailStr

Запрос с "email": "invalid" вернёт:

{ "detail": [ { "type": "value_error", "loc": ["body", "email"], "msg": "value is not a valid email address", "input": "invalid" } ] }

#Пример: Полноценное API для блога

from fastapi import FastAPI from pydantic import BaseModel, Field, EmailStr, ConfigDict from datetime import datetime from typing import Literal app = FastAPI() # Модели class CommentCreate(BaseModel): content: str = Field(..., min_length=1, max_length=1000) author: str = Field(..., min_length=3, max_length=50) class PostCreate(BaseModel): title: str = Field(..., min_length=5, max_length=200) content: str = Field(..., min_length=10, max_length=50000) tags: list[str] = [] status: Literal['draft', 'published', 'scheduled'] = 'draft' published_at: datetime | None = None class PostResponse(BaseModel): id: int title: str content: str tags: list[str] status: str created_at: datetime comments_count: int = 0 # Хранилище (в реальности — база данных) posts_db = [] comments_db = {} # Endpoints @app.post('/posts', response_model=PostResponse, status_code=201) def create_post(post: PostCreate): post_id = len(posts_db) + 1 new_post = { 'id': post_id, **post.model_dump(), 'created_at': datetime.now(), 'comments_count': 0 } posts_db.append(new_post) comments_db[post_id] = [] return new_post @app.post('/posts/{post_id}/comments') def add_comment(post_id: int, comment: CommentCreate): if post_id not in comments_db: from fastapi import HTTPException raise HTTPException(status_code=404, detail="Post not found") comment_data = { 'id': len(comments_db[post_id]) + 1, **comment.model_dump(), 'created_at': datetime.now() } comments_db[post_id].append(comment_data) # Увеличиваем счётчик комментариев for post in posts_db: if post['id'] == post_id: post['comments_count'] += 1 break return comment_data @app.get('/posts/{post_id}', response_model=PostResponse) def get_post(post_id: int): for post in posts_db: if post['id'] == post_id: return post from fastapi import HTTPException raise HTTPException(status_code=404, detail="Post not found")

#Несколько моделей для одного ресурса

Хорошая практика — разные модели для создания, обновления и ответа:

class UserCreate(BaseModel): """Модель для создания пользователя""" username: str = Field(..., min_length=3) email: EmailStr password: str = Field(..., min_length=8) class UserUpdate(BaseModel): """Модель для обновления (все поля необязательны)""" username: str | None = Field(None, min_length=3) email: EmailStr | None = None password: str | None = Field(None, min_length=8) class UserResponse(BaseModel): """Модель для ответа (без пароля)""" id: int username: str email: str created_at: datetime model_config = ConfigDict(from_attributes=True) # Для работы с ORM

#Зачем разные модели?

UserCreate:

  • Все поля обязательны
  • Включает пароль

UserUpdate:

  • Все поля необязательны (можно обновить только email)
  • Включает пароль (опционально)

UserResponse:

  • Включает id, created_at (генерируются сервером)
  • Не включает пароль (безопасность)

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

#Ошибка 1: Отправка данных в GET-запросе

@app.get('/items') def create_item(item: ItemCreate): # Ошибка: GET не должен иметь body ...

Проблема: GET-запросы не должны иметь тело. Данные передаются через query-параметры.

Решение: Используйте POST для отправки body:

@app.post('/items') def create_item(item: ItemCreate): ...

#Ошибка 2: Возврат модели создания в ответе

@app.post('/users', response_model=UserCreate) def create_user(user: UserCreate): return {'id': 1, **user.model_dump()} # Вернёт пароль!

Проблема: В ответе будет пароль пользователя.

Решение: Используйте response model без пароля:

@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): return {'id': 1, 'username': user.username, 'email': user.email, 'created_at': datetime.now()}

#Ошибка 3: Изменение модели inplace

class ItemCreate(BaseModel): name: str price: float @app.post('/items') def create_item(item: ItemCreate): item.id = 1 # Ошибка: Pydantic модели immutable по умолчанию return item

Решение: Создайте новую модель или используйте словарь:

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

#Ошибка 4: Неправильный Content-Type

Клиент отправляет без заголовка:

POST /items {"name": "Laptop"}

Проблема: Некоторые клиенты не устанавливают Content-Type: application/json.

Решение: FastAPI автоматически определит JSON, но лучше требовать правильный заголовок. В документации Swagger UI заголовок устанавливается автоматически.

#Отладка request body

#Swagger UI

Откройте /docs, найдите POST-эндпоинт:

  1. Нажмите на эндпоинт
  2. Кнопка "Try it out"
  3. Введите данные в поле "Request body"
  4. Execute
  5. Увидите ответ и curl-команду

#Логирование входящих данных

import logging import json logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) @app.post('/items') def create_item(item: ItemCreate): logger.debug(f"Received item: {json.dumps(item.model_dump(), indent=2)}") return {'id': 1, **item.model_dump()}

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

Лаборатория «Создание пользователей с Pydantic v2» доступна в публичном GitLab. Она проверяет вложенные модели, Field(min_length=, max_length=, pattern=), Literal для статусов, разделение моделей Create/Update/Response и extra="forbid".

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

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 request_body/tests

Далее: Response model