Ответы и ошибки
Хорошее API отвечает предсказуемо: правильным кодом, понятным телом ошибки и нужными заголовками.
Коды ответов
| Код | Когда |
|---|---|
| 200 OK | обычный успешный ответ |
| 201 Created | создана запись |
| 204 No Content | успешно, тела нет (часто на DELETE) |
| 400 Bad Request | неверный запрос |
| 401 / 403 | не представились / нельзя |
| 404 Not Found | записи нет |
| 409 Conflict | запрос противоречит данным (такая запись уже есть) |
| 422 | данные не прошли проверку (FastAPI отвечает сам) |
Вместо чисел можно писать константы: from fastapi import status → status.HTTP_409_CONFLICT.
Свои исключения
Когда ошибок много, удобно завести своё исключение и одно место, которое превращает его в ответ:
from fastapi import Request
from fastapi.responses import JSONResponse
class AppError(Exception):
def __init__(self, code: int, message: str):
super().__init__(message)
self.code = code
self.message = message
@app.exception_handler(AppError)
def app_error(request: Request, exc: AppError):
return JSONResponse(status_code=exc.code, content={"error": exc.message})
@app.post("/todos/{tid}/done")
def mark_done(tid: int):
if tid not in TODOS:
raise AppError(404, "Задача не найдена")
...
Код логики не думает о формате ответа — он просто бросает исключение. А если формат ошибок понадобится поменять, правка будет в одном месте.
Заголовки и пустые ответы
from fastapi import Response
@app.post("/todos", status_code=201)
def create(todo: TodoIn, response: Response):
...
response.headers["Location"] = f"/todos/{item['id']}"
return item
@app.delete("/todos/{tid}", status_code=204)
def delete(tid: int):
...
return Response(status_code=204)
- параметр с типом
Response— объект будущего ответа: в него пишут заголовки (response.headers), cookie (response.set_cookie) и код (response.status_code); - заголовок
Locationв ответе 201 сообщает адрес созданной записи; - для 204 тела быть не должно, поэтому возвращают пустой
Response.
Номера записей
Номер новой записи нельзя считать как len(TODOS) + 1: после удаления номера начнут повторяться, и новая задача затрёт старую. Используйте счётчик, который только растёт, — itertools.count(1) и next(ids). В базе данных этим занимается сама база.
Итог
- коды 201, 204, 409 и константы
status.HTTP_…; - своё исключение +
@app.exception_handler+JSONResponse; response: Response— заголовки,Locationдля созданной записи;Response(status_code=204); номера — растущим счётчиком.