14 KiB
Обработка ошибок
Существует множество ситуаций, когда необходимо сообщить об ошибке клиенту, использующему ваш API.
Таким клиентом может быть браузер с фронтендом, чужой код, IoT-устройство и т.д.
Возможно, вам придется сообщить клиенту о следующем:
- Клиент не имеет достаточных привилегий для выполнения данной операции.
- Клиент не имеет доступа к данному ресурсу.
- Элемент, к которому клиент пытался получить доступ, не существует.
- и т.д.
В таких случаях обычно возвращается HTTP-код статуса ответа в диапазоне 400 (от 400 до 499).
Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно.
Четырёхсотые статус-коды означают, что ошибка произошла по вине клиента.
Помните ли ошибки "404 Not Found " (и шутки) ?
Использование HTTPException
Для возврата клиенту HTTP-ответов с ошибками используется HTTPException.
Импортируйте HTTPException
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
Вызовите HTTPException в своем коде
HTTPException - это обычное исключение Python с дополнительными данными, актуальными для API.
Поскольку это исключение Python, то его не возвращают, а вызывают.
Это также означает, что если вы находитесь внутри функции, которая вызывается внутри вашей функции операции пути, и вы поднимаете HTTPException внутри этой функции, то она не будет выполнять остальной код в функции операции пути, а сразу завершит запрос и отправит HTTP-ошибку из HTTPException клиенту.
О том, насколько выгоднее вызывать исключение, чем возвращать значение, будет рассказано в разделе, посвященном зависимостям и безопасности.
В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом 404:
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
Возвращаемый ответ
Если клиент запросит 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 hl[14] *}
Установка пользовательских обработчиков исключений
Вы можете добавить пользовательские обработчики исключений с помощью тех же утилит обработки исключений из Starlette.
Допустим, у вас есть пользовательское исключение UnicornException, которое вы (или используемая вами библиотека) можете вызвать.
И вы хотите обрабатывать это исключение глобально с помощью FastAPI.
Можно добавить собственный обработчик исключений с помощью @app.exception_handler():
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
Здесь, если запросить /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 hl[2,14:19] *}
Теперь, если перейти к /items/foo, то вместо стандартной JSON-ошибки с:
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
вы получите текстовую версию:
Validation errors:
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
Переопределите обработчик ошибок HTTPException
Аналогичным образом можно переопределить обработчик HTTPException.
Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON:
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *}
/// note | Технические детали
Можно также использовать from starlette.responses import PlainTextResponse.
FastAPI предоставляет тот же starlette.responses, что и fastapi.responses, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.
///
/// warning | Внимание
Имейте в виду, что RequestValidationError содержит информацию об имени файла и строке, где произошла ошибка валидации, чтобы вы могли при желании отобразить её в логах с релевантными данными.
Но это означает, что если вы просто преобразуете её в строку и вернёте эту информацию напрямую, вы можете допустить небольшую утечку информации о своей системе, поэтому здесь код извлекает и показывает каждую ошибку отдельно.
///
Используйте тело RequestValidationError
Ошибка RequestValidationError содержит полученное тело с недопустимыми данными.
Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д.
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
Теперь попробуйте отправить недействительный элемент, например:
{
"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 принимает любые данные, пригодные для преобразования в JSON, в поле detail, тогда как HTTPException в Starlette принимает для него только строки.
Таким образом, вы можете продолжать вызывать HTTPException от FastAPI как обычно в своем коде.
Но когда вы регистрируете обработчик исключений, вы должны зарегистрировать его для HTTPException от Starlette.
Таким образом, если какая-либо часть внутреннего кодa 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 hl[2:5,15,21] *}
В этом примере вы просто выводите в терминал ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений.