3131type TaskFunc [** P , R ] = CoroFunc [P , R ] | Callable [P , asyncio .Task [R ]]
3232type TaskCoroFunc [** P , R ] = CoroFunc [P , R ] | TaskFunc [P , R ]
3333
34+ type _CT_RET = tuple [tuple [t .Any , ...], dict [t .str , t .Any ]]
35+ type CacheTransformer = Callable [[tuple [t .Any , ...], dict [t .str , t .Any ]], _CT_RET ]
36+
3437type Deco [** P , R ] = Callable [[TaskCoroFunc [P , R ]], TaskFunc [P , R ]]
3538
3639# Non-annotation assignments for transformed functions
3740_WRAP_ASSIGN = ("__module__" , "__name__" , "__qualname__" , "__doc__" )
3841
3942
40- def taskcache [** P , R ](ttl : float | None = None ) -> Deco [P , R ]:
43+ def taskcache [** P , R ](
44+ ttl : float | None = None ,
45+ * ,
46+ cache_transform : CacheTransformer | None = None ,
47+ ) -> Deco [P , R ]:
4148 """Cache the results of the decorated coroutine.
4249
4350 Decorator to modify coroutine functions to instead act as functions
@@ -46,23 +53,32 @@ def taskcache[**P, R](ttl: float | None = None) -> Deco[P, R]:
4653 For general use, this leaves the end user API largely the same,
4754 while leveraging tasks to allow preemptive caching.
4855
49- Note: This uses the args and kwargs of the original coroutine function as a
50- cache key. This includes instances (self) when wrapping methods.
56+ Note: This by default uses the args and kwargs of the original coroutine
57+ function as a cache key. This includes instances (self) when wrapping methods.
5158 Consider not wrapping instance methods, but what those methods call when
52- feasible in cases where this may matter.
59+ feasible in cases where this may matter, or using a cache transform .
5360
5461 The ordering of args and kwargs matters.
5562
5663 Parameters
5764 ----------
5865 ttl: float | None
5966 The time to live in seconds for cached results. Defaults to None (forever)
67+ cache_transform: CacheTransformer | None
68+ An optional callable that transforms args and kwargs used
69+ as a cache key.
6070
6171 Returns
6272 -------
6373 A decorator which wraps coroutine-like objects in functions that return
6474 preemptively cached tasks.
6575 """
76+ if cache_transform is None :
77+ key_func = make_key
78+ else :
79+
80+ def key_func (args : tuple [t .Any , ...], kwds : dict [t .Any , t .Any ]) -> Hashable :
81+ return make_key (* cache_transform (args , kwds ))
6682
6783 def wrapper (coro : TaskCoroFunc [P , R ]) -> TaskFunc [P , R ]:
6884 internal_cache : dict [Hashable , asyncio .Task [R ]] = {}
@@ -74,7 +90,7 @@ def _internal_cache_evict(key: Hashable, _ignored_task: object) -> None:
7490
7591 @wraps (coro , assigned = _WRAP_ASSIGN )
7692 def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> asyncio .Task [R ]:
77- key = make_key (args , kwargs )
93+ key = key_func (args , kwargs )
7894 if (cached := internal_cache .get (key )) is not None :
7995 return cached
8096
@@ -100,7 +116,12 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> asyncio.Task[R]:
100116 return wrapper
101117
102118
103- def lrutaskcache [** P , R ](ttl : float | None = None , maxsize : int = 1024 ) -> Deco [P , R ]:
119+ def lrutaskcache [** P , R ](
120+ ttl : float | None = None ,
121+ maxsize : int = 1024 ,
122+ * ,
123+ cache_transform : CacheTransformer | None = None ,
124+ ) -> Deco [P , R ]:
104125 """Cache the results of the decorated coroutine.
105126
106127 Decorator to modify coroutine functions to instead act as functions
@@ -109,10 +130,10 @@ def lrutaskcache[**P, R](ttl: float | None = None, maxsize: int = 1024) -> Deco[
109130 For general use, this leaves the end user API largely the same,
110131 while leveraging tasks to allow preemptive caching.
111132
112- Note: This uses the args and kwargs of the original coroutine function as a
113- cache key. This includes instances (self) when wrapping methods.
133+ Note: This by default uses the args and kwargs of the original coroutine
134+ function as a cache key. This includes instances (self) when wrapping methods.
114135 Consider not wrapping instance methods, but what those methods call when
115- feasible in cases where this may matter.
136+ feasible in cases where this may matter, or using a cache transform .
116137
117138 The ordering of args and kwargs matters.
118139
@@ -127,12 +148,21 @@ def lrutaskcache[**P, R](ttl: float | None = None, maxsize: int = 1024) -> Deco[
127148 The maximum number of items to retain no matter if they have reached
128149 expiration by ttl or not.
129150 Items evicted by this policy are evicted by least recent use.
151+ cache_transform: CacheTransformer | None
152+ An optional callable that transforms args and kwargs used
153+ as a cache key.
130154
131155 Returns
132156 -------
133157 A decorator which wraps coroutine-like objects in functions that return
134158 preemptively cached tasks.
135159 """
160+ if cache_transform is None :
161+ key_func = make_key
162+ else :
163+
164+ def key_func (args : tuple [t .Any , ...], kwds : dict [t .Any , t .Any ]) -> Hashable :
165+ return make_key (* cache_transform (args , kwds ))
136166
137167 def wrapper (coro : TaskCoroFunc [P , R ]) -> TaskFunc [P , R ]:
138168 internal_cache : LRU [Hashable , asyncio .Task [R ]] = LRU (maxsize )
@@ -144,7 +174,7 @@ def _internal_cache_evict(key: Hashable, _ignored_task: object) -> None:
144174
145175 @wraps (coro , assigned = _WRAP_ASSIGN )
146176 def wrapped (* args : P .args , ** kwargs : P .kwargs ) -> asyncio .Task [R ]:
147- key = make_key (args , kwargs )
177+ key = key_func (args , kwargs )
148178 if (cached := internal_cache .get (key , None )) is not None :
149179 return cached
150180
0 commit comments