committed by
GitHub
1 changed files with 255 additions and 0 deletions
@ -0,0 +1,255 @@ |
|||||
|
# Обробка Помилок |
||||
|
|
||||
|
Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш 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 відповідь: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"item": "The Foo Wrestlers" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає статус-код 404 (помилка "не знайдено") та відповідь: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"detail": "Item not found" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
/// tip | Порада |
||||
|
|
||||
|
Під час виклику `HTTPException` Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише рядок (`str`). |
||||
|
|
||||
|
Ви можете передати `dict`, `list` тощо. |
||||
|
|
||||
|
Вони обробляються автоматично за допомогою **FastAPI** та перетворюються в JSON. |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
## Додавання власних заголовків |
||||
|
|
||||
|
Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки. |
||||
|
|
||||
|
Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді. |
||||
|
|
||||
|
Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки: |
||||
|
|
||||
|
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} |
||||
|
|
||||
|
## Встановлення власних обробників помилок |
||||
|
|
||||
|
Ви можете додати власні обробники помилок за допомогою <a href="https://www.starlette.io/exceptions/" class="external-link" target="_blank">тих самих утиліт обробки помилок зі Starlette</a>. |
||||
|
|
||||
|
Припустимо, у Вас є власний обʼєкт помилки `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-відповіддю: |
||||
|
|
||||
|
```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-помилку: |
||||
|
|
||||
|
```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 <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>. |
||||
|
|
||||
|
**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] *} |
||||
|
|
||||
|
Тепер спробуйте надіслати некоректний елемент, наприклад: |
||||
|
|
||||
|
```JSON |
||||
|
{ |
||||
|
"title": "towel", |
||||
|
"size": "XL" |
||||
|
} |
||||
|
``` |
||||
|
Ви отримаєте відповідь, яка повідомить Вам, які саме дані є некоректні у вашому тілі запиту: |
||||
|
|
||||
|
|
||||
|
```JSON hl_lines="12-15" |
||||
|
{ |
||||
|
"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`: |
||||
|
|
||||
|
```Python |
||||
|
from starlette.exceptions import HTTPException as StarletteHTTPException |
||||
|
``` |
||||
|
|
||||
|
### Повторне використання обробників помилок **FastAPI** |
||||
|
|
||||
|
Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у **FastAPI**, Ви можете імпортувати та повторно використовувати їх із `fastapi.exception_handlers`: |
||||
|
|
||||
|
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} |
||||
|
|
||||
|
У цьому прикладі Ви просто використовуєте `print` для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням. |
Loading…
Reference in new issue