From 65ef53ae6ac110ae7d56f81a7723e58ab319f339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 27 Jul 2026 13:18:26 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20the=20lru=5Fcache?= =?UTF-8?q?=20limit=20for=20dependencies=20to=20account=20for=20large=20ap?= =?UTF-8?q?ps=20(#16062)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/models.py | 7 ++++--- tests/test_dependency_models.py | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 3ae682cb39..58eae0eafb 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -52,6 +52,7 @@ class Dependant: _UsesScopesCache = dict[int, tuple[Dependant, bool]] +_CALLABLE_CLASSIFICATION_CACHE_SIZE = 4096 class _CallIdentity: @@ -137,7 +138,7 @@ def _get_security_dependencies(*, dependant: Dependant) -> list[Dependant]: return [dep for dep in dependant.dependencies if _is_security_scheme(dependant=dep)] -@lru_cache(maxsize=1024) +@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE) def _is_gen_callable_cached(call_identity: _CallIdentity) -> bool: call = call_identity.call if inspect.isgeneratorfunction(_impartial(call)) or inspect.isgeneratorfunction( @@ -167,7 +168,7 @@ def _is_gen_callable(call: Callable[..., Any] | None) -> bool: return _is_gen_callable_cached(_CallIdentity(call)) -@lru_cache(maxsize=1024) +@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE) def _is_async_gen_callable_cached(call_identity: _CallIdentity) -> bool: call = call_identity.call if inspect.isasyncgenfunction(_impartial(call)) or inspect.isasyncgenfunction( @@ -197,7 +198,7 @@ def _is_async_gen_callable(call: Callable[..., Any] | None) -> bool: return _is_async_gen_callable_cached(_CallIdentity(call)) -@lru_cache(maxsize=1024) +@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE) def _is_coroutine_callable_cached(call_identity: _CallIdentity) -> bool: call = call_identity.call if inspect.isroutine(_impartial(call)) and iscoroutinefunction(_impartial(call)): diff --git a/tests/test_dependency_models.py b/tests/test_dependency_models.py index a2a36eaff4..9ba6009442 100644 --- a/tests/test_dependency_models.py +++ b/tests/test_dependency_models.py @@ -1,4 +1,4 @@ -from collections.abc import AsyncGenerator, Generator +from collections.abc import AsyncGenerator, Callable, Generator from typing import Any from fastapi.dependencies.models import ( @@ -93,7 +93,27 @@ def test_callable_classification_is_shared_by_call() -> None: cache_info = cached_function.cache_info() assert cache_info.hits == 1 assert cache_info.misses == 1 - assert cache_info.maxsize == 1024 + assert cache_info.maxsize == 4096 + + +def test_callable_classification_cache_supports_large_apps() -> None: + callables: list[Callable[[], None]] = [lambda: None for _ in range(3000)] + + for classifier, cached_classifier in ( + (_is_gen_callable, _is_gen_callable_cached), + (_is_async_gen_callable, _is_async_gen_callable_cached), + (_is_coroutine_callable, _is_coroutine_callable_cached), + ): + cached_classifier.cache_clear() + + for _ in range(2): + assert all(not classifier(call) for call in callables) + + cache_info = cached_classifier.cache_info() + assert cache_info.hits == len(callables) + assert cache_info.misses == len(callables) + assert cache_info.maxsize == 4096 + cached_classifier.cache_clear() def test_unhashable_callable_classification() -> None: