Si usas un bloque try en una dependencia con yield, recibirás cualquier excepción que haya sido lanzada al usar la dependencia.
Por ejemplo, si algún código en algún punto intermedio, en otra dependencia o en una path operation, hizo un "rollback" de una transacción de base de datos o creó cualquier otra excepción, recibirías la excepción en tu dependencia.
Entonces, puedes buscar esa excepción específica dentro de la dependencia con except SomeException.
De la misma manera, puedes usar finally para asegurarte de que los pasos de salida se ejecuten, sin importar si hubo una excepción o no.
Viste que puedes usar dependencias con yield y tener bloques try que intentan ejecutar algo de código y luego ejecutan algo de exit code después de finally.
También puedes usar except para capturar la excepción que fue lanzada y hacer algo con ella.
Por ejemplo, puedes lanzar una excepción diferente, como HTTPException.
Consejo
Esta es una técnica algo avanzada, y en la mayoría de los casos no la necesitarás realmente, ya que puedes lanzar excepciones (incluyendo HTTPException) desde dentro del resto del código de tu aplicación, por ejemplo, en la path operation function.
Pero está ahí por si lo necesitas. 🤓
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()data={"plumbus":{"description":"Freshly pickled plumbus","owner":"Morty"},"portal-gun":{"description":"Gun to create portals","owner":"Rick"},}classOwnerError(Exception):passdefget_username():try:yield"Rick"exceptOwnerErrorase:raiseHTTPException(status_code=400,detail=f"Owner error: {e}")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_idnotindata:raiseHTTPException(status_code=404,detail="Item not found")item=data[item_id]ifitem["owner"]!=username:raiseOwnerError(username)returnitem
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()data={"plumbus":{"description":"Freshly pickled plumbus","owner":"Morty"},"portal-gun":{"description":"Gun to create portals","owner":"Rick"},}classOwnerError(Exception):passdefget_username():try:yield"Rick"exceptOwnerErrorase:raiseHTTPException(status_code=400,detail=f"Owner error: {e}")@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_idnotindata:raiseHTTPException(status_code=404,detail="Item not found")item=data[item_id]ifitem["owner"]!=username:raiseOwnerError(username)returnitem
Si quieres capturar excepciones y crear una respuesta personalizada basada en eso, crea un Custom Exception Handler.
Si capturas una excepción usando except en una dependencia con yield y no la lanzas de nuevo (o lanzas una nueva excepción), FastAPI no podrá notar que hubo una excepción, de la misma manera que pasaría con Python regular:
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("Oops, we didn't raise again, Britney 😱")@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("Oops, we didn't raise again, Britney 😱")@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
En este caso, el cliente verá una respuesta HTTP 500 Internal Server Error como debería, dado que no estamos lanzando un HTTPException o similar, pero el servidor no tendrá ningún log ni ninguna otra indicación de cuál fue el error. 😱
Si capturas una excepción en una dependencia con yield, a menos que estés lanzando otro HTTPException o similar, deberías volver a lanzar la excepción original.
Puedes volver a lanzar la misma excepción usando raise:
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:Annotated[str,Depends(get_username)]):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
fromfastapiimportDepends,FastAPI,HTTPExceptionapp=FastAPI()classInternalError(Exception):passdefget_username():try:yield"Rick"exceptInternalError:print("We don't swallow the internal error here, we raise again 😎")raise@app.get("/items/{item_id}")defget_item(item_id:str,username:str=Depends(get_username)):ifitem_id=="portal-gun":raiseInternalError(f"The portal gun is too dangerous to be owned by {username}")ifitem_id!="plumbus":raiseHTTPException(status_code=404,detail="Item not found, there's only a plumbus here")returnitem_id
Ahora el cliente obtendrá la misma respuesta HTTP 500 Internal Server Error, pero el servidor tendrá nuestro InternalError personalizado en los logs. 😎
La secuencia de ejecución es más o menos como este diagrama. El tiempo fluye de arriba hacia abajo. Y cada columna es una de las partes interactuando o ejecutando código.
Nota
Solo una respuesta será enviada al cliente. Puede ser una de las respuestas de error o será la respuesta de la path operation.
Después de que una de esas respuestas sea enviada, no se puede enviar ninguna otra respuesta.
Consejo
Si lanzas cualquier excepción en el código de la path operation function, será pasada a las dependencias con yield, incluyendo HTTPException. En la mayoría de los casos querrás volver a lanzar esa misma excepción o una nueva desde la dependencia con yield para asegurarte de que se maneje correctamente.
Normalmente el exit code de las dependencias con yield se ejecuta después de la respuesta que se envía al cliente.
Pero si sabes que no necesitarás usar la dependencia después de retornar de la path operation function, puedes usar Depends(scope="function") para decirle a FastAPI que debería cerrar la dependencia después de que la path operation function retorne, pero antes de que la respuesta sea enviada.
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPIapp=FastAPI()defget_username():try:yield"Rick"finally:print("Cleanup up before response is sent")@app.get("/users/me")defget_user_me(username:Annotated[str,Depends(get_username,scope="function")]):returnusername
🤓 Otras versiones y variantes
Consejo
Preferible usar la versión con Annotated si es posible.
fromfastapiimportDepends,FastAPIapp=FastAPI()defget_username():try:yield"Rick"finally:print("Cleanup up before response is sent")@app.get("/users/me")defget_user_me(username:str=Depends(get_username,scope="function")):returnusername
Depends() recibe un parámetro scope que puede ser:
"function": inicia la dependencia antes de la path operation function que maneja la petición, termina la dependencia después de que la path operation function termine, pero antes de que la respuesta sea enviada de vuelta al cliente. Así, la función de dependencia será ejecutada alrededor de la path operation function.
"request": inicia la dependencia antes de la path operation function que maneja la petición (similar a cuando se usa "function"), pero termina después de que la respuesta sea enviada de vuelta al cliente. Así, la función de dependencia será ejecutada alrededor del ciclo de petición y respuesta.
Si no se especifica y la dependencia tiene yield, tendrá un scope de "request" por defecto.
Cuando declaras una dependencia con scope="request" (el valor por defecto), cualquier sub-dependencia necesita tener también un scope de "request".
Pero una dependencia con scope de "function" puede tener dependencias con scope de "function" y scope de "request".
Esto se debe a que cualquier dependencia necesita poder ejecutar su código de salida antes que las sub-dependencias, ya que podría necesitar usarlas durante su código de salida.
Dependencias con yield, HTTPException, except y Background Tasks¶
Las dependencias con yield han evolucionado con el tiempo para cubrir diferentes casos de uso y arreglar algunos problemas.
Por debajo, open("./somefile.txt") crea un objeto que se llama "Context Manager".
Cuando el bloque with termina, se asegura de cerrar el archivo, incluso si hubo excepciones.
Cuando creas una dependencia con yield, FastAPI creará internamente un context manager para ella, y lo combinará con algunas otras herramientas relacionadas.