Skip to content

Глава 3.6: Обработка ошибок и валидация

Время изучения: 50 минут


1. Обработка ошибок: "Аварийные щиты" космического корабля

Даже на самом совершенном корабле могут случиться непредвиденные ситуации:

  • Неверная команда от ЦУП: Клиент отправил некорректные данные.
  • Потеря связи с модулем: Ресурс не найден в базе данных.
  • Сбой в реакторе: Внутренняя ошибка сервера.

Правильная обработка ошибок — это система "аварийных щитов". Она не дает кораблю развалиться, а вместо этого отправляет в ЦУП четкий сигнал о том, что пошло не так.

💡 Космическая аналогия:

Вместо того чтобы просто передать в ЦУП сигнал "АВАРИЯ!", хороший бортовой компьютер отправит структурированный отчет:

{
  "error_code": "ENGINE_OVERHEAT",
  "message": "Температура двигателя №2 превысила норму",
  "suggested_action": "Запустить систему охлаждения"
}
Это позволяет инженерам на Земле быстро понять проблему и принять меры.


2. Валидация Pydantic: Встроенный "бортовой компьютер"

Мы уже столкнулись с магией Pydantic. Если вы попытаетесь создать корабль с неверным типом данных (например, launch_year в виде строки), FastAPI автоматически вернет ошибку 422 Unprocessable Entity с подробным описанием, какое поле и почему не прошло проверку.

Пример запроса к POST /spaceships:

{
  "name": "X-Wing",
  "type": "Истребитель",
  "launch_year": "давно",  // <-- Неверный тип!
  "status": "В строю"
}

Автоматический ответ FastAPI:

{
  "detail": [
    {
      "loc": [
        "body",
        "launch_year"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}
Это невероятно мощно! Вам не нужно писать код для проверки типов — FastAPI и Pydantic делают это за вас.


3. Обработка "Ресурс не найден": Исключение HTTPException

Мы уже использовали это в CRUD-операциях. HTTPException — это стандартный способ FastAPI прервать выполнение запроса и немедленно вернуть клиенту ответ с ошибкой.

Вспомним код из GET /spaceships/{ship_id}:

# main.py
from fastapi import FastAPI, HTTPException # Убедитесь, что HTTPException импортирован

# ...

@app.get("/spaceships/{ship_id}", response_model=Spaceship, tags=["Космические аппараты"])
def get_spaceship(ship_id: int):
    ship = db_spaceships.get(ship_id)
    if not ship:
        # Если корабль не найден, "выбрасываем" исключение 404
        raise HTTPException(status_code=404, detail=f"Космический аппарат с ID {ship_id} не найден")
    return ship

  • raise HTTPException(...): Этот вызов останавливает выполнение функции.
  • status_code=404: Устанавливает HTTP-статус ответа.
  • detail: Сообщение, которое будет отправлено клиенту в теле JSON-ответа.

4. Кастомные валидаторы: "Особые проверки" перед запуском

Что, если мы хотим добавить свою, более сложную бизнес-логику? Например, запретить запускать корабли с названием "Death Star".

Для этого в Pydantic есть мощный инструмент — валидаторы.

Шаг 1: Добавляем валидатор в модель SpaceshipCreate

# main.py
from pydantic import BaseModel, Field, validator

class SpaceshipCreate(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    type: str
    launch_year: int = Field(..., gt=1950)
    status: str

    @validator('name')
    def name_must_not_be_forbidden(cls, v):
        """Проверяет, что имя корабля не входит в список запрещенных."""
        if 'Death Star' in v:
            raise ValueError('Названия вроде "Death Star" запрещены Имперским указом!')
        return v.title() # Заодно приводим имя к заглавным буквам

  • @validator('name'): Декоратор, который "привязывает" эту функцию к полю name.
  • cls, v: Метод получает сам класс (cls) и значение поля (v).
  • raise ValueError(...): Если проверка не пройдена, мы вызываем стандартное исключение Python. FastAPI перехватит его и превратит в красивую ошибку 422.
  • return v.title(): Если все хорошо, мы обязательно должны вернуть значение. Мы можем даже изменить его на лету (например, привести к стандартному виду).

Шаг 2: Тестируем Перезапустите uvicorn и попробуйте создать корабль с запрещенным именем через /docs. Вы получите ошибку 422 с вашим кастомным сообщением!


5. Глобальная обработка ошибок: "Аварийный протокол" станции

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

Для этого используется декоратор @app.exception_handler.

Пример: Перехват всех ошибок ValueError

# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

# ...

@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
    """
    Глобальный обработчик для всех ошибок ValueError,
    чтобы возвращать стандартизированный JSON.
    """
    return JSONResponse(
        status_code=400,
        content={"message": f"Ошибка в данных: {str(exc)}"},
    )

  • @app.exception_handler(ValueError): Говорит FastAPI, что эта функция должна обрабатывать все ValueError, которые не были перехвачены ранее.
  • async def ...: Обработчики исключений должны быть асинхронными (async).
  • JSONResponse: Позволяет полностью контролировать тело и статус ответа.

Теперь, когда сработает наш кастомный валидатор, ответ будет иметь более дружелюбный формат, который мы определили.


Квиз для закрепления

1. Если клиент отправляет данные неверного типа (строку вместо числа), FastAPI автоматически вернет статус...

2. `raise HTTPException(status_code=404)` используется, чтобы...

3. Декоратор `@validator('field_name')` в Pydantic нужен для:

4. Что должна делать функция-валидатор в Pydantic, если данные корректны?

5. `@app.exception_handler()` позволяет...


🚀 Итог главы:

Вы установили на свой API-корабль мощную систему защиты и аварийные протоколы. Теперь он умеет:

  • 🛡️ Автоматически отражать атаки "некорректных данных" с помощью Pydantic.
  • 🚨 Грамотно сообщать об отсутствии ресурсов (404 Not Found) через HTTPException.
  • ⚙️ Проводить "особые проверки" с помощью кастомных валидаторов.
  • 🧯 Глобально перехватывать непредвиденные сбои и давать стандартизированные ответы.

Ваш "гипердвигатель" не только быстр, но и невероятно надежен!

📌 Проверка:

  • Попробуйте создать корабль с названием "Death Star" и убедитесь, что получаете ошибку 400 с вашим кастомным сообщением.
  • Попробуйте запросить GET /spaceships/999 и убедитесь, что получаете ошибку 404.
  • Попробуйте отправить POST-запрос с launch_year в виде строки и убедитесь, что получаете ошибку 422.

⚠️ Если ошибки:

  • Убедитесь, что все необходимые модули (HTTPException, validator, Request, JSONResponse) импортированы.
  • Проверьте, что декораторы @validator и @app.exception_handler написаны без опечаток.

Поздравляем с завершением Главы 3! Вы с нуля построили и запустили мощное, документированное и защищенное API на FastAPI. Вы готовы к выполнению настоящих космических миссий