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 (від 200 до 299). Ці "200" статус-коди означають, що запит пройшов успішно.

Статус-коди в діапазоні 400 означають, що сталася помилка з боку клієнта.

Пам'ятаєте всі ці помилки 404 Not Found (і жарти про них)?

Використання HTTPException

Щоб повернути HTTP-відповіді з помилками клієнту, використовуйте HTTPException.

Імпорт HTTPException

{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}

Використання HTTPException у коді

HTTPException — це звичайна помилка Python із додатковими даними, які стосуються API.

Оскільки це помилка Python, Ви не повертаєте його, а генеруєте (генеруєте помилку).

Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї функції операції шляху, і там генеруєте HTTPException, всередині цієї допоміжної функції, то решта коду в функції операції шляху не буде виконана. Запит одразу завершиться, і HTTP-помилка з HTTPException буде надіслана клієнту.

Перевага використання генерації (raise) помилки замість повернення значення (return) стане більш очевидним в розділі про Залежності та Безпеку.

У цьому прикладі, якщо клієнт запитує елемент за 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 (помилка "не знайдено") та відповідь:

{
  "detail": "Item not found"
}

/// tip | Порада

Під час виклику HTTPException Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр detail, а не лише рядок (str).

Ви можете передати dict, list тощо.

Вони обробляються автоматично за допомогою FastAPI та перетворюються в JSON.

///

Додавання власних заголовків

Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки.

Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді.

Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки:

{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}

Встановлення власних обробників помилок

Ви можете додати власні обробники помилок за допомогою тих самих утиліт обробки помилок зі Starlette.

Припустимо, у Вас є власний обʼєкт помилки UnicornException, яке Ви (або бібліотека, яку Ви використовуєте) може згенерувати (raise).

І Ви хочете обробляти це виключення глобально за допомогою 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-відповідей, коли Ви генеруєте (raise) HTTPException, а також коли запит містить некоректні дані.

Ви можете перевизначити ці обробники, створивши власні.

Перевизначення помилок валідації запиту

Коли запит містить некоректні дані, FastAPI генерує RequestValidationError.

І також включає обробник помилок за замовчуванням для нього.

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

Обробник помилок отримує Request і саму помилку.

{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}

Тепер, якщо Ви перейдете за посиланням /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.

Так має бути, якщо у Вас виникла ValidationError Pydantic у відповіді або деінде у вашому коді (не у запиті клієнта), це насправді є помилкою у Вашому коді.

І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.

Перевизначення обробника помилок HTTPException

Аналогічно, Ви можете перевизначити обробник HTTPException.

Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:

{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}

/// note | Технічні деталі

Ви також можете використовувати from starlette.responses import PlainTextResponse.

FastAPI надає ті самі starlette.responses, що й fastapi.responses, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.

///

Використання тіла RequestValidationError

RequestValidationError містить body, який він отримав із некоректними даними.

Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо.

{* ../../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 проти HTTPException Starlette

FastAPI має власний HTTPException.

І клас помилки HTTPException в FastAPI успадковується від класу помилки HTTPException в Starlette.

Єдина різниця полягає в тому, що HTTPException в FastAPI приймає будь-які дані, які можна перетворити на JSON, для поля detail, тоді як HTTPException у Starlette приймає тільки рядки.

Отже, Ви можете продовжувати використовувати HTTPException в FastAPI як зазвичай у своєму коді.

Але коли Ви реєструєте обробник виключень, слід реєструвати його для HTTPException зі Starlette.

Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує (raise) 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] *}

У цьому прикладі Ви просто використовуєте print для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням.