From e8381e94fbbe415e58cd7a5436ff0fc074f646ec Mon Sep 17 00:00:00 2001 From: Alan Potter Date: Fri, 6 Feb 2026 06:45:05 -0500 Subject: [PATCH] Add `default_dependency_scope` parameter to `FastAPI` Add default_dependency_scope to allow the user to specify a default for dependencies declared without a scope. --- fastapi/applications.py | 11 ++++++- fastapi/dependencies/utils.py | 4 ++- tests/test_dependency_yield_scope.py | 48 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/fastapi/applications.py b/fastapi/applications.py index 340cabfc29..ee8a75a8da 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -27,7 +27,7 @@ from fastapi.openapi.docs import ( ) from fastapi.openapi.utils import get_openapi from fastapi.params import Depends -from fastapi.types import DecoratedCallable, IncEx +from fastapi.types import DecoratedCallable, DependencyScope, IncEx from fastapi.utils import generate_unique_id from starlette.applications import Starlette from starlette.datastructures import State @@ -844,6 +844,12 @@ class FastAPI(Starlette): """ ), ] = None, + default_dependency_scope: Annotated[ + DependencyScope, + Doc(""" + Default scope for dependencies that don't specify a scope. + """), + ] = None, **extra: Annotated[ Any, Doc( @@ -873,6 +879,7 @@ class FastAPI(Starlette): self.servers = servers or [] self.separate_input_output_schemas = separate_input_output_schemas self.openapi_external_docs = openapi_external_docs + self.default_dependency_scope = default_dependency_scope self.extra = extra self.openapi_version: Annotated[ str, @@ -1135,6 +1142,8 @@ class FastAPI(Starlette): async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if self.root_path: scope["root_path"] = self.root_path + if self.default_dependency_scope: + scope["fastapi_default_dependency_scope"] = self.default_dependency_scope await super().__call__(scope, receive, send) def add_api_route( diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index ccd5ae78af..702507f900 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -558,6 +558,7 @@ async def solve_dependencies( assert isinstance(request_astack, AsyncExitStack), "fastapi_inner_astack not found in request scope" function_astack = request.scope.get("fastapi_function_astack") assert isinstance(function_astack, AsyncExitStack), "fastapi_function_astack not found in request scope" + default_dependency_scope = request.scope.get("fastapi_default_dependency_scope") values: dict[str, Any] = {} errors: list[Any] = [] if response is None: @@ -601,7 +602,8 @@ async def solve_dependencies( solved = dependency_cache[sub_dependant.cache_key] elif use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable: use_astack = request_astack - if sub_dependant.scope == "function": + sub_dependant_scope = sub_dependant.scope or default_dependency_scope + if sub_dependant_scope == "function": use_astack = function_astack solved = await _solve_generator( dependant=use_sub_dependant, diff --git a/tests/test_dependency_yield_scope.py b/tests/test_dependency_yield_scope.py index f3fc3cc94f..6e6435aae0 100644 --- a/tests/test_dependency_yield_scope.py +++ b/tests/test_dependency_yield_scope.py @@ -243,3 +243,51 @@ def test_app_level_dep_scope_request() -> None: response = client.get("/app-scope-request") assert response.status_code == 200 assert response.json() == {"status": "ok"} + + +@pytest.mark.parametrize( + "default_scope,expected_is_open", + ( + ( + None, + True, + ), # When default_dependency_scope is unset, scope defaults to "request" + ("function", False), + ("request", True), + ), +) +def test_default_dependency_scope(default_scope, expected_is_open) -> None: + app = FastAPI(default_dependency_scope=default_scope) + + endpoint = "/default-scope" + + @app.get(endpoint) + def _endpoint(session: SessionDefaultDep) -> Any: + def iter_data(): + yield json.dumps({"is_open": session.open}) + + return StreamingResponse(iter_data()) + + with TestClient(app, raise_server_exceptions=False) as client: + response = client.get(endpoint) + assert response.status_code == 200 + data = response.json() + assert data["is_open"] is expected_is_open + +def test_override_default_dependency_scope() -> None: + app = FastAPI(default_dependency_scope="function") + + endpoint = "/request-scope" + + @app.get(endpoint) + def _endpoint(session: SessionRequestDep) -> Any: + def iter_data(): + yield json.dumps({"is_open": session.open}) + + return StreamingResponse(iter_data()) + + with TestClient(app, raise_server_exceptions=False) as client: + response = client.get(endpoint) + assert response.status_code == 200 + data = response.json() + assert data["is_open"] is True