JavaRush /Курсы /Модуль 4: FastAPI /Ошибки и исключения: обработка и генерация ответов

Ошибки и исключения: обработка и генерация ответов

Модуль 4: FastAPI
2 уровень , 9 лекция
Открыта

Сегодня мы разберём, как сделать наш 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 готов к взаимодействию с миром, включая дружелюбные пользователи, невинные баги и программисты, которые в своих запросах творят магию хаоса!

1
Задача
Модуль 4: FastAPI, 2 уровень, 9 лекция
Недоступна
Простая обработка ошибки 404
Простая обработка ошибки 404
1
Задача
Модуль 4: FastAPI, 2 уровень, 9 лекция
Недоступна
Обработка нескольких типов ошибок
Обработка нескольких типов ошибок
3
Опрос
Работа с Query Parameters, 2 уровень, 9 лекция
Недоступен
Работа с Query Parameters
Работа с Query Parameters
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ