[QUESTION] Is it possible to catch exceptions in async generator dependencies?
See original GitHub issueDescription
Is it possible for an async generator dependency (as seen in the docs) to handle exceptions thrown by an endpoint?
Additional context
Suppose I have the following get_db
function:
async def get_db(request: Request) -> asyncpg.connection.Connection:
"""Obtain a database connection from the pool."""
if pool is None:
logger.error("Unable to provide a connection on an uninitalised pool.")
raise HTTPException(
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
detail="Unable to provide a connection on an uninitalised pool.",
)
conn = None
try:
conn = await pool.acquire()
# Test that the connection is still active by running a trivial query
# (https://docs.sqlalchemy.org/en/13/core/pooling.html#disconnect-handling-pessimistic)
try:
await conn.execute("SELECT 1")
except ConnectionDoesNotExistError:
conn = await pool.acquire()
yield conn
except (PostgresConnectionError, OSError) as e:
logger.error("Unable to connect to the database: %s", e)
raise HTTPException(
status_code=HTTP_500_INTERNAL_SERVER_ERROR, detail="Unable to connect to the database."
)
# --- The problem is with this particular exception handling ---
except SyntaxOrAccessError as e:
logger.error("Unable to execute query: %s", e)
raise HTTPException(
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
detail="Unable to execute the required query against the database.",
)
finally:
if pool is not None and conn:
await pool.release(conn)
If an endpoint using this dependency runs an invalid query, I would like to catch that exception and handle it here. However, it seems that due to the way FastAPI is wrapping the async context manager, this is not possible. Instead the exception is not caught by my dependency function, but instead thrown in the ASGI console.
The client just sees:
HTTP/1.1 500 Internal Server Error
content-length: 21
content-type: text/plain; charset=utf-8
date: Mon, 21 Oct 2019 21:24:53 GMT
server: uvicorn
Internal Server Error
This is normally possible using asynccontextmanager
like so:
from async_generator import asynccontextmanager
@asynccontextmanager
async def boo():
print("before")
try:
yield "hello"
except ValueError as e:
print("got an exception", e)
print("after")
async with boo() as b:
print("got", b)
raise ValueError("oh no")
Output:
before
got hello
got an exception oh no
after
I suspect this relates to the following function and how it handles exceptions: https://github.com/tiangolo/fastapi/blob/master/fastapi/concurrency.py#L36
Thanks so much for your help in advance 😄 Fotis
Issue Analytics
- State:
- Created 4 years ago
- Comments:6 (4 by maintainers)
Top GitHub Comments
@fgimian you can’t raise exceptions from dependencies after the
yield
. You can raise before theyield
and it will be handled by the default exception handler (even beforeexcept
code in your dependency is reached).But the context of dependencies is before/around the context of exception handlers. It happens outside of the request handling.
So, you could even return a response, and then have a bunch of background tasks running using the same DB session from the dependency, and they could throw an error, and it would still be caught by the dependency with
yield
in the theexcept
block. And that would be long after sending the response. Also, long after the last chance to raise anHTTPException
, but you could still handle the error and close the DB session.Oh, I see what you mean, you want it to basically work as an exception handler. I think that might be hard to handle properly. The reason for that is that the dependency function isn’t executed in a parent scope of the endpoint function.
But it should at least currently behave properly in terms of cleanup.
I suspect there may be a way to make this work as is or with minor modifications to fastapi, but I think it might require some heavy digging into contextlib to figure it out.