From 293b140528c75bcd1a8c7f12438ca01b0558e613 Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 23 Sep 2025 14:03:46 +0300 Subject: [PATCH 1/4] Update func.pyi with precise cache decorator type hints --- stubs/cachetools/cachetools/func.pyi | 55 +++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/stubs/cachetools/cachetools/func.pyi b/stubs/cachetools/cachetools/func.pyi index 8608a1060e43..0480334ffe19 100644 --- a/stubs/cachetools/cachetools/func.pyi +++ b/stubs/cachetools/cachetools/func.pyi @@ -1,16 +1,51 @@ -from _typeshed import IdentityFunction from collections.abc import Callable, Sequence -from typing import TypeVar +from typing import Any, Final, Generic, NamedTuple, TypeVar, overload + +__all__: Final = ("fifo_cache", "lfu_cache", "lru_cache", "rr_cache", "ttl_cache") -__all__ = ("fifo_cache", "lfu_cache", "lru_cache", "rr_cache", "ttl_cache") _T = TypeVar("_T") +_R = TypeVar("_R") + +class _CacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int | None + currsize: int + +class _cachetools_cache_wrapper(Generic[_R]): + __wrapped__: Callable[..., _R] + def __call__(self, /, *args: Any, **kwargs: Any) -> _R: ... + def cache_info(self) -> _CacheInfo: ... + def cache_clear(self) -> None: ... + def cache_parameters(self) -> dict[str, Any]: ... -def fifo_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ... -def lfu_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ... -def lru_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ... +@overload +def fifo_cache( + maxsize: int | None = 128, typed: bool = False +) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ... +@overload +def fifo_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ... +@overload +def lfu_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ... +@overload +def lfu_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ... +@overload +def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ... +@overload +def lru_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ... +@overload def rr_cache( - maxsize: float | None = 128, choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False -) -> IdentityFunction: ... + maxsize: int | None = 128, choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False +) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ... +@overload +def rr_cache( + maxsize: Callable[..., _R], choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False +) -> _cachetools_cache_wrapper[_R]: ... +@overload +def ttl_cache( + maxsize: int | None = 128, ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False +) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ... +@overload def ttl_cache( - maxsize: float | None = 128, ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False -) -> IdentityFunction: ... + maxsize: Callable[..., _R], ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False +) -> _cachetools_cache_wrapper[_R]: ... From 8dda4ea3b527cbba392af4b96bc3bfb73b2ba000 Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 23 Sep 2025 14:04:17 +0300 Subject: [PATCH 2/4] Add positional-only marker to methodkey and typedmethodkey --- stubs/cachetools/cachetools/keys.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/cachetools/cachetools/keys.pyi b/stubs/cachetools/cachetools/keys.pyi index 2f16e47edbf8..be1c7903c9c0 100644 --- a/stubs/cachetools/cachetools/keys.pyi +++ b/stubs/cachetools/cachetools/keys.pyi @@ -4,6 +4,6 @@ from collections.abc import Hashable __all__ = ("hashkey", "methodkey", "typedkey", "typedmethodkey") def hashkey(*args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... -def methodkey(self: Unused, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... +def methodkey(self: Unused, /, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... def typedkey(*args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... -def typedmethodkey(self: Unused, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... +def typedmethodkey(self: Unused, /, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ... From ea350e1aebe3753bd6b3ff2eafe75dd3604fad0e Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 23 Sep 2025 14:10:06 +0300 Subject: [PATCH 3/4] Add precise type annotations for cached decorator and helpers --- stubs/cachetools/cachetools/__init__.pyi | 51 +++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/stubs/cachetools/cachetools/__init__.pyi b/stubs/cachetools/cachetools/__init__.pyi index 47db5e279956..bd3e14d39abc 100644 --- a/stubs/cachetools/cachetools/__init__.pyi +++ b/stubs/cachetools/cachetools/__init__.pyi @@ -2,15 +2,17 @@ from _typeshed import IdentityFunction, Unused from collections.abc import Callable, Iterator, MutableMapping, Sequence from contextlib import AbstractContextManager from threading import Condition -from typing import Any, TypeVar, overload +from typing import Any, Generic, Literal, NamedTuple, TypeVar, overload from typing_extensions import Self, deprecated __all__ = ("Cache", "FIFOCache", "LFUCache", "LRUCache", "RRCache", "TLRUCache", "TTLCache", "cached", "cachedmethod") + __version__: str _KT = TypeVar("_KT") _VT = TypeVar("_VT") _T = TypeVar("_T") +_R = TypeVar("_R") class Cache(MutableMapping[_KT, _VT]): @overload @@ -99,22 +101,61 @@ class TLRUCache(_TimedCache[_KT, _VT]): def ttu(self) -> Callable[[_KT, _VT, float], float]: ... def expire(self, time: float | None = None) -> list[tuple[_KT, _VT]]: ... +class _CacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int | None + currsize: int + +class _cached_wrapper(Generic[_R]): + __wrapped__: Callable[..., _R] + def __call__(self, /, *args: Any, **kwargs: Any) -> _R: ... + +class _cached_wrapper_info(_cached_wrapper[_R]): + def cache_info(self) -> _CacheInfo: ... + def cache_clear(self) -> None: ... + @overload def cached( cache: MutableMapping[_KT, Any] | None, key: Callable[..., _KT] = ..., lock: AbstractContextManager[Any] | None = None, condition: Condition | None = None, - info: bool = False, -) -> IdentityFunction: ... + *, + info: Literal[True], +) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ... +@overload +def cached( + cache: MutableMapping[_KT, Any] | None, + key: Callable[..., _KT] = ..., + lock: AbstractContextManager[Any] | None = None, + condition: Condition | None = None, + *, + info: Literal[False] = False, +) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ... +@overload +def cached( # без параметра info (по умолчанию False) + cache: MutableMapping[_KT, Any] | None, + key: Callable[..., _KT] = ..., + lock: AbstractContextManager[Any] | None = None, + condition: Condition | None = None, +) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ... @overload @deprecated("Passing `info` as positional parameter is deprecated.") def cached( cache: MutableMapping[_KT, Any] | None, key: Callable[..., _KT] = ..., lock: AbstractContextManager[Any] | None = None, - condition: bool | None = None, -) -> IdentityFunction: ... + condition: Literal[True] = ..., +) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ... +@overload +@deprecated("Passing `info` as positional parameter is deprecated.") +def cached( + cache: MutableMapping[_KT, Any] | None, + key: Callable[..., _KT] = ..., + lock: AbstractContextManager[Any] | None = None, + condition: Literal[False] | None = ..., +) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ... def cachedmethod( cache: Callable[[Any], MutableMapping[_KT, Any] | None], key: Callable[..., _KT] = ..., From 80cf6f8ea6a6e8d5822d2e4a6e3c269e210cd4f8 Mon Sep 17 00:00:00 2001 From: Shamil Date: Tue, 23 Sep 2025 14:30:14 +0300 Subject: [PATCH 4/4] Make info parameter in cached overloads keyword-optional --- stubs/cachetools/cachetools/__init__.pyi | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/stubs/cachetools/cachetools/__init__.pyi b/stubs/cachetools/cachetools/__init__.pyi index bd3e14d39abc..8adfb544c640 100644 --- a/stubs/cachetools/cachetools/__init__.pyi +++ b/stubs/cachetools/cachetools/__init__.pyi @@ -6,7 +6,6 @@ from typing import Any, Generic, Literal, NamedTuple, TypeVar, overload from typing_extensions import Self, deprecated __all__ = ("Cache", "FIFOCache", "LFUCache", "LRUCache", "RRCache", "TLRUCache", "TTLCache", "cached", "cachedmethod") - __version__: str _KT = TypeVar("_KT") @@ -121,8 +120,7 @@ def cached( key: Callable[..., _KT] = ..., lock: AbstractContextManager[Any] | None = None, condition: Condition | None = None, - *, - info: Literal[True], + info: Literal[True] = ..., ) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ... @overload def cached( @@ -130,15 +128,7 @@ def cached( key: Callable[..., _KT] = ..., lock: AbstractContextManager[Any] | None = None, condition: Condition | None = None, - *, - info: Literal[False] = False, -) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ... -@overload -def cached( # без параметра info (по умолчанию False) - cache: MutableMapping[_KT, Any] | None, - key: Callable[..., _KT] = ..., - lock: AbstractContextManager[Any] | None = None, - condition: Condition | None = None, + info: Literal[False] = ..., ) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ... @overload @deprecated("Passing `info` as positional parameter is deprecated.")