diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 4b69e39a1..a927dbb69 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -54,7 +54,7 @@ from fastapi.concurrency import ( contextmanager_in_threadpool, ) 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.security.base import SecurityBase from fastapi.security.oauth2 import OAuth2, SecurityScopes @@ -549,7 +549,24 @@ async def _solve_generator( cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values)) elif dependant.is_async_gen_callable: 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 diff --git a/tests/test_dependency_runtime_errors.py b/tests/test_dependency_runtime_errors.py new file mode 100644 index 000000000..5dd45976e --- /dev/null +++ b/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]