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, contextmanager_in_threadpool,
) )
from fastapi.dependencies.models import Dependant, SecurityRequirement from fastapi.dependencies.models import Dependant, SecurityRequirement
from fastapi.exceptions import DependencyScopeError
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
@ -74,7 +75,7 @@ from starlette.datastructures import (
from starlette.requests import HTTPConnection, Request from starlette.requests import HTTPConnection, Request
from starlette.responses import Response from starlette.responses import Response
from starlette.websockets import WebSocket 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 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: if isinstance(depends, params.Security) and depends.scopes:
use_security_scopes.extend(depends.scopes) use_security_scopes.extend(depends.scopes)
return get_dependant( 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, name: Optional[str] = None,
security_scopes: Optional[List[str]] = None, security_scopes: Optional[List[str]] = None,
use_cache: bool = True, use_cache: bool = True,
scope: Literal["function", "request"] = "request",
) -> Dependant: ) -> Dependant:
dependant = Dependant( dependant = Dependant(
call=call, call=call,
@ -244,6 +249,7 @@ def get_dependant(
path=path, path=path,
security_scopes=security_scopes, security_scopes=security_scopes,
use_cache=use_cache, use_cache=use_cache,
scope=scope,
) )
path_param_names = get_path_param_names(path) path_param_names = get_path_param_names(path)
endpoint_signature = get_typed_signature(call) endpoint_signature = get_typed_signature(call)
@ -258,6 +264,12 @@ def get_dependant(
) )
if param_details.depends is not None: if param_details.depends is not None:
assert param_details.depends.dependency 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 [] use_security_scopes = security_scopes or []
if isinstance(param_details.depends, params.Security): if isinstance(param_details.depends, params.Security):
if param_details.depends.scopes: if param_details.depends.scopes:
@ -268,6 +280,7 @@ def get_dependant(
name=param_name, name=param_name,
security_scopes=use_security_scopes, security_scopes=use_security_scopes,
use_cache=param_details.depends.use_cache, use_cache=param_details.depends.use_cache,
scope=param_details.depends.scope,
) )
if isinstance(param_details.depends.dependency, SecurityBase): if isinstance(param_details.depends.dependency, SecurityBase):
use_scopes: List[str] = [] use_scopes: List[str] = []
@ -585,9 +598,19 @@ async def solve_dependencies(
response: Optional[Response] = None, response: Optional[Response] = None,
dependency_overrides_provider: Optional[Any] = None, dependency_overrides_provider: Optional[Any] = None,
dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], 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, async_exit_stack: AsyncExitStack,
embed_body_fields: bool, embed_body_fields: bool,
) -> SolvedDependency: ) -> 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] = {} values: Dict[str, Any] = {}
errors: List[Any] = [] errors: List[Any] = []
if response is None: if response is None:
@ -596,7 +619,6 @@ async def solve_dependencies(
response.status_code = None # type: ignore response.status_code = None # type: ignore
if dependency_cache is None: if dependency_cache is None:
dependency_cache = {} dependency_cache = {}
sub_dependant: Dependant
for sub_dependant in dependant.dependencies: for sub_dependant in dependant.dependencies:
sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
sub_dependant.cache_key = cast( sub_dependant.cache_key = cast(
@ -618,6 +640,7 @@ async def solve_dependencies(
call=call, call=call,
name=sub_dependant.name, name=sub_dependant.name,
security_scopes=sub_dependant.security_scopes, security_scopes=sub_dependant.security_scopes,
scope=sub_dependant.scope,
) )
solved_result = await solve_dependencies( 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: if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
solved = dependency_cache[sub_dependant.cache_key] solved = dependency_cache[sub_dependant.cache_key]
elif is_gen_callable(call) or is_async_gen_callable(call): 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( 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): elif is_coroutine_callable(call):
solved = await call(**solved_result.values) solved = await call(**solved_result.values)

Loading…
Cancel
Save