Manejo de Errores¶
🌐 Traducción por IA y humanos
Esta traducción fue hecha por IA guiada por humanos. 🤝
Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖
Puedes mejorar esta traducciónayudándonos a guiar mejor al LLM de IA.
Existen muchas situaciones en las que necesitas notificar un error a un cliente que está usando tu API.
Este cliente podría ser un navegador con un frontend, un código de otra persona, un dispositivo IoT, etc.
Podrías necesitar decirle al cliente que:
- El cliente no tiene suficientes privilegios para esa operación.
- El cliente no tiene acceso a ese recurso.
- El ítem al que el cliente intentaba acceder no existe.
- etc.
En estos casos, normalmente devolverías uncódigo de estado HTTP en el rango de400 (de 400 a 499).
Esto es similar a los códigos de estado HTTP 200 (de 200 a 299). Esos códigos de estado "200" significan que de alguna manera hubo un "éxito" en el request.
Los códigos de estado en el rango de 400 significan que hubo un error por parte del cliente.
¿Recuerdas todos esos errores de"404 Not Found" (y chistes)?
UsaHTTPException¶
Para devolver responses HTTP con errores al cliente, usaHTTPException.
ImportaHTTPException¶
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]}Lanza unHTTPException en tu código¶
HTTPException es una excepción de Python normal con datos adicionales relevantes para APIs.
Debido a que es una excepción de Python, no lareturn, sino que laraise.
Esto también significa que si estás dentro de una función de utilidad que estás llamando dentro de tupath operation function, y lanzas elHTTPException desde dentro de esa función de utilidad, no se ejecutará el resto del código en lapath operation function, terminará ese request de inmediato y enviará el error HTTP delHTTPException al cliente.
El beneficio de lanzar una excepción en lugar dereturnar un valor será más evidente en la sección sobre Dependencias y Seguridad.
En este ejemplo, cuando el cliente solicita un ítem por un ID que no existe, lanza una excepción con un código de estado de404:
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]}El response resultante¶
Si el cliente solicitahttp://example.com/items/foo (unitem_id"foo"), ese cliente recibirá un código de estado HTTP de 200, y un response JSON de:
{"item":"The Foo Wrestlers"}Pero si el cliente solicitahttp://example.com/items/bar (unitem_id inexistente"bar"), ese cliente recibirá un código de estado HTTP de 404 (el error "no encontrado"), y un response JSON de:
{"detail":"Item not found"}Consejo
Cuando lanzas unHTTPException, puedes pasar cualquier valor que pueda convertirse a JSON como el parámetrodetail, no solostr.
Podrías pasar undict, unlist, etc.
Son manejados automáticamente porFastAPI y convertidos a JSON.
Agrega headers personalizados¶
Existen algunas situaciones en las que es útil poder agregar headers personalizados al error HTTP. Por ejemplo, para algunos tipos de seguridad.
Probablemente no necesitarás usarlos directamente en tu código.
Pero en caso de que los necesites para un escenario avanzado, puedes agregar headers personalizados:
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]}Instalar manejadores de excepciones personalizados¶
Puedes agregar manejadores de excepciones personalizados conlas mismas utilidades de excepciones de Starlette.
Supongamos que tienes una excepción personalizadaUnicornException que tú (o un paquete que usas) podrías lanzar.
Y quieres manejar esta excepción globalmente con FastAPI.
Podrías agregar un manejador de excepciones personalizado con@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}Aquí, si solicitas/unicorns/yolo, lapath operation lanzará unUnicornException.
Pero será manejado por elunicorn_exception_handler.
Así que recibirás un error limpio, con un código de estado HTTP de418 y un contenido JSON de:
{"message":"Oops! yolo did something. There goes a rainbow..."}Nota Técnica
También podrías usarfrom starlette.requests import Request yfrom starlette.responses import JSONResponse.
FastAPI ofrece las mismasstarlette.responses comofastapi.responses solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette. Lo mismo conRequest.
Sobrescribir los manejadores de excepciones predeterminados¶
FastAPI tiene algunos manejadores de excepciones predeterminados.
Estos manejadores se encargan de devolver los responses JSON predeterminadas cuando lanzas unHTTPException y cuando el request tiene datos inválidos.
Puedes sobrescribir estos manejadores de excepciones con los tuyos propios.
Sobrescribir excepciones de validación de request¶
Cuando un request contiene datos inválidos,FastAPI lanza internamente unRequestValidationError.
Y también incluye un manejador de excepciones predeterminado para ello.
Para sobrescribirlo, importa elRequestValidationError y úsalo con@app.exception_handler(RequestValidationError) para decorar el manejador de excepciones.
El manejador de excepciones recibirá unRequest y la excepción.
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:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,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}Ahora, si vas a/items/foo, en lugar de obtener el error JSON por defecto con:
{"detail":[{"loc":["path","item_id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}obtendrás una versión en texto, con:
Validation errors:Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integerSobrescribir el manejador de errores deHTTPException¶
De la misma manera, puedes sobrescribir el manejador deHTTPException.
Por ejemplo, podrías querer devolver un response de texto plano en lugar de JSON para estos errores:
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:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,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}Nota Técnica
También podrías usarfrom starlette.responses import PlainTextResponse.
FastAPI ofrece las mismasstarlette.responses comofastapi.responses solo como una conveniencia para ti, el desarrollador. Pero la mayoría de los responses disponibles vienen directamente de Starlette.
Advertencia
Ten en cuenta queRequestValidationError contiene la información del nombre de archivo y la línea donde ocurre el error de validación, para que puedas mostrarla en tus logs con la información relevante si quieres.
Pero eso significa que si simplemente lo conviertes a un string y devuelves esa información directamente, podrías estar filtrando un poquito de información sobre tu sistema, por eso aquí el código extrae y muestra cada error de forma independiente.
Usar el body deRequestValidationError¶
ElRequestValidationError contiene elbody que recibió con datos inválidos.
Podrías usarlo mientras desarrollas tu aplicación para registrar el body y depurarlo, devolverlo al usuario, etc.
fromfastapiimportFastAPI,Requestfromfastapi.encodersimportjsonable_encoderfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportJSONResponsefrompydanticimportBaseModelapp=FastAPI()@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request:Request,exc:RequestValidationError):returnJSONResponse(status_code=422,content=jsonable_encoder({"detail":exc.errors(),"body":exc.body}),)classItem(BaseModel):title:strsize:int@app.post("/items/")asyncdefcreate_item(item:Item):returnitemAhora intenta enviar un ítem inválido como:
{"title":"towel","size":"XL"}Recibirás un response que te dirá que los datos son inválidos conteniendo el body recibido:
{"detail":[{"loc":["body","size"],"msg":"value is not a valid integer","type":"type_error.integer"}],"body":{"title":"towel","size":"XL"}}HTTPException de FastAPI vsHTTPException de Starlette¶
FastAPI tiene su propioHTTPException.
Y la clase de errorHTTPException deFastAPI hereda de la clase de errorHTTPException de Starlette.
La única diferencia es que elHTTPException deFastAPI acepta cualquier dato JSON-able para el campodetail, mientras que elHTTPException de Starlette solo acepta strings para ello.
Así que puedes seguir lanzando unHTTPException deFastAPI como de costumbre en tu código.
Pero cuando registras un manejador de excepciones, deberías registrarlo para elHTTPException de Starlette.
De esta manera, si alguna parte del código interno de Starlette, o una extensión o plug-in de Starlette, lanza unHTTPException de Starlette, tu manejador podrá capturarlo y manejarlo.
En este ejemplo, para poder tener ambosHTTPException en el mismo código, las excepciones de Starlette son renombradas aStarletteHTTPException:
fromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionReutilizar los manejadores de excepciones deFastAPI¶
Si quieres usar la excepción junto con los mismos manejadores de excepciones predeterminados deFastAPI, puedes importar y reutilizar los manejadores de excepciones predeterminados defastapi.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}En este ejemplo solo estásprinteando el error con un mensaje muy expresivo, pero te haces una idea. Puedes usar la excepción y luego simplemente reutilizar los manejadores de excepciones predeterminados.







