1import inspect2import sys3from collections.abc import Callable4from dataclasses import dataclass, field5from functools import lru_cache, partial6from typing import Any, Literal78from fastapi._compat import ModelField9from fastapi.security.base import SecurityBase10from fastapi.types import DependencyCacheKey1112if sys.version_info >= (3, 13): # pragma: no cover13 from inspect import iscoroutinefunction14else: # pragma: no cover15 from asyncio import iscoroutinefunction161718def _unwrapped_call(call: Callable[..., Any] | None) -> Any:19 if call is None:20 return call # pragma: no cover21 unwrapped = inspect.unwrap(_impartial(call))22 return unwrapped232425def _impartial(func: Callable[..., Any]) -> Callable[..., Any]:26 while isinstance(func, partial):27 func = func.func28 return func293031@dataclass(slots=True)32class Dependant:33 path_params: list[ModelField] = field(default_factory=list)34 query_params: list[ModelField] = field(default_factory=list)35 header_params: list[ModelField] = field(default_factory=list)36 cookie_params: list[ModelField] = field(default_factory=list)37 body_params: list[ModelField] = field(default_factory=list)38 dependencies: list["Dependant"] = field(default_factory=list)39 name: str | None = None40 call: Callable[..., Any] | None = None41 request_param_name: str | None = None42 websocket_param_name: str | None = None43 http_connection_param_name: str | None = None44 response_param_name: str | None = None45 background_tasks_param_name: str | None = None46 security_scopes_param_name: str | None = None47 own_oauth_scopes: list[str] | None = None48 parent_oauth_scopes: list[str] | None = None49 use_cache: bool = True50 path: str | None = None51 scope: Literal["function", "request"] | None = None525354_UsesScopesCache = dict[int, tuple[Dependant, bool]]55_CALLABLE_CLASSIFICATION_CACHE_SIZE = 4096565758class _CallIdentity:59 __slots__ = ("call",)6061 def __init__(self, call: Callable[..., Any]) -> None:62 self.call = call6364 def __hash__(self) -> int:65 return id(self.call)6667 def __eq__(self, other: object) -> bool:68 return isinstance(other, _CallIdentity) and self.call is other.call697071def _get_oauth_scopes(*, dependant: Dependant) -> list[str]:72 scopes = (73 dependant.parent_oauth_scopes.copy() if dependant.parent_oauth_scopes else []74 )75 # This doesn't use a set to preserve order, just in case76 for scope in dependant.own_oauth_scopes or []:77 if scope not in scopes:78 scopes.append(scope)79 return scopes808182def _get_cache_key(83 *,84 dependant: Dependant,85 uses_scopes_cache: _UsesScopesCache | None = None,86) -> DependencyCacheKey:87 scopes_for_cache = (88 tuple(sorted(set(_get_oauth_scopes(dependant=dependant))))89 if _uses_scopes(dependant=dependant, cache=uses_scopes_cache)90 else ()91 )92 return (93 dependant.call,94 scopes_for_cache,95 _get_computed_scope(dependant=dependant) or "",96 )979899def _uses_scopes(100 *, dependant: Dependant, cache: _UsesScopesCache | None = None101) -> bool:102 if cache is None:103 cache = {}104 cache_key = id(dependant)105 cached = cache.get(cache_key)106 if cached is not None and cached[0] is dependant:107 return cached[1]108 if dependant.own_oauth_scopes:109 result = True110 elif dependant.security_scopes_param_name is not None:111 result = True112 elif _is_security_scheme(dependant=dependant):113 result = True114 else:115 result = any(116 _uses_scopes(dependant=sub_dep, cache=cache)117 for sub_dep in dependant.dependencies118 )119 cache[cache_key] = (dependant, result)120 return result121122123def _is_security_scheme(*, dependant: Dependant) -> bool:124 if dependant.call is None:125 return False # pragma: no cover126 unwrapped = _unwrapped_call(dependant.call)127 return isinstance(unwrapped, SecurityBase)128129130def _get_security_scheme(*, dependant: Dependant) -> SecurityBase:131 # Mainly to get the type of SecurityBase, but it's the same dependant.call132 unwrapped = _unwrapped_call(dependant.call)133 assert isinstance(unwrapped, SecurityBase)134 return unwrapped135136137@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)138def _is_gen_callable_cached(call_identity: _CallIdentity) -> bool:139 call = call_identity.call140 if inspect.isgeneratorfunction(_impartial(call)) or inspect.isgeneratorfunction(141 _unwrapped_call(call)142 ):143 return True144 if inspect.isclass(_unwrapped_call(call)):145 return False146 dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004147 if dunder_call is None:148 return False # pragma: no cover149 if inspect.isgeneratorfunction(150 _impartial(dunder_call)151 ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):152 return True153 dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004154 if dunder_unwrapped_call is None:155 return False # pragma: no cover156 return inspect.isgeneratorfunction(157 _impartial(dunder_unwrapped_call)158 ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call))159160161def _is_gen_callable(call: Callable[..., Any] | None) -> bool:162 if call is None:163 return False # pragma: no cover164 return _is_gen_callable_cached(_CallIdentity(call))165166167@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)168def _is_async_gen_callable_cached(call_identity: _CallIdentity) -> bool:169 call = call_identity.call170 if inspect.isasyncgenfunction(_impartial(call)) or inspect.isasyncgenfunction(171 _unwrapped_call(call)172 ):173 return True174 if inspect.isclass(_unwrapped_call(call)):175 return False176 dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004177 if dunder_call is None:178 return False # pragma: no cover179 if inspect.isasyncgenfunction(180 _impartial(dunder_call)181 ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):182 return True183 dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004184 if dunder_unwrapped_call is None:185 return False # pragma: no cover186 return inspect.isasyncgenfunction(187 _impartial(dunder_unwrapped_call)188 ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call))189190191def _is_async_gen_callable(call: Callable[..., Any] | None) -> bool:192 if call is None:193 return False # pragma: no cover194 return _is_async_gen_callable_cached(_CallIdentity(call))195196197@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)198def _is_coroutine_callable_cached(call_identity: _CallIdentity) -> bool:199 call = call_identity.call200 if inspect.isroutine(_impartial(call)) and iscoroutinefunction(_impartial(call)):201 return True202 if inspect.isroutine(_unwrapped_call(call)) and iscoroutinefunction(203 _unwrapped_call(call)204 ):205 return True206 if inspect.isclass(_unwrapped_call(call)):207 return False208 dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004209 if dunder_call is None:210 return False # pragma: no cover211 if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(212 _unwrapped_call(dunder_call)213 ):214 return True215 dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004216 if dunder_unwrapped_call is None:217 return False # pragma: no cover218 return iscoroutinefunction(219 _impartial(dunder_unwrapped_call)220 ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call))221222223def _is_coroutine_callable(call: Callable[..., Any] | None) -> bool:224 if call is None:225 return False # pragma: no cover226 return _is_coroutine_callable_cached(_CallIdentity(call))227228229def _get_computed_scope(*, dependant: Dependant) -> str | None:230 if dependant.scope:231 return dependant.scope232 if _is_gen_callable(dependant.call) or _is_async_gen_callable(dependant.call):233 return "request"234 return None
Findings
✓ No findings reported for this file.