JavaRush /Курсы /Модуль 4: FastAPI /Создание кастомных обработчиков исключений

Создание кастомных обработчиков исключений

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

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

Например, что делать, если пользователь пытается обновить данные, которые не существуют? Возвращать стандартную ошибку 404? Вряд ли это даст пользователю понять, что конкретно пошло не так. Или как быть с валидатором данных, который кидает исключения с длинными и загадочными сообщениями? Кастомные обработчики исключений позволяют перехватывать такие ситуации и возвращать ответы, понятные и полезные.


Как работают кастомные обработчики исключений в FastAPI

В FastAPI мы можем определять свои собственные классы исключений, а затем создавать обработчики, которые будут срабатывать, когда вызванное исключение попадёт в приложение.

Основные шаги:

  1. Создание кастомных классов исключений.
  2. Регистрация обработчика исключений для FastAPI.
  3. Настройка обработки и возвращаемых ответов.

Начнем с простого.

Предположим, у нас есть приложение, в котором пользователю запрещено искать ресурсы, если у него нет необходимых прав.


from fastapi import FastAPI, HTTPException

app = FastAPI()

# 1. Определяем кастомное исключение
class ResourceNotAllowedException(Exception):
    def __init__(self, resource_name: str):
        self.resource_name = resource_name

# 2. Регистрация обработчика исключения
@app.exception_handler(ResourceNotAllowedException)
async def resource_not_allowed_handler(request, exc: ResourceNotAllowedException):
    return {
        "error": "Access denied",
        "resource": exc.resource_name,
        "message": f"You are not allowed to access the resource: {exc.resource_name}"
    }

# 3. Использование исключения в эндпоинте
@app.get("/items/{item_id}")
async def read_item(item_id: int, access: bool = False):
    if not access:  # если доступ запрещён
        raise ResourceNotAllowedException(resource_name=f"Item {item_id}")
    return {"item_id": item_id, "access": "granted"}

Теперь, если пользователь запрашивает какой-то ресурс с недостаточными правами, мы возвращаем структурированный и понятный ответ вместо сухого HTTP 500.

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


GET /items/42?access=false

Ответ:


{
  "error": "Access denied",
  "resource": "Item 42",
  "message": "You are not allowed to access the resource: Item 42"
}

Почему это удобно? Во-первых, пользователю сразу понятно, что пошло не так — никакой путаницы. Во-вторых, вы можете вынести всю логику обработки ошибок в отдельное исключение и не засорять основной код эндпоинта. А ещё такой подход легко повторно использовать — одно кастомное исключение и его обработчик могут работать во многих частях приложения.


Расширенные обработчики: Работа с HTTP-статусами

В FastAPI можно настроить обработчик так, чтобы он возвращал HTTP-ответ с нужным статусом. Например, если мы хотим вернуть 403 Forbidden, это делается следующим образом:


from fastapi.responses import JSONResponse

@app.exception_handler(ResourceNotAllowedException)
async def resource_not_allowed_handler(request, exc: ResourceNotAllowedException):
    return JSONResponse(
        status_code=403,
        content={
            "error": "Access forbidden",
            "resource": exc.resource_name,
            "message": f"You have no permission to access: {exc.resource_name}"
        },
    )

Теперь наш API возвращает HTTP код 403 вместо стандартного 200.


Продвинутая обработка: несколько кастомных исключений

Обычно в реальных приложениях требуется обрабатывать разные типы ошибок. Например, у нас есть исключение для "недостаточных прав" и исключение, если ресурс не найден.


class CustomNotFoundException(Exception):
    def __init__(self, detail: str):
        self.detail = detail

@app.exception_handler(CustomNotFoundException)
async def custom_not_found_handler(request, exc: CustomNotFoundException):
    return JSONResponse(
        status_code=404,
        content={"error": "Not found", "message": exc.detail},
    )

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    if user_id not in [1, 2, 3]:
        raise CustomNotFoundException(detail=f"User with ID {user_id} does not exist")
    return {"user_id": user_id}

Если пользователь запрашивает несуществующего пользователя, он получает понятный ответ с кодом 404.


Кастомные исключения с Pydantic

Иногда возникает необходимость более сложной обработки — например, с использованием вложенных структур данных. В таких случаях можно использовать Pydantic для описания ответа об ошибке.


from pydantic import BaseModel

class ErrorResponse(BaseModel):
    error: str
    detail: str

@app.exception_handler(CustomNotFoundException)
async def custom_not_found_handler(request, exc: CustomNotFoundException):
    response = ErrorResponse(
        error="Not found",
        detail=f"The requested resource '{exc.detail}' was not found."
    )
    return JSONResponse(
        status_code=404,
        content=response.dict(),
    )

Теперь структура ответа о ошибке строго типизирована, что особенно полезно при документировании через OpenAPI.


Интеграция глобального обработчика ошибок

Если необходимо ловить все необработанные исключения и возвращать дружелюбное сообщение вместо ошибок сервера, можно использовать глобальный обработчик.


@app.exception_handler(Exception)
async def global_exception_handler(request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"error": "Internal server error", "message": "Something went wrong. We're on it!"}
    )

Запрос к несуществующему ресурсу или ошибка в коде больше не приведет к падению приложения с HTTP 500. Вы всё ещё можете логировать или анализировать оригинальные ошибки, чтобы устранить их в будущем.


Общие ошибки и особенности

Иногда разработчики забывают о правильной регистрации обработчиков. Если обработчик не связан с кастомным исключением, он попросту не сработает. Также важно помнить, что при использовании глобального обработчика исключений (например, Exception) обработка ошибок может стать менее прозрачной, и важно сохранять оригинальные логи исключений.


Практическое применение

Кастомные обработчики исключений полезны для:

  1. Улучшения пользовательского опыта: понятные сообщения вместо загадочных ошибок.
  2. Сохранения чистоты кода: вся обработка ошибок вынесена в одно место.
  3. Дебага и мониторинга: можно отправлять данные о ошибках в лог-систему или внешние инструменты мониторинга.

Применение этих подходов важно для реальных проектов, особенно если API используется в продакшн-системах. Знание кастомных обработчиков также часто задают на технических собеседованиях, чтобы проверить, насколько вы понимаете, как работает обработка ошибок в FastAPI.


{
  "счастье": "Обработка ошибок больше не доставляет боли!"
}
1
Задача
Модуль 4: FastAPI, 20 уровень, 3 лекция
Недоступна
Создание базового кастомного обработчика исключений
Создание базового кастомного обработчика исключений
1
Задача
Модуль 4: FastAPI, 20 уровень, 3 лекция
Недоступна
Глобальный обработчик необработанных исключений
Глобальный обработчик необработанных исключений
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ