2020from typing import Any , ParamSpec , TypeVar
2121
2222from ._cpython_stuff import make_key
23+ from ._lru import LRU
2324
2425__all__ = ("corocache" , "lrucorocache" )
2526
2627
2728P = ParamSpec ("P" )
28- T = TypeVar ("T " )
29+ R = TypeVar ("R " )
2930
3031
31- type CoroFunc [** P , T ] = Callable [P , Coroutine [Any , Any , T ]]
32- type CoroLike [** P , T ] = Callable [P , Awaitable [T ]]
33-
34-
35- class LRU [K , V ]:
36- def __init__ (self , maxsize : int , / ):
37- self .cache : dict [K , V ] = {}
38- self .maxsize = maxsize
39-
40- def get (self , key : K , default : T , / ) -> V | T :
41- if key not in self .cache :
42- return default
43- self .cache [key ] = self .cache .pop (key )
44- return self .cache [key ]
45-
46- def __getitem__ (self , key : K , / ) -> V :
47- self .cache [key ] = self .cache .pop (key )
48- return self .cache [key ]
49-
50- def __setitem__ (self , key : K , value : V , / ):
51- self .cache [key ] = value
52- if len (self .cache ) > self .maxsize :
53- self .cache .pop (next (iter (self .cache )))
54-
55- def remove (self , key : K , / ) -> None :
56- self .cache .pop (key , None )
32+ type CoroFunc [** P , R ] = Callable [P , Coroutine [Any , Any , R ]]
33+ type CoroLike [** P , R ] = Callable [P , Awaitable [R ]]
5734
5835
5936def corocache (
6037 ttl : float | None = None ,
61- ) -> Callable [[CoroLike [P , T ]], CoroFunc [P , T ]]:
38+ ) -> Callable [[CoroLike [P , R ]], CoroFunc [P , R ]]:
6239 """Decorator to cache coroutine functions.
6340
64- This is less powerful than the version in task_cache.py but may work better for
65- some cases where typing of libraries this interacts with is too restrictive.
41+ This is less powerful than the version in task_cache.py but may work better
42+ for some cases where typing of libraries this interacts with is too restrictive.
6643
67- Note: This uses the args and kwargs of the original coroutine function as a cache key.
68- This includes instances (self) when wrapping methods.
69- Consider not wrapping instance methods, but what those methods call when feasible in cases where this may matter.
44+ Note: This uses the args and kwargs of the original coroutine function as a
45+ cache key. This includes instances (self) when wrapping methods.
46+ Consider not wrapping instance methods, but what those methods call when feasible
47+ in cases where this may matter.
7048
7149 The ordering of args and kwargs matters."""
7250
73- def wrapper (coro : CoroLike [P , T ]) -> CoroFunc [P , T ]:
74- internal_cache : dict [Hashable , asyncio .Future [T ]] = {}
51+ def wrapper (coro : CoroLike [P , R ]) -> CoroFunc [P , R ]:
52+ internal_cache : dict [Hashable , asyncio .Future [R ]] = {}
7553
76- async def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> T :
54+ async def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> R :
7755 key = make_key (args , kwargs )
7856 try :
7957 return await internal_cache [key ]
8058 except KeyError :
81- internal_cache [key ] = fut = asyncio .ensure_future (coro (* args , ** kwargs ))
59+ internal_cache [key ] = fut = asyncio .ensure_future (
60+ coro (* args , ** kwargs )
61+ )
8262 if ttl is not None :
8363 # This results in internal_cache.pop(key, fut) later
8464 # while avoiding a late binding issue with a lambda instead
@@ -96,37 +76,45 @@ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
9676 return wrapper
9777
9878
99- def _lru_evict (ttl : float , cache : LRU [Hashable , Any ], key : Hashable , _ignored_fut : object ) -> None :
79+ def _lru_evict (
80+ ttl : float , cache : LRU [Hashable , Any ], key : Hashable , _ignored_fut : object
81+ ) -> None :
10082 asyncio .get_running_loop ().call_later (ttl , cache .remove , key )
10183
10284
103- def lrucorocache (ttl : float | None = None , maxsize : int = 1024 ) -> Callable [[CoroLike [P , T ]], CoroFunc [P , T ]]:
85+ def lrucorocache (
86+ ttl : float | None = None , maxsize : int = 1024
87+ ) -> Callable [[CoroLike [P , R ]], CoroFunc [P , R ]]:
10488 """Decorator to cache coroutine functions.
10589
106- This is less powerful than the version in task_cache.py but may work better for
107- some cases where typing of libraries this interacts with is too restrictive.
90+ This is less powerful than the version in task_cache.py but may work better
91+ for some cases where typing of libraries this interacts with is too restrictive.
10892
109- Note: This uses the args and kwargs of the original coroutine function as a cache key.
110- This includes instances (self) when wrapping methods.
111- Consider not wrapping instance methods, but what those methods call when feasible in cases where this may matter.
93+ Note: This uses the args and kwargs of the original coroutine function as a
94+ cache key. This includes instances (self) when wrapping methods.
95+ Consider not wrapping instance methods, but what those methods call when feasible
96+ in cases where this may matter.
11297
11398 The ordering of args and kwargs matters.
11499
115- futs are evicted by LRU and ttl.
116- """
100+ cached results are evicted by LRU and ttl."""
117101
118- def wrapper (coro : CoroLike [P , T ]) -> CoroFunc [P , T ]:
119- internal_cache : LRU [Hashable , asyncio .Future [T ]] = LRU (maxsize )
102+ def wrapper (coro : CoroLike [P , R ]) -> CoroFunc [P , R ]:
103+ internal_cache : LRU [Hashable , asyncio .Future [R ]] = LRU (maxsize )
120104
121105 @wraps (coro )
122- async def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> T :
106+ async def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> R :
123107 key = make_key (args , kwargs )
124108 try :
125109 return await internal_cache [key ]
126110 except KeyError :
127- internal_cache [key ] = fut = asyncio .ensure_future (coro (* args , ** kwargs ))
111+ internal_cache [key ] = fut = asyncio .ensure_future (
112+ coro (* args , ** kwargs )
113+ )
128114 if ttl is not None :
129- fut .add_done_callback (partial (_lru_evict , ttl , internal_cache , key ))
115+ fut .add_done_callback (
116+ partial (_lru_evict , ttl , internal_cache , key )
117+ )
130118 return await fut
131119
132120 return wrapped
0 commit comments