From a69af67db34b25f7cc1546902f7ce6acc77977bf Mon Sep 17 00:00:00 2001 From: ipeluffo Date: Tue, 14 Apr 2026 11:18:15 +0100 Subject: [PATCH] Refactor `Dependant.is_coroutine_callable` property for improved memory usage ``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ``` --- fastapi/dependencies/models.py | 69 ++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index a19afd2fa8..f1aace7329 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -58,6 +58,7 @@ class Dependant: _security_dependencies_cache: list["Dependant"] = field(default=None, init=False, repr=False) _is_gen_callable_cache: bool = field(default=None, init=False, repr=False) _is_async_gen_callable_cache: bool = field(default=None, init=False, repr=False) + _is_coroutine_callable_cache: bool = field(default=None, init=False, repr=False) @property def oauth_scopes(self) -> list[str]: @@ -210,35 +211,47 @@ class Dependant: return self._is_async_gen_callable_cache - @cached_property + @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 + 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(_unwrapped_call(self.call), "__call__", None) # noqa: B004 + 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 + + return self._is_coroutine_callable_cache @cached_property def computed_scope(self) -> str | None: