fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}
HTTPException — це звичайна помилка Python із додатковими даними, які стосуються API.
Оскільки це помилка Python, Ви не повертаєте його, а генеруєте (генеруєте помилку).
Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї функції операції шляху, і там генеруєте HTTPException, всередині цієї допоміжної функції, то решта коду в функції операції шляху не буде виконана. Запит одразу завершиться, і HTTP-помилка з HTTPException буде надіслана клієнту.
Перевага використання генерації (raise) помилки замість повернення значення (return) стане більш очевидним в розділі про Залежності та Безпеку.
У цьому прикладі, якщо клієнт запитує елемент за ID, якого не існує, буде згенеровано помилку зі статус-кодом 404:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}
Якщо клієнт робить запит за шляхом 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"}
Порада
Під час виклику HTTPException Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр detail, а не лише рядок (str).
Ви можете передати dict, list тощо.
Вони обробляються автоматично за допомогою FastAPI та перетворюються в JSON.
Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки.
Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді.
Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items-header/{item_id}")asyncdefread_item_header(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found",headers={"X-Error":"There goes my error"},)return{"item":items[item_id]}
Припустимо, у Вас є власний обʼєкт помилки UnicornException, яке Ви (або бібліотека, яку Ви використовуєте) може згенерувати (raise).
І Ви хочете обробляти це виключення глобально за допомогою FastAPI.
Ви можете додати власний обробник виключень за допомогою @app.exception_handler():
fromfastapiimportFastAPI,Requestfromfastapi.responsesimportJSONResponseclassUnicornException(Exception):def__init__(self,name:str):self.name=nameapp=FastAPI()@app.exception_handler(UnicornException)asyncdefunicorn_exception_handler(request:Request,exc:UnicornException):returnJSONResponse(status_code=418,content={"message":f"Oops! {exc.name} did something. There goes a rainbow..."},)@app.get("/unicorns/{name}")asyncdefread_unicorn(name:str):ifname=="yolo":raiseUnicornException(name=name)return{"unicorn_name":name}
Тут, якщо Ви звернетеся до /unicorns/yolo, то згенерується помилка UnicornException.
Але вона буде оброблена функцією-обробником unicorn_exception_handler.
Отже, Ви отримаєте зрозумілу помилку зі HTTP-статусом 418 і JSON-відповіддю:
{"message":"Oops! yolo did something. There goes a rainbow..."}
Технічні деталі
Ви також можете використовувати 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 і саму помилку.
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):returnPlainTextResponse(str(exc),status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
Тепер, якщо Ви перейдете за посиланням /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 є підкласом Pydantic ValidationError.
FastAPI використовує його для того, якщо Ви використовуєте модель Pydantic у response_model і у ваших даних є помилка, Ви побачили помилку у своєму журналі.
Але клієнт/користувач не побачить її. Натомість клієнт отримає "Internal Server Error" зі статусом HTTP 500.
Так має бути, якщо у Вас виникла ValidationError Pydantic у відповіді або деінде у вашому коді (не у запиті клієнта), це насправді є помилкою у Вашому коді.
І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.
Аналогічно, Ви можете перевизначити обробник HTTPException.
Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):returnPlainTextResponse(str(exc),status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
Технічні деталі
Ви також можете використовувати from starlette.responses import PlainTextResponse.
FastAPI надає ті самі starlette.responses, що й fastapi.responses, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
Тепер спробуйте надіслати некоректний елемент, наприклад:
{"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:
Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у FastAPI, Ви можете імпортувати та повторно використовувати їх із fastapi.exception_handlers:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exception_handlersimport(http_exception_handler,request_validation_exception_handler,)fromfastapi.exceptionsimportRequestValidationErrorfromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefcustom_http_exception_handler(request,exc):print(f"OMG! An HTTP error!: {repr(exc)}")returnawaithttp_exception_handler(request,exc)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):print(f"OMG! The client sent invalid data!: {exc}")returnawaitrequest_validation_exception_handler(request,exc)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}
У цьому прикладі Ви просто використовуєте print для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням.