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.


{
  "щастя": "Обробка помилок більше не приносить болю!"
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ