Browse Source

Fix: make solve_dependencies re-raise RuntimeError

If an async generator dependency raises RuntimeError:
generator didn't yield, make solve_dependencies catch
and re-raise it, to more easily identify the dependency
responsible for the error, and to provide more information
on how to fix the dependency
pull/13264/head
Jeremy Epstein 1 year ago
parent
commit
8ffd22f890
  1. 21
      fastapi/dependencies/utils.py
  2. 29
      tests/test_dependency_runtime_errors.py

21
fastapi/dependencies/utils.py

@ -54,7 +54,7 @@ from fastapi.concurrency import (
contextmanager_in_threadpool, contextmanager_in_threadpool,
) )
from fastapi.dependencies.models import Dependant, SecurityRequirement from fastapi.dependencies.models import Dependant, SecurityRequirement
from fastapi.exceptions import DependencyScopeError from fastapi.exceptions import DependencyScopeError, FastAPIError
from fastapi.logger import logger from fastapi.logger import logger
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import OAuth2, SecurityScopes from fastapi.security.oauth2 import OAuth2, SecurityScopes
@ -549,7 +549,24 @@ async def _solve_generator(
cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values)) cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))
elif dependant.is_async_gen_callable: elif dependant.is_async_gen_callable:
cm = asynccontextmanager(dependant.call)(**sub_values) cm = asynccontextmanager(dependant.call)(**sub_values)
return await stack.enter_async_context(cm)
try:
solved = await stack.enter_async_context(cm)
except RuntimeError as ex:
if str(ex) != "generator didn't yield":
raise ex
dependency_name = getattr(call, "__name__", "(unknown)")
raise FastAPIError(
f"Dependency {dependency_name} raised: {ex}. There's a high chance that "
"this is a dependency with yield that has a block with a bare except, or a "
"block with except Exception, and is not raising the exception again. Read "
"more about it in the docs: "
"https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield"
"/#dependencies-with-yield-and-except"
) from ex
return solved
@dataclass @dataclass

29
tests/test_dependency_runtime_errors.py

@ -0,0 +1,29 @@
import pytest
from anyio import open_file
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
async def get_username():
try:
async with await open_file("/path/to/sanchez.txt", "r") as f:
yield await f.read() # pragma: no cover
except OSError as ex:
raise RuntimeError("File something something, wubba lubba dub dub!") from ex
@app.get("/me")
def get_me(username: str = Depends(get_username)):
return username # pragma: no cover
client = TestClient(app)
@pytest.mark.anyio
def test_runtime_error():
with pytest.raises(RuntimeError) as exc_info:
client.get("/me")
assert "File something something" in exc_info.value.args[0]
Loading…
Cancel
Save