If you use a try block in a dependency with yield, you'll receive any exception that was thrown when using the dependency.
For example, if some code at some point in the middle, in another dependency or in a path operation, made a database transaction "rollback" or create any other error, you will receive the exception in your dependency.
So, you can look for that specific exception inside the dependency with except SomeException.
In the same way, you can use finally to make sure the exit steps are executed, no matter if there was an exception or not.
The same way, you could have some dependencies with yield and some other dependencies with return, and have some of those depend on some of the others.
And you could have a single dependency that requires several other dependencies with yield, etc.
You can have any combinations of dependencies that you want.
FastAPI will make sure everything is run in the correct order.
You saw that you can use dependencies with yield and have try blocks that catch exceptions.
The same way, you could raise an HTTPException or similar in the exit code, after the yield.
Tip
This is a somewhat advanced technique, and in most of the cases you won't really need it, as you can raise exceptions (including HTTPException) from inside of the rest of your application code, for example, in the path operation function.
But it's there for you if you need it. 🤓
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
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=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
Tip
Prefer to use the Annotated version if possible.
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
An alternative you could use to catch exceptions (and possibly also raise another HTTPException) is to create a Custom Exception Handler.
If you catch an exception using except in a dependency with yield and you don't raise it again (or raise a new exception), FastAPI won't be able to notice there was an exception, the same way that would happen with regular Python:
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
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=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
Tip
Prefer to use the Annotated version if possible.
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
In this case, the client will see an HTTP 500 Internal Server Error response as it should, given that we are not raising an HTTPException or similar, but the server will not have any logs or any other indication of what was the error. 😱
Always raise in Dependencies with yield and except¶
If you catch an exception in a dependency with yield, unless you are raising another HTTPException or similar, you should re-raise the original exception.
You can re-raise the same exception using 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
fromfastapiimportDepends,FastAPI,HTTPExceptionfromtyping_extensionsimportAnnotatedapp=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
Tip
Prefer to use the Annotated version if possible.
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
Now the client will get the same HTTP 500 Internal Server Error response, but the server will have our custom InternalError in the logs. 😎
The sequence of execution is more or less like this diagram. Time flows from top to bottom. And each column is one of the parts interacting or executing code.
Info
Only one response will be sent to the client. It might be one of the error responses or it will be the response from the path operation.
After one of those responses is sent, no other response can be sent.
Tip
This diagram shows HTTPException, but you could also raise any other exception that you catch in a dependency with yield or with a Custom Exception Handler.
If you raise any exception, it will be passed to the dependencies with yield, including HTTPException. In most cases you will want to re-raise that same exception or a new one from the dependency with yield to make sure it's properly handled.
Dependencies with yield, HTTPException, except and Background Tasks¶
Warning
You most probably don't need these technical details, you can skip this section and continue below.
These details are useful mainly if you were using a version of FastAPI prior to 0.106.0 and used resources from dependencies with yield in background tasks.
Dependencies with yield and except, Technical Details¶
Before FastAPI 0.110.0, if you used a dependency with yield, and then you captured an exception with except in that dependency, and you didn't raise the exception again, the exception would be automatically raised/forwarded to any exception handlers or the internal server error handler.
This was changed in version 0.110.0 to fix unhandled memory consumption from forwarded exceptions without a handler (internal server errors), and to make it consistent with the behavior of regular Python code.
Background Tasks and Dependencies with yield, Technical Details¶
Before FastAPI 0.106.0, raising exceptions after yield was not possible, the exit code in dependencies with yield was executed after the response was sent, so Exception Handlers would have already run.
This was designed this way mainly to allow using the same objects "yielded" by dependencies inside of background tasks, because the exit code would be executed after the background tasks were finished.
Nevertheless, as this would mean waiting for the response to travel through the network while unnecessarily holding a resource in a dependency with yield (for example a database connection), this was changed in FastAPI 0.106.0.
Tip
Additionally, a background task is normally an independent set of logic that should be handled separately, with its own resources (e.g. its own database connection).
So, this way you will probably have cleaner code.
If you used to rely on this behavior, now you should create the resources for background tasks inside the background task itself, and use internally only data that doesn't depend on the resources of dependencies with yield.
For example, instead of using the same database session, you would create a new database session inside of the background task, and you would obtain the objects from the database using this new session. And then instead of passing the object from the database as a parameter to the background task function, you would pass the ID of that object and then obtain the object again inside the background task function.