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.
This commit is contained in:
Alan Potter 2026-02-06 06:45:05 -05:00
parent 95bceaa2fb
commit e8381e94fb
3 changed files with 61 additions and 2 deletions

View File

@ -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(

View File

@ -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,

View File

@ -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