Сегодня мы разберём, как сделать наш API более устойчивым и дружелюбным, грамотно обрабатывая ошибки и исключения.
Когда вы в первый раз запускаете API и получаете красивую (но пугающую) ошибку 500, это сигнал к тому, что пора говорить об обработке ошибок. Ведь если вы не контролируете, как приложение реагирует на проблемы, это делают ваши пользователи. А нам, разработчикам, такой сценарий не подходит.
Основы обработки ошибок
Представьте, что вы разработали API, который должен возвращать данные пользователей. Но пользователь запрашивает данные несуществующего ID, и... БУМ! Сервер возвращает страшный 500-й код с огромным трейсбэком. В реальной жизни такие случаи могут привести к потере доверия пользователей и утечке конфиденциальной информации. Поэтому наша задача — перехватывать такие ситуации и отвечать осмысленно.
FastAPI предоставляет мощные инструменты для обработки ошибок, и всё это можно сделать, не ломая голову о сложности реализации.
Исключения и ошибки в Python
В Python исключения обрабатываются с помощью конструкции try-except. Это позволяет перехватывать ошибки и делать ваш код более устойчивым:
try:
value = 5 / 0
except ZeroDivisionError:
print("На ноль делить нельзя!")
FastAPI расширяет эту концепцию, предоставляя более удобный способ работы с HTTP-специфичными исключениями.
Обработка ошибок в FastAPI
Основной инструмент для обработки ошибок в FastAPI — класс HTTPException из модуля fastapi.exceptions. С его помощью мы можем вернуть пользователю понятный HTTP-ответ с соответствующим кодом и описанием ошибки.
Пример обработки 404 ошибки:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id < 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}
Если запросить элемент с ID, которого не существует, сервер вернёт ответ:
{
"detail": "Item not found"
}
Генерация кодов ошибок
FastAPI поддерживает все стандартные HTTP-статусы. Вот самые популярные из них и ситуации, в которых они используются:
| Код | Описание | Когда использовать |
|---|---|---|
| 400 | Bad Request | Неверный запрос, например, некорректные данные от клиента. |
| 401 | Unauthorized | Пользователь не прошёл аутентификацию. |
| 403 | Forbidden | Пользователь не имеет прав на доступ к ресурсу. |
| 404 | Not Found | Запрошенный ресурс не найден. |
| 500 | Internal Server Error | Необработанная ошибка на стороне сервера. |
Пример обработки нескольких типов ошибок
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id < 0:
raise HTTPException(status_code=400, detail="User ID must be positive")
if user_id not in [1, 2, 3]:
raise HTTPException(status_code=404, detail="User not found")
return {"user_id": user_id}
Здесь мы обрабатываем две ситуации: если ID пользователя отрицательный (400) и если пользователь не найден (404).
Кастомизация ответов об ошибках
Иногда требуется настроить текст ошибок или возвращать дополнительную информацию. Например, вы хотите добавить поле documentation_url, которое указывает, где искать помощь.
@app.get("/docs_error")
async def docs_error():
raise HTTPException(
status_code=400,
detail={"message": "Invalid request", "documentation_url": "http://myapi/docs/errors"}
)
Ответ API:
{
"detail": {
"message": "Invalid request",
"documentation_url": "http://myapi/docs/errors"
}
}
Middleware для обработки глобальных ошибок
FastAPI позволяет настроить глобальную обработку ошибок через middleware. Это полезно, если вы хотите обработать все неожиданные исключения, не добавляя избыточный код в каждый эндпоинт.
Пример middleware для 500 Internal Server Error
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
class CustomErrorMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
try:
response = await call_next(request)
return response
except Exception as exc:
return JSONResponse(
status_code=500,
content={"detail": "Internal Server Error", "error": str(exc)}
)
app.add_middleware(CustomErrorMiddleware)
Теперь все необработанные ошибки будут возвращать более дружелюбный ответ.
Пример обработки пользовательских ошибок
FastAPI также позволяет создавать свои исключения. Это полезно, когда вам нужно писать повторяющийся код для одной группы эндпоинтов.
from fastapi import Request
class UserNotFoundError(Exception):
def __init__(self, user_id: int):
self.user_id = user_id
@app.exception_handler(UserNotFoundError)
async def user_not_found_handler(request: Request, exc: UserNotFoundError):
return JSONResponse(
status_code=404,
content={"detail": f"User with ID {exc.user_id} not found"}
)
@app.get("/custom_users/{user_id}")
async def read_user(user_id: int):
if user_id not in [1, 2, 3]:
raise UserNotFoundError(user_id)
return {"user_id": user_id}
Если пользователь с заданным ID не существует, ваш кастомный обработчик вернёт аккуратный и понятный ответ.
Генерация подробных ответов об ошибках
Когда вы работаете с API, где клиенту важно понимать, что пошло не так, имеет смысл предоставлять более подробные ответы. Например, если ошибка произошла из-за валидации.
Если указать неправильное значение для модели Pydantic, FastAPI автоматически сгенерирует подробный ответ:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
@app.post("/users/")
async def create_user(user: User):
return user
При отправке некорректных данных, таких как id: "string", API вернёт подробный ответ с деталями о неправильных полях.
Логирование ошибок
Обработка ошибок тесно связана с логированием. Хотя клиенту вы можете вернуть дружелюбный ответ, разработчики (то есть вы) должны видеть полную картину. Используйте встроенный модуль logging для этих целей:
import logging
logger = logging.getLogger("api_errors")
@app.get("/error_example")
async def error_example():
try:
1 / 0
except ZeroDivisionError as e:
logger.error(f"Unhandled exception: {str(e)}")
raise HTTPException(status_code=500, detail="Something went wrong")
Теперь ваш API готов к взаимодействию с миром, включая дружелюбные пользователи, невинные баги и программисты, которые в своих запросах творят магию хаоса!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ