API, схемы и валидация
граница, на которой проверяют всё
Что это
API — набор адресов, по которым с твоим сервером можно разговаривать. Схема — описание того, какие данные приходят и уходят. Валидация — проверка, что пришедшее соответствует схеме.
Для работы с ИИ это самое важное место бэкенда: граница, где данные приходят снаружи, — единственное место, где их ещё можно проверить. Пропустил здесь — дальше неверные данные расходятся по всему приложению.
Как выглядит в коде
from pydantic import BaseModel, Field
class SubIn(BaseModel):
city: str = Field(min_length=2, max_length=50)
class SubOut(BaseModel):
user_id: int
cities: list[str]
@app.post("/subs/{user_id}", response_model=SubOut, status_code=201)
def add_sub(user_id: int, body: SubIn) -> SubOut:
cities = SUBS.setdefault(user_id, [])
if body.city in cities:
raise HTTPException(status_code=409, detail="уже подписан")
cities.append(body.city)
return SubOut(user_id=user_id, cities=cities)Что происходит при неверных данных:
curl -X POST http://127.0.0.1:8765/subs/7 \
-H "Content-Type: application/json" -d '{"city":"X"}'[422]
{"detail":[{"type":"string_too_short","loc":["body","city"],
"msg":"String should have at least 2 characters",
"input":"X","ctx":{"min_length":2}}]}Обработчик не запустился вообще. Проверка сработала до него, ответ описывает ровно то, что не так и где.
Почему это та же мысль, что и типы
Вернись к статье 6.3: mypy ловит ошибки до запуска. Схема делает то же самое
на границе сервера — ловит неверные данные до обработчика.
Разница в том, где проходит граница:
| Инструмент | Что проверяет | Когда |
|---|---|---|
mypy | твой код согласован сам с собой | до запуска |
| Схема запроса | данные снаружи соответствуют ожиданиям | до обработчика |
| Схема ответа | ты отдал то, что обещал | до отправки |
Три разных рубежа, одна идея: описать форму данных один раз и дать машине проверять.
Что писать в схеме
Не только типы, но и ограничения:
class SubIn(BaseModel):
city: str = Field(min_length=2, max_length=50)
notify_at: int = Field(ge=0, le=23) # час от 0 до 23
email: EmailStr | None = None # формат проверяетсяКаждое ограничение в схеме — это проверка, которую не придётся писать руками и о которой не забудет ИИ.
Адреса
Короткие правила, которых хватает:
| Правило | Пример |
|---|---|
| Существительные во множественном числе | /subs, /users |
| Действие выражает метод, а не адрес | DELETE /subs/7, не /deleteSub |
| Вложенность — через путь | /users/7/subs |
| Фильтры — через строку запроса | /subs?city=Oslo&limit=10 |
Как сказать ИИ
Опиши схему запроса и ответа для POST /subs/{user_id}:
город — строка от 2 до 50 символов, час уведомления —
число от 0 до 23, необязательное. В ответе: user_id
и список городов. Код обработчика пока не пиши.Схема как постановка задачи — то же, что сигнатура в статье 6.5: короче слов и проверяется машиной.
Проверь, что в response_model не попадают внутренние поля:
хеши паролей, служебные id, пометки. Перечисли, что отдаётся
клиенту.Какие ограничения стоит добавить в эту схему, чтобы кривые
данные не доходили до обработчика? Только ограничения,
логику не трогай.На что смотреть
Валидация внутри обработчика. if len(city) < 2: raise ... — работает, но
проверка размазывается по коду, и часть случаев забывается. Место проверок —
схема.
dict вместо схемы. def add_sub(body: dict) отключает проверку целиком:
придёт что угодно.
Нет response_model. Отдаётся всё, что вернул обработчик.
Схема есть, ограничений нет. city: str без длины примет и пустую строку,
и строку на мегабайт.
Разные схемы для одного и того же. Три слегка отличающихся описания подписки в трёх местах — рассинхронизируются при первой же правке.
Шпаргалка
Граница сервера — единственное место, где внешние данные ещё можно проверить.
Схема описывает форму, валидация проверяет, фреймворк делает это сам.
При неверных данных обработчик не запускается вообще.
response_modelзащищает от случайной отдачи лишних полей.Ограничения (
min_length,ge,le) — проверки, которые не забудутся.Схема в запросе к ИИ работает как сигнатура: точнее слов.