From cb18282fc8b6c19ac2ab24ff001955d835a27b13 Mon Sep 17 00:00:00 2001 From: ipeluffo Date: Thu, 18 Jun 2026 17:52:32 +0100 Subject: [PATCH] Keep original logic to calculate fields --------- Co-authored-by: Federico Jasson --- fastapi/dependencies/models.py | 314 +++++++++++++++++---------------- 1 file changed, 159 insertions(+), 155 deletions(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 3744fc7a1..3f3bc2f0d 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -49,6 +49,155 @@ class Dependant: use_cache: bool = True path: str | None = None scope: Literal["function", "request"] | None = None + + @property + def _oauth_scopes(self) -> list[str]: + scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else [] + # This doesn't use a set to preserve order, just in case + for scope in self.own_oauth_scopes or []: + if scope not in scopes: + scopes.append(scope) + return scopes + + @property + def _cache_key(self) -> DependencyCacheKey: + scopes_for_cache = ( + tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else () + ) + return ( + self.call, + scopes_for_cache, + self.computed_scope or "", + ) + + @property + def __uses_scopes(self) -> bool: + if self.own_oauth_scopes: + return True + if self.security_scopes_param_name is not None: + return True + if self._is_security_scheme: + return True + for sub_dep in self.dependencies: + if sub_dep._uses_scopes: + return True + return False + + @property + def __is_security_scheme(self) -> bool: + if self.call is None: + return False # pragma: no cover + unwrapped = _unwrapped_call(self.call) + return isinstance(unwrapped, SecurityBase) + + # Mainly to get the type of SecurityBase, but it's the same self.call + @property + def __security_scheme(self) -> SecurityBase: + unwrapped = _unwrapped_call(self.call) + assert isinstance(unwrapped, SecurityBase) + return unwrapped + + @property + def __security_dependencies(self) -> list["Dependant"]: + security_deps = [dep for dep in self.dependencies if dep._is_security_scheme] + return security_deps + + @property + def _is_gen_callable(self) -> bool: + if self.call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(self.call) + ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)): + return True + if inspect.isclass(_unwrapped_call(self.call)): + return False + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(dunder_call) + ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)): + return True + dunder_unwrapped_call = getattr( + _unwrapped_call(self.call), "__call__", None + ) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(dunder_unwrapped_call) + ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)): + return True + return False + + @property + def _is_async_gen_callable(self) -> bool: + if self.call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(self.call) + ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)): + return True + if inspect.isclass(_unwrapped_call(self.call)): + return False + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(dunder_call) + ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)): + return True + dunder_unwrapped_call = getattr( + _unwrapped_call(self.call), "__call__", None + ) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(dunder_unwrapped_call) + ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)): + return True + return False + + @property + def _is_coroutine_callable(self) -> bool: + if self.call is None: + return False # pragma: no cover + if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction( + _impartial(self.call) + ): + return True + if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction( + _unwrapped_call(self.call) + ): + return True + if inspect.isclass(_unwrapped_call(self.call)): + return False + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction( + _unwrapped_call(dunder_call) + ): + return True + dunder_unwrapped_call = getattr( + _unwrapped_call(self.call), "__call__", None + ) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if iscoroutinefunction( + _impartial(dunder_unwrapped_call) + ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)): + return True + return False + + @property + def _computed_scope(self) -> str | None: + if self.scope: + return self.scope + if self.is_gen_callable or self.is_async_gen_callable: + return "request" + return None + # Lazy cached fields _oauth_scopes_cache: list[str] | None = field(default=None, init=False, repr=False) _cache_key_cache: DependencyCacheKey | None = field( @@ -74,214 +223,69 @@ class Dependant: @property def oauth_scopes(self) -> list[str]: if self._oauth_scopes_cache is None: - scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else [] - # This doesn't use a set to preserve order, just in case - for scope in self.own_oauth_scopes or []: - if scope not in scopes: - scopes.append(scope) - self._oauth_scopes_cache = scopes + self._oauth_scopes_cache = self._oauth_scopes return self._oauth_scopes_cache @property def cache_key(self) -> DependencyCacheKey: if self._cache_key_cache is None: - scopes_for_cache = ( - tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else () - ) - self._cache_key_cache = ( - self.call, - scopes_for_cache, - self.computed_scope or "", - ) + self._cache_key_cache = self._cache_key return self._cache_key_cache @property def _uses_scopes(self) -> bool: if self._uses_scopes_cache is None: - if self.own_oauth_scopes: - self._uses_scopes_cache = True - elif self.security_scopes_param_name is not None: - self._uses_scopes_cache = True - elif self._is_security_scheme: - self._uses_scopes_cache = True - - for sub_dep in self.dependencies: - if sub_dep._uses_scopes: - self._uses_scopes_cache = True - break - - if self._uses_scopes_cache is None: - self._uses_scopes_cache = False + self._uses_scopes_cache = self.__uses_scopes return self._uses_scopes_cache @property def _is_security_scheme(self) -> bool: if self._is_security_scheme_cache is None: - if self.call is None: - self._is_security_scheme_cache = False # pragma: no cover - else: - unwrapped = _unwrapped_call(self.call) - self._is_security_scheme_cache = isinstance(unwrapped, SecurityBase) + self._is_security_scheme_cache = self.__is_security_scheme return self._is_security_scheme_cache - # Mainly to get the type of SecurityBase, but it's the same self.call @property def _security_scheme(self) -> SecurityBase: if self._security_scheme_cache is None: - unwrapped = _unwrapped_call(self.call) - assert isinstance(unwrapped, SecurityBase) - self._security_scheme_cache = unwrapped + self._security_scheme_cache = self.__security_scheme return self._security_scheme_cache @property def _security_dependencies(self) -> list["Dependant"]: if self._security_dependencies_cache is None: - security_deps = [ - dep for dep in self.dependencies if dep._is_security_scheme - ] - self._security_dependencies_cache = security_deps + self._security_dependencies_cache = self.__security_dependencies return self._security_dependencies_cache @property def is_gen_callable(self) -> bool: if self._is_gen_callable_cache is None: - if self.call is None: - self._is_gen_callable_cache = False # pragma: no cover - elif inspect.isgeneratorfunction( - _impartial(self.call) - ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)): - self._is_gen_callable_cache = True - elif inspect.isclass(_unwrapped_call(self.call)): - self._is_gen_callable_cache = False - - if self._is_gen_callable_cache is not None: - return self._is_gen_callable_cache - - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - self._is_gen_callable_cache = False # pragma: no cover - elif inspect.isgeneratorfunction( - _impartial(dunder_call) - ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)): - self._is_gen_callable_cache = True - - if self._is_gen_callable_cache is not None: - return self._is_gen_callable_cache - - dunder_unwrapped_call = getattr( # noqa: B004 - _unwrapped_call(self.call), - "__call__", - None, - ) - if dunder_unwrapped_call is None: - self._is_gen_callable_cache = False # pragma: no cover - if inspect.isgeneratorfunction( - _impartial(dunder_unwrapped_call) - ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)): - self._is_gen_callable_cache = True - else: - self._is_gen_callable_cache = False + self._is_gen_callable_cache = self._is_gen_callable return self._is_gen_callable_cache @property def is_async_gen_callable(self) -> bool: if self._is_async_gen_callable_cache is None: - if self.call is None: - self._is_async_gen_callable_cache = False # pragma: no cover - elif inspect.isasyncgenfunction( - _impartial(self.call) - ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)): - self._is_async_gen_callable_cache = True - elif inspect.isclass(_unwrapped_call(self.call)): - self._is_async_gen_callable_cache = False - - if self._is_async_gen_callable_cache is not None: - return self._is_async_gen_callable_cache - - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - self._is_async_gen_callable_cache = False # pragma: no cover - elif inspect.isasyncgenfunction( - _impartial(dunder_call) - ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)): - self._is_async_gen_callable_cache = True - - if self._is_async_gen_callable_cache is not None: - return self._is_async_gen_callable_cache - - dunder_unwrapped_call = getattr( # noqa: B004 - _unwrapped_call(self.call), "__call__", None - ) - if dunder_unwrapped_call is None: - self._is_async_gen_callable_cache = False # pragma: no cover - elif inspect.isasyncgenfunction( - _impartial(dunder_unwrapped_call) - ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)): - self._is_async_gen_callable_cache = True - else: - self._is_async_gen_callable_cache = False + self._is_async_gen_callable_cache = self._is_async_gen_callable return self._is_async_gen_callable_cache @property def is_coroutine_callable(self) -> bool: if self._is_coroutine_callable_cache is None: - if self.call is None: - self._is_coroutine_callable_cache = False # pragma: no cover - elif inspect.isroutine(_impartial(self.call)) and iscoroutinefunction( - _impartial(self.call) - ): - self._is_coroutine_callable_cache = True - elif inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction( - _unwrapped_call(self.call) - ): - self._is_coroutine_callable_cache = True - elif inspect.isclass(_unwrapped_call(self.call)): - self._is_coroutine_callable_cache = False - - if self._is_coroutine_callable_cache is not None: - return self._is_coroutine_callable_cache - - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - self._is_coroutine_callable_cache = False # pragma: no cover - elif iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction( - _unwrapped_call(dunder_call) - ): - self._is_coroutine_callable_cache = True - - if self._is_coroutine_callable_cache is not None: - return self._is_coroutine_callable_cache - - dunder_unwrapped_call = getattr( # noqa: B004 - _unwrapped_call(self.call), "__call__", None - ) - if dunder_unwrapped_call is None: - self._is_coroutine_callable_cache = False # pragma: no cover - elif iscoroutinefunction( - _impartial(dunder_unwrapped_call) - ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)): - self._is_coroutine_callable_cache = True - else: - self._is_coroutine_callable_cache = False + self._is_coroutine_callable_cache = self._is_coroutine_callable return self._is_coroutine_callable_cache @property def computed_scope(self) -> str | None: if self._computed_scope_cache is None: - if self.scope: - self._computed_scope_cache = self.scope - elif self.is_gen_callable or self.is_async_gen_callable: - self._computed_scope_cache = "request" - else: - self._computed_scope_cache = None + self._computed_scope_cache = self._computed_scope return self._computed_scope_cache