You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14 KiB

Обработка ошибок

Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.

Таким клиентом может быть браузер с фронтендом, чужой код, IoT-устройство и т.д.

Возможно, вам придется сообщить клиенту о следующем:

  • Клиент не имеет достаточных привилегий для выполнения данной операции.
  • Клиент не имеет доступа к данному ресурсу.
  • Элемент, к которому клиент пытался получить доступ, не существует.
  • и т.д.

В таких случаях обычно возвращается HTTP-код статуса ответа в диапазоне 400 (от 400 до 499).

Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно.

Четырёхсотые статус-коды означают, что ошибка произошла по вине клиента.

Помните ли ошибки "404 Not Found " (и шутки) ?

Использование HTTPException

Для возврата клиенту HTTP-ответов с ошибками используется HTTPException.

Импортируйте HTTPException

{!../../../docs_src/handling_errors/tutorial001.py!}

Вызовите HTTPException в своем коде

HTTPException - это обычное исключение Python с дополнительными данными, актуальными для API.

Поскольку это исключение Python, то его не возвращают, а вызывают.

Это также означает, что если вы находитесь внутри функции, которая вызывается внутри вашей функции операции пути, и вы поднимаете HTTPException внутри этой функции, то она не будет выполнять остальной код в функции операции пути, а сразу завершит запрос и отправит HTTP-ошибку из HTTPException клиенту.

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

В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом 404:

{!../../../docs_src/handling_errors/tutorial001.py!}

Возвращаемый ответ

Если клиент запросит http://example.com/items/foo (item_id "foo"), то он получит статус-код 200 и ответ в формате JSON:

{
  "item": "The Foo Wrestlers"
}

Но если клиент запросит http://example.com/items/bar (несуществующий item_id "bar"), то он получит статус-код 404 (ошибка "не найдено") и JSON-ответ в виде:

{
  "detail": "Item not found"
}

!!! tip "Подсказка" При вызове HTTPException в качестве параметра detail можно передавать любое значение, которое может быть преобразовано в JSON, а не только str.

Вы можете передать `dict`, `list` и т.д.

Они автоматически обрабатываются **FastAPI** и преобразуются в JSON.

Добавление пользовательских заголовков

В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности.

Скорее всего, вам не потребуется использовать его непосредственно в коде.

Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки:

{!../../../docs_src/handling_errors/tutorial002.py!}

Установка пользовательских обработчиков исключений

Вы можете добавить пользовательские обработчики исключений с помощью то же самое исключение - утилиты от Starlette.

Допустим, у вас есть пользовательское исключение UnicornException, которое вы (или используемая вами библиотека) можете вызвать.

И вы хотите обрабатывать это исключение глобально с помощью FastAPI.

Можно добавить собственный обработчик исключений с помощью @app.exception_handler():

{!../../../docs_src/handling_errors/tutorial003.py!}

Здесь, если запросить /unicorns/yolo, то операция пути вызовет UnicornException.

Но оно будет обработано unicorn_exception_handler.

Таким образом, вы получите чистую ошибку с кодом состояния HTTP 418 и содержимым JSON:

{"message": "Oops! yolo did something. There goes a rainbow..."}

!!! note "Технические детали" Также можно использовать from starlette.requests import Request и from starlette.responses import JSONResponse.

**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette. То же самое касается и `Request`.

Переопределение стандартных обработчиков исключений

FastAPI имеет некоторые обработчики исключений по умолчанию.

Эти обработчики отвечают за возврат стандартных JSON-ответов при вызове HTTPException и при наличии в запросе недопустимых данных.

Вы можете переопределить эти обработчики исключений на свои собственные.

Переопределение исключений проверки запроса

Когда запрос содержит недопустимые данные, FastAPI внутренне вызывает ошибку RequestValidationError.

А также включает в себя обработчик исключений по умолчанию.

Чтобы переопределить его, импортируйте RequestValidationError и используйте его с @app.exception_handler(RequestValidationError) для создания обработчика исключений.

Обработчик исключения получит объект Request и исключение.

{!../../../docs_src/handling_errors/tutorial004.py!}

Теперь, если перейти к /items/foo, то вместо стандартной JSON-ошибки с:

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

вы получите текстовую версию:

1 validation error
path -> item_id
  value is not a valid integer (type=type_error.integer)

RequestValidationError или ValidationError

!!! warning "Внимание" Это технические детали, которые можно пропустить, если они не важны для вас сейчас.

RequestValidationError является подклассом Pydantic ValidationError.

FastAPI использует его для того, чтобы, если вы используете Pydantic-модель в response_model, и ваши данные содержат ошибку, вы увидели ошибку в журнале.

Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP 500.

Так и должно быть, потому что если в вашем ответе или где-либо в вашем коде (не в запросе клиента) возникает Pydantic ValidationError, то это действительно ошибка в вашем коде.

И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности.

Переопределите обработчик ошибок HTTPException

Аналогичным образом можно переопределить обработчик HTTPException.

Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:

{!../../../docs_src/handling_errors/tutorial004.py!}

!!! note "Технические детали" Можно также использовать from starlette.responses import PlainTextResponse.

**FastAPI** предоставляет тот же `starlette.responses`, что и `fastapi.responses`, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.

Используйте тело RequestValidationError

Ошибка RequestValidationError содержит полученное тело с недопустимыми данными.

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

{!../../../docs_src/handling_errors/tutorial005.py!}

Теперь попробуйте отправить недействительный элемент, например:

{
  "title": "towel",
  "size": "XL"
}

Вы получите ответ о том, что данные недействительны, содержащий следующее тело:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}

HTTPException в FastAPI или в Starlette

FastAPI имеет собственный HTTPException.

Класс ошибок FastAPI HTTPException наследует от класса ошибок Starlette HTTPException.

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

Он необходим/используется внутри системы для OAuth 2.0 и некоторых утилит безопасности.

Таким образом, вы можете продолжать вызывать HTTPException от FastAPI как обычно в своем коде.

Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для HTTPException от Starlette.

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

В данном примере, чтобы иметь возможность использовать оба HTTPException в одном коде, исключения Starlette переименованы в StarletteHTTPException:

from starlette.exceptions import HTTPException as StarletteHTTPException

Переиспользование обработчиков исключений FastAPI

Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из FastAPI, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из fastapi.exception_handlers:

{!../../../docs_src/handling_errors/tutorial006.py!}

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