Browse Source

Use new scoped async exit stacks for dependencies

pull/14262/head
Sebastián Ramírez 9 months ago
parent
commit
86ad30fa92
  1. 34
      fastapi/dependencies/utils.py

34
fastapi/dependencies/utils.py

@ -55,6 +55,7 @@ from fastapi.concurrency import (
contextmanager_in_threadpool,
)
from fastapi.dependencies.models import Dependant, SecurityRequirement
from fastapi.exceptions import DependencyScopeError
from fastapi.logger import logger
from fastapi.security.base import SecurityBase
from fastapi.security.oauth2 import OAuth2, SecurityScopes
@ -74,7 +75,7 @@ from starlette.datastructures import (
from starlette.requests import HTTPConnection, Request
from starlette.responses import Response
from starlette.websockets import WebSocket
from typing_extensions import Annotated, get_args, get_origin
from typing_extensions import Annotated, Literal, get_args, get_origin
from .. import temp_pydantic_v1_params
@ -133,7 +134,10 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De
if isinstance(depends, params.Security) and depends.scopes:
use_security_scopes.extend(depends.scopes)
return get_dependant(
path=path, call=depends.dependency, security_scopes=use_security_scopes
path=path,
call=depends.dependency,
security_scopes=use_security_scopes,
scope=depends.scope,
)
@ -237,6 +241,7 @@ def get_dependant(
name: Optional[str] = None,
security_scopes: Optional[List[str]] = None,
use_cache: bool = True,
scope: Literal["function", "request"] = "request",
) -> Dependant:
dependant = Dependant(
call=call,
@ -244,6 +249,7 @@ def get_dependant(
path=path,
security_scopes=security_scopes,
use_cache=use_cache,
scope=scope,
)
path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call)
@ -258,6 +264,12 @@ def get_dependant(
)
if param_details.depends is not None:
assert param_details.depends.dependency
if dependant.scope == "request":
if param_details.depends.scope == "function":
raise DependencyScopeError(
f'The dependency {dependant} has a scope of "request", it'
'cannot depend on dependencies with scope "function".'
)
use_security_scopes = security_scopes or []
if isinstance(param_details.depends, params.Security):
if param_details.depends.scopes:
@ -268,6 +280,7 @@ def get_dependant(
name=param_name,
security_scopes=use_security_scopes,
use_cache=param_details.depends.use_cache,
scope=param_details.depends.scope,
)
if isinstance(param_details.depends.dependency, SecurityBase):
use_scopes: List[str] = []
@ -585,9 +598,19 @@ async def solve_dependencies(
response: Optional[Response] = None,
dependency_overrides_provider: Optional[Any] = None,
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None,
# TODO: remove this parameter later, no longer used, not removing it yet as some
# people might be monkey patching this function (although that's not supported)
async_exit_stack: AsyncExitStack,
embed_body_fields: bool,
) -> SolvedDependency:
request_astack = request.scope.get("fastapi_inner_astack")
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"
)
values: Dict[str, Any] = {}
errors: List[Any] = []
if response is None:
@ -596,7 +619,6 @@ async def solve_dependencies(
response.status_code = None # type: ignore
if dependency_cache is None:
dependency_cache = {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies:
sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
sub_dependant.cache_key = cast(
@ -618,6 +640,7 @@ async def solve_dependencies(
call=call,
name=sub_dependant.name,
security_scopes=sub_dependant.security_scopes,
scope=sub_dependant.scope,
)
solved_result = await solve_dependencies(
@ -638,8 +661,11 @@ async def solve_dependencies(
if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
solved = dependency_cache[sub_dependant.cache_key]
elif is_gen_callable(call) or is_async_gen_callable(call):
use_astack = request_astack
if sub_dependant.scope == "function":
use_astack = function_astack
solved = await solve_generator(
call=call, stack=async_exit_stack, sub_values=solved_result.values
call=call, stack=use_astack, sub_values=solved_result.values
)
elif is_coroutine_callable(call):
solved = await call(**solved_result.values)

Loading…
Cancel
Save