Es gibt viele Situationen, in denen Sie einem Client, der Ihre API nutzt, einen Fehler mitteilen müssen.
Dieser Client könnte ein Browser mit einem Frontend sein, ein Code von jemand anderem, ein IoT-Gerät usw.
Sie könnten dem Client mitteilen müssen, dass:
Der Client nicht genügend Berechtigungen für diese Operation hat.
Der Client keinen Zugriff auf diese Ressource hat.
Die Ressource, auf die der Client versucht hat, zuzugreifen, nicht existiert.
usw.
In diesen Fällen würden Sie normalerweise einen HTTP-Statuscode im Bereich 400 (von 400 bis 499) zurückgeben.
Dies ist vergleichbar mit den HTTP-Statuscodes im Bereich 200 (von 200 bis 299). Diese „200“-Statuscodes bedeuten, dass der Request in irgendeiner Weise erfolgreich war.
Die Statuscodes im Bereich 400 bedeuten hingegen, dass es einen Fehler seitens des Clients gab.
Erinnern Sie sich an all diese „404 Not Found“ Fehler (und Witze)?
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 ist eine normale Python-Exception mit zusätzlichen Daten, die für APIs relevant sind.
Weil es eine Python-Exception ist, geben Sie sie nicht zurück (return), sondern lösen sie aus (raise).
Das bedeutet auch, wenn Sie sich innerhalb einer Hilfsfunktion befinden, die Sie innerhalb Ihrer Pfadoperation-Funktion aufrufen, und Sie die HTTPException aus dieser Hilfsfunktion heraus auslösen, wird der restliche Code in der Pfadoperation-Funktion nicht ausgeführt. Der Request wird sofort abgebrochen und der HTTP-Error der HTTPException wird an den Client gesendet.
Der Vorteil des Auslösens einer Exception gegenüber dem Zurückgeben eines Wertes wird im Abschnitt über Abhängigkeiten und Sicherheit deutlicher werden.
In diesem Beispiel lösen wir eine Exception mit einem Statuscode von 404 aus, wenn der Client einen Artikel mit einer nicht existierenden ID anfordert:
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]}
Wenn der Client http://example.com/items/foo anfordert (ein item_id"foo"), erhält dieser Client einen HTTP-Statuscode 200 und diese JSON-Response:
{"item":"The Foo Wrestlers"}
Aber wenn der Client http://example.com/items/bar anfordert (ein nicht-existierendes item_id"bar"), erhält er einen HTTP-Statuscode 404 (der „Not Found“-Error) und eine JSON-Response wie:
{"detail":"Item not found"}
Tipp
Wenn Sie eine HTTPException auslösen, können Sie dem Parameter detail jeden Wert übergeben, der in JSON konvertiert werden kann, nicht nur str.
Sie könnten ein dict, eine list, usw. übergeben.
Diese werden von FastAPI automatisch gehandhabt und in JSON konvertiert.
Es gibt Situationen, in denen es nützlich ist, dem HTTP-Error benutzerdefinierte Header hinzuzufügen. Zum Beispiel in einigen Sicherheitsszenarien.
Sie werden es wahrscheinlich nicht direkt in Ihrem Code verwenden müssen.
Aber falls Sie es für ein fortgeschrittenes Szenario benötigen, können Sie benutzerdefinierte Header hinzufügen:
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]}
Angenommen, Sie haben eine benutzerdefinierte Exception UnicornException, die Sie (oder eine Bibliothek, die Sie verwenden) raisen könnten.
Und Sie möchten diese Exception global mit FastAPI handhaben.
Sie könnten einen benutzerdefinierten Exceptionhandler mit @app.exception_handler() hinzufügen:
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}
Hier, wenn Sie /unicorns/yolo anfordern, wird die Pfadoperation eine UnicornExceptionraisen.
Aber diese wird von unicorn_exception_handler gehandhabt.
Sie erhalten also einen sauberen Fehler mit einem HTTP-Statuscode von 418 und dem JSON-Inhalt:
{"message":"Oops! yolo did something. There goes a rainbow..."}
Technische Details
Sie könnten auch from starlette.requests import Request und from starlette.responses import JSONResponse verwenden.
FastAPI bietet dieselben starlette.responses auch via fastapi.responses an, nur als Annehmlichkeit für Sie, den Entwickler. Aber die meisten verfügbaren Responses kommen direkt von Starlette. Dasselbe gilt für Request.
Diese Handler sind dafür verantwortlich, die Default-JSON-Responses zurückzugeben, wenn Sie eine HTTPExceptionraisen und wenn der Request ungültige Daten enthält.
Sie können diese Exceptionhandler mit Ihren eigenen überschreiben.
Überschreiben von Request-Validierungs-Exceptions¶
Wenn ein Request ungültige Daten enthält, löst FastAPI intern einen RequestValidationError aus.
Und es enthält auch einen Default-Exceptionhandler für diesen.
Um diesen zu überschreiben, importieren Sie den RequestValidationError und verwenden Sie ihn mit @app.exception_handler(RequestValidationError), um den Exceptionhandler zu dekorieren.
Der Exceptionhandler erhält einen Request und die Exception.
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}
Wenn Sie nun zu /items/foo gehen, erhalten Sie anstelle des standardmäßigen JSON-Fehlers mit:
{"detail":[{"loc":["path","item_id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
eine Textversion mit:
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
Dies sind technische Details, die Sie überspringen können, wenn sie für Sie jetzt nicht wichtig sind.
RequestValidationError ist eine Unterklasse von Pydantics ValidationError.
FastAPI verwendet diesen so, dass, wenn Sie ein Pydantic-Modell in response_model verwenden und Ihre Daten einen Fehler haben, Sie den Fehler in Ihrem Log sehen.
Aber der Client/Benutzer wird ihn nicht sehen. Stattdessen erhält der Client einen „Internal Server Error“ mit einem HTTP-Statuscode 500.
Es sollte so sein, denn wenn Sie einen Pydantic ValidationError in Ihrer Response oder irgendwo anders in Ihrem Code haben (nicht im Request des Clients), ist es tatsächlich ein Fehler in Ihrem Code.
Und während Sie den Fehler beheben, sollten Ihre Clients/Benutzer keinen Zugriff auf interne Informationen über den Fehler haben, da das eine Sicherheitslücke aufdecken könnte.
Auf die gleiche Weise können Sie den HTTPException-Handler überschreiben.
Zum Beispiel könnten Sie eine Klartext-Response statt JSON für diese Fehler zurückgeben wollen:
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}
Technische Details
Sie könnten auch from starlette.responses import PlainTextResponse verwenden.
FastAPI bietet dieselben starlette.responses auch via fastapi.responses an, nur als Annehmlichkeit für Sie, den Entwickler. Aber die meisten verfügbaren Responses kommen direkt von Starlette.
Versuchen Sie nun, einen ungültigen Artikel zu senden:
{"title":"towel","size":"XL"}
Sie erhalten eine Response, die Ihnen sagt, dass die Daten ungültig sind und die den empfangenen Body enthält:
{"detail":[{"loc":["body","size"],"msg":"value is not a valid integer","type":"type_error.integer"}],"body":{"title":"towel","size":"XL"}}
FastAPIs HTTPException vs. Starlettes HTTPException¶
FastAPI hat seine eigene HTTPException.
Und die HTTPException-Fehlerklasse von FastAPI erbt von der HTTPException-Fehlerklasse von Starlette.
Der einzige Unterschied besteht darin, dass die HTTPException von FastAPI beliebige JSON-konvertierbare Daten für das detail-Feld akzeptiert, während die HTTPException von Starlette nur Strings dafür akzeptiert.
Sie können also weiterhin die HTTPException von FastAPI wie üblich in Ihrem Code auslösen.
Aber wenn Sie einen Exceptionhandler registrieren, sollten Sie ihn für die HTTPException von Starlette registrieren.
Auf diese Weise, wenn irgendein Teil des internen Codes von Starlette, oder eine Starlette-Erweiterung oder ein Plug-in, eine Starlette HTTPException auslöst, wird Ihr Handler in der Lage sein, diese abzufangen und zu handhaben.
Um in diesem Beispiel beide HTTPExceptions im selben Code zu haben, wird die Exception von Starlette zu StarletteHTTPException umbenannt:
Wenn Sie die Exception zusammen mit den gleichen Default-Exceptionhandlern von FastAPI verwenden möchten, können Sie die Default-Exceptionhandler aus fastapi.exception_handlers importieren und wiederverwenden:
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}
In diesem Beispiel geben Sie nur den Fehler mit einer sehr ausdrucksstarken Nachricht aus, aber Sie verstehen das Prinzip. Sie können die Exception verwenden und dann einfach die Default-Exceptionhandler wiederverwenden.