Skip to content

Commit fa0e5fc

Browse files
authored
cachetools: precise typing for decorators and cached(); expose cache_info/cache_clear and fix keys signatures (#14770)
1 parent 4c4f999 commit fa0e5fc

File tree

4 files changed

+187
-17
lines changed

4 files changed

+187
-17
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Hashable
4+
from typing import Any
5+
from typing_extensions import assert_type
6+
7+
from cachetools import LRUCache, cached, keys as cachekeys
8+
from cachetools.func import fifo_cache, lfu_cache, lru_cache, rr_cache, ttl_cache
9+
10+
# Tests for cachetools.cached
11+
12+
# Explicitly parameterize the cache to avoid Unknown types
13+
cache_inst: LRUCache[int, int] = LRUCache(maxsize=128)
14+
15+
16+
@cached(cache_inst)
17+
def check_cached(x: int) -> int:
18+
return x * 2
19+
20+
21+
assert_type(check_cached(3), int)
22+
# Methods cache_info/cache_clear are only present when info=True; do not access them here.
23+
24+
25+
@cached(cache_inst, info=True)
26+
def check_cached_with_info(x: int) -> int:
27+
return x + 1
28+
29+
30+
assert_type(check_cached_with_info(4), int)
31+
assert_type(check_cached_with_info.cache_info().misses, int)
32+
check_cached_with_info.cache_clear()
33+
34+
35+
# Tests for cachetools.func decorators
36+
37+
38+
@lru_cache
39+
def lru_noparens(x: int) -> int:
40+
return x * 2
41+
42+
43+
@lru_cache(maxsize=32)
44+
def lru_with_maxsize(x: int) -> int:
45+
return x * 3
46+
47+
48+
assert_type(lru_noparens(3), int)
49+
assert_type(lru_with_maxsize(3), int)
50+
assert_type(lru_noparens.cache_info().hits, int)
51+
assert_type(lru_with_maxsize.cache_info().misses, int)
52+
assert_type(lru_with_maxsize.cache_parameters(), dict[str, Any])
53+
lru_with_maxsize.cache_clear()
54+
55+
56+
@fifo_cache
57+
def fifo_func(x: int) -> int:
58+
return x
59+
60+
61+
@lfu_cache
62+
def lfu_func(x: int) -> int:
63+
return x
64+
65+
66+
@rr_cache
67+
def rr_func(x: int) -> int:
68+
return x
69+
70+
71+
@ttl_cache
72+
def ttl_func(x: int) -> int:
73+
return x
74+
75+
76+
assert_type(fifo_func(1), int)
77+
assert_type(lfu_func(1), int)
78+
assert_type(rr_func(1), int)
79+
assert_type(ttl_func(1), int)
80+
assert_type(fifo_func.cache_info().currsize, int)
81+
assert_type(lfu_func.cache_parameters(), dict[str, Any])
82+
83+
84+
# Tests for cachetools.keys
85+
86+
k1 = cachekeys.hashkey(1, "a")
87+
assert_type(k1, tuple[Hashable, ...])
88+
89+
90+
class C:
91+
def method(self, a: int) -> int:
92+
return a
93+
94+
95+
inst = C()
96+
97+
k2 = cachekeys.methodkey(inst, 5)
98+
assert_type(k2, tuple[Hashable, ...])
99+
100+
k3 = cachekeys.typedkey(1, "x")
101+
assert_type(k3, tuple[Hashable, ...])
102+
103+
k4 = cachekeys.typedmethodkey(inst, 2)
104+
assert_type(k4, tuple[Hashable, ...])

stubs/cachetools/cachetools/__init__.pyi

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from _typeshed import IdentityFunction, Unused
22
from collections.abc import Callable, Iterator, MutableMapping, Sequence
33
from contextlib import AbstractContextManager
44
from threading import Condition
5-
from typing import Any, TypeVar, overload
5+
from typing import Any, Generic, Literal, NamedTuple, TypeVar, overload
66
from typing_extensions import Self, deprecated
77

88
__all__ = ("Cache", "FIFOCache", "LFUCache", "LRUCache", "RRCache", "TLRUCache", "TTLCache", "cached", "cachedmethod")
@@ -11,6 +11,7 @@ __version__: str
1111
_KT = TypeVar("_KT")
1212
_VT = TypeVar("_VT")
1313
_T = TypeVar("_T")
14+
_R = TypeVar("_R")
1415

1516
class Cache(MutableMapping[_KT, _VT]):
1617
@overload
@@ -99,22 +100,52 @@ class TLRUCache(_TimedCache[_KT, _VT]):
99100
def ttu(self) -> Callable[[_KT, _VT, float], float]: ...
100101
def expire(self, time: float | None = None) -> list[tuple[_KT, _VT]]: ...
101102

103+
class _CacheInfo(NamedTuple):
104+
hits: int
105+
misses: int
106+
maxsize: int | None
107+
currsize: int
108+
109+
class _cached_wrapper(Generic[_R]):
110+
__wrapped__: Callable[..., _R]
111+
def __call__(self, /, *args: Any, **kwargs: Any) -> _R: ...
112+
113+
class _cached_wrapper_info(_cached_wrapper[_R]):
114+
def cache_info(self) -> _CacheInfo: ...
115+
def cache_clear(self) -> None: ...
116+
102117
@overload
103118
def cached(
104119
cache: MutableMapping[_KT, Any] | None,
105120
key: Callable[..., _KT] = ...,
106121
lock: AbstractContextManager[Any] | None = None,
107122
condition: Condition | None = None,
108-
info: bool = False,
109-
) -> IdentityFunction: ...
123+
info: Literal[True] = ...,
124+
) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ...
125+
@overload
126+
def cached(
127+
cache: MutableMapping[_KT, Any] | None,
128+
key: Callable[..., _KT] = ...,
129+
lock: AbstractContextManager[Any] | None = None,
130+
condition: Condition | None = None,
131+
info: Literal[False] = ...,
132+
) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ...
110133
@overload
111134
@deprecated("Passing `info` as positional parameter is deprecated.")
112135
def cached(
113136
cache: MutableMapping[_KT, Any] | None,
114137
key: Callable[..., _KT] = ...,
115138
lock: AbstractContextManager[Any] | None = None,
116-
condition: bool | None = None,
117-
) -> IdentityFunction: ...
139+
condition: Literal[True] = ...,
140+
) -> Callable[[Callable[..., _R]], _cached_wrapper_info[_R]]: ...
141+
@overload
142+
@deprecated("Passing `info` as positional parameter is deprecated.")
143+
def cached(
144+
cache: MutableMapping[_KT, Any] | None,
145+
key: Callable[..., _KT] = ...,
146+
lock: AbstractContextManager[Any] | None = None,
147+
condition: Literal[False] | None = ...,
148+
) -> Callable[[Callable[..., _R]], _cached_wrapper[_R]]: ...
118149
def cachedmethod(
119150
cache: Callable[[Any], MutableMapping[_KT, Any] | None],
120151
key: Callable[..., _KT] = ...,
Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,51 @@
1-
from _typeshed import IdentityFunction
21
from collections.abc import Callable, Sequence
3-
from typing import TypeVar
2+
from typing import Any, Final, Generic, NamedTuple, TypeVar, overload
3+
4+
__all__: Final = ("fifo_cache", "lfu_cache", "lru_cache", "rr_cache", "ttl_cache")
45

5-
__all__ = ("fifo_cache", "lfu_cache", "lru_cache", "rr_cache", "ttl_cache")
66
_T = TypeVar("_T")
7+
_R = TypeVar("_R")
8+
9+
class _CacheInfo(NamedTuple):
10+
hits: int
11+
misses: int
12+
maxsize: int | None
13+
currsize: int
14+
15+
class _cachetools_cache_wrapper(Generic[_R]):
16+
__wrapped__: Callable[..., _R]
17+
def __call__(self, /, *args: Any, **kwargs: Any) -> _R: ...
18+
def cache_info(self) -> _CacheInfo: ...
19+
def cache_clear(self) -> None: ...
20+
def cache_parameters(self) -> dict[str, Any]: ...
721

8-
def fifo_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ...
9-
def lfu_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ...
10-
def lru_cache(maxsize: float | None = 128, typed: bool = False) -> IdentityFunction: ...
22+
@overload
23+
def fifo_cache(
24+
maxsize: int | None = 128, typed: bool = False
25+
) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ...
26+
@overload
27+
def fifo_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ...
28+
@overload
29+
def lfu_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ...
30+
@overload
31+
def lfu_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ...
32+
@overload
33+
def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ...
34+
@overload
35+
def lru_cache(maxsize: Callable[..., _R], typed: bool = False) -> _cachetools_cache_wrapper[_R]: ...
36+
@overload
1137
def rr_cache(
12-
maxsize: float | None = 128, choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False
13-
) -> IdentityFunction: ...
38+
maxsize: int | None = 128, choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False
39+
) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ...
40+
@overload
41+
def rr_cache(
42+
maxsize: Callable[..., _R], choice: Callable[[Sequence[_T]], _T] | None = ..., typed: bool = False
43+
) -> _cachetools_cache_wrapper[_R]: ...
44+
@overload
45+
def ttl_cache(
46+
maxsize: int | None = 128, ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False
47+
) -> Callable[[Callable[..., _R]], _cachetools_cache_wrapper[_R]]: ...
48+
@overload
1449
def ttl_cache(
15-
maxsize: float | None = 128, ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False
16-
) -> IdentityFunction: ...
50+
maxsize: Callable[..., _R], ttl: float = 600, timer: Callable[[], float] = ..., typed: bool = False
51+
) -> _cachetools_cache_wrapper[_R]: ...

stubs/cachetools/cachetools/keys.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ from collections.abc import Hashable
44
__all__ = ("hashkey", "methodkey", "typedkey", "typedmethodkey")
55

66
def hashkey(*args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...
7-
def methodkey(self: Unused, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...
7+
def methodkey(self: Unused, /, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...
88
def typedkey(*args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...
9-
def typedmethodkey(self: Unused, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...
9+
def typedmethodkey(self: Unused, /, *args: Hashable, **kwargs: Hashable) -> tuple[Hashable, ...]: ...

0 commit comments

Comments
 (0)