1
1
import logging
2
+ from inspect import Signature , signature
3
+ from typing import Tuple # deprecated, but required for Python 3.8 and below
2
4
3
5
try :
4
6
from _pylibmc import Error as CacheSetError
15
17
logger = logging .getLogger (__name__ )
16
18
17
19
20
+ def _get_function_cache_keys (func_name : str , func_signature : Signature , args : tuple , kwargs : dict ) -> Tuple [str , str ]:
21
+ """
22
+ Generate hashed and non-hashed function cache keys, ensuring that args and kwargs are correctly bound to function.
23
+
24
+ :param func_name: The fully specified name of the function to be cached.
25
+ :param func_signature: The signature of the function to be cached.
26
+ :param args: The positional arguments passed to the function.
27
+ :param kwargs: The keyword arguments passed to the function.
28
+
29
+ :return: A tuple containing the hashed cache key and the non-hashed cache key.
30
+ """
31
+ bound_arguments = func_signature .bind (* args , ** kwargs )
32
+ bound_arguments .apply_defaults ()
33
+ cache_key_string = utils .get_function_cache_key (func_name , bound_arguments .args , bound_arguments .kwargs )
34
+ cache_key_hashed = utils .get_hashed_cache_key (cache_key_string )
35
+ return cache_key_hashed , cache_key_string
36
+
37
+
18
38
def cached (timeout ):
19
39
def _cached (func ):
20
40
func_name = utils .get_function_name (func )
41
+ func_signature = signature (func )
21
42
22
43
@wraps (func )
23
44
def wrapper (* args , ** kwargs ):
24
- function_cache_key = utils .get_function_cache_key (func_name , args , kwargs )
25
- cache_key = utils .get_hashed_cache_key (function_cache_key )
45
+ cache_key_hashed , cache_key_string = _get_function_cache_keys (func_name , func_signature , args , kwargs )
26
46
27
47
# We need to determine whether the object exists in the cache, and since we may have stored a literal value
28
48
# None, use a sentinel object as the default
29
49
sentinel = object ()
30
50
try :
31
- value = cache .get (cache_key , sentinel )
51
+ value = cache .get (cache_key_hashed , sentinel )
32
52
except Exception :
33
53
logger .warning (
34
- f'Error retrieving value from Cache for Key: { function_cache_key } ' ,
54
+ f'Error retrieving value from Cache for Key: { cache_key_string } ' ,
35
55
exc_info = True ,
36
56
)
37
57
value = sentinel
@@ -41,7 +61,7 @@ def wrapper(*args, **kwargs):
41
61
if value is None :
42
62
logger .warning (
43
63
'None cache value found for cache key: {}, function cache key: {}, value: {}' .format (
44
- cache_key , function_cache_key , value
64
+ cache_key_hashed , cache_key_string , value
45
65
)
46
66
)
47
67
@@ -51,10 +71,10 @@ def wrapper(*args, **kwargs):
51
71
# But if it fails on an error from the underlying
52
72
# cache system, handle it.
53
73
try :
54
- cache .set (cache_key , value , timeout )
74
+ cache .set (cache_key_hashed , value , timeout )
55
75
except CacheSetError :
56
76
logger .warning (
57
- f'Error saving value to Cache for Key: { function_cache_key } ' ,
77
+ f'Error saving value to Cache for Key: { cache_key_string } ' ,
58
78
exc_info = True ,
59
79
)
60
80
@@ -68,8 +88,7 @@ def invalidate(*args, **kwargs):
68
88
:param kwargs: The kwargs passed into the original function.
69
89
:rtype: None
70
90
"""
71
- function_cache_key = utils .get_function_cache_key (func_name , args , kwargs )
72
- cache_key = utils .get_hashed_cache_key (function_cache_key )
91
+ cache_key , _ = _get_function_cache_keys (func_name , func_signature , args , kwargs )
73
92
cache .delete (cache_key )
74
93
75
94
wrapper .invalidate = invalidate
@@ -81,21 +100,23 @@ def invalidate(*args, **kwargs):
81
100
def cached_class_method (timeout ):
82
101
def _cached (func ):
83
102
func_name = utils .get_function_name (func )
103
+ func_signature = signature (func )
84
104
85
105
@wraps (func )
86
106
def wrapper (* args , ** kwargs ):
87
- # skip the first arg because it will be the class itself
88
- function_cache_key = utils .get_function_cache_key (func_name , args [1 :], kwargs )
89
- cache_key = utils .get_hashed_cache_key (function_cache_key )
90
-
107
+ # replace the first arg for caching purposes because it will be the class itself
108
+ cls_adjusted_args = (None , * args [1 :])
109
+ cache_key_hashed , cache_key_string = _get_function_cache_keys (
110
+ func_name , func_signature , cls_adjusted_args , kwargs
111
+ )
91
112
# We need to determine whether the object exists in the cache, and since we may have stored a literal value
92
113
# None, use a sentinel object as the default
93
114
sentinel = object ()
94
115
try :
95
- value = cache .get (cache_key , sentinel )
116
+ value = cache .get (cache_key_hashed , sentinel )
96
117
except Exception :
97
118
logger .warning (
98
- f'Error retrieving value from Cache for Key: { function_cache_key } ' ,
119
+ f'Error retrieving value from Cache for Key: { cache_key_string } ' ,
99
120
exc_info = True ,
100
121
)
101
122
value = sentinel
@@ -105,7 +126,7 @@ def wrapper(*args, **kwargs):
105
126
if value is None :
106
127
logger .warning (
107
128
'None cache value found for cache key: {}, function cache key: {}, value: {}' .format (
108
- cache_key , function_cache_key , value
129
+ cache_key_hashed , cache_key_string , value
109
130
)
110
131
)
111
132
@@ -115,10 +136,10 @@ def wrapper(*args, **kwargs):
115
136
# But if it fails on an error from the underlying
116
137
# cache system, handle it.
117
138
try :
118
- cache .set (cache_key , value , timeout )
139
+ cache .set (cache_key_hashed , value , timeout )
119
140
except CacheSetError :
120
141
logger .warning (
121
- f'Error saving value to Cache for Key: { function_cache_key } ' ,
142
+ f'Error saving value to Cache for Key: { cache_key_string } ' ,
122
143
exc_info = True ,
123
144
)
124
145
@@ -127,13 +148,16 @@ def wrapper(*args, **kwargs):
127
148
def invalidate (* args , ** kwargs ):
128
149
"""
129
150
A method to invalidate a result from the cache.
130
- :param args: The args passed into the original function. This includes `self` for instance methods, and
151
+ :param args: The args passed into the original function. This excludes `self` for instance methods, and
131
152
`cls` for class methods.
132
153
:param kwargs: The kwargs passed into the original function.
133
154
:rtype: None
134
155
"""
135
- function_cache_key = utils .get_function_cache_key (func_name , args , kwargs )
136
- cache_key = utils .get_hashed_cache_key (function_cache_key )
156
+ # note: args does not include the class itself, but because it *is* passed to wrapper() and we replaced
157
+ # it with None for consistent cache behavior with subclasses, we need to account for it here by updating
158
+ # args to include None
159
+ cls_adjusted_args = (None , * args )
160
+ cache_key , _ = _get_function_cache_keys (func_name , func_signature , cls_adjusted_args , kwargs )
137
161
cache .delete (cache_key )
138
162
139
163
wrapper .invalidate = invalidate
@@ -171,16 +195,16 @@ def __get__(self, obj, objtype):
171
195
return fn
172
196
173
197
def __call__ (self , * args , ** kwargs ):
174
- cache_key , function_cache_key = self .create_cache_key (* args , ** kwargs )
198
+ cache_key_hashed , cache_key_string = self .create_cache_key (* args , ** kwargs )
175
199
176
200
# We need to determine whether the object exists in the cache, and since we may have stored a literal value
177
201
# None, use a sentinel object as the default
178
202
sentinel = object ()
179
203
try :
180
- value = cache .get (cache_key , sentinel )
204
+ value = cache .get (cache_key_hashed , sentinel )
181
205
except Exception :
182
206
logger .warning (
183
- f'Error retrieving value from Cache for Key: { function_cache_key } ' ,
207
+ f'Error retrieving value from Cache for Key: { cache_key_string } ' ,
184
208
exc_info = True ,
185
209
)
186
210
value = sentinel
@@ -190,7 +214,7 @@ def __call__(self, *args, **kwargs):
190
214
if value is None :
191
215
logger .warning (
192
216
'None cache value found for cache key: {}, function cache key: {}, value: {}' .format (
193
- cache_key , function_cache_key , value
217
+ cache_key_hashed , cache_key_string , value
194
218
)
195
219
)
196
220
@@ -200,10 +224,10 @@ def __call__(self, *args, **kwargs):
200
224
# But if it fails on an error from the underlying
201
225
# cache system, handle it.
202
226
try :
203
- cache .set (cache_key , value , timeout )
227
+ cache .set (cache_key_hashed , value , timeout )
204
228
except CacheSetError :
205
229
logger .warning (
206
- f'Error saving value to Cache for Key: { function_cache_key } ' ,
230
+ f'Error saving value to Cache for Key: { cache_key_string } ' ,
207
231
exc_info = True ,
208
232
)
209
233
@@ -217,14 +241,14 @@ def _invalidate(self, *args, **kwargs):
217
241
:param kwargs: The kwargs passed into the original function.
218
242
:rtype: None
219
243
"""
220
- cache_key , _ = self .create_cache_key (* args , ** kwargs )
221
- cache .delete (cache_key )
244
+ cache_key_hashed , _ = self .create_cache_key (* args , ** kwargs )
245
+ cache .delete (cache_key_hashed )
222
246
223
247
def create_cache_key (self , * args , ** kwargs ):
224
248
# Need to include the first arg (self) in the cache key
225
249
func_name = utils .get_function_name (self .func )
226
- function_cache_key = utils . get_function_cache_key ( func_name , args , kwargs )
227
- cache_key = utils . get_hashed_cache_key ( function_cache_key )
228
- return cache_key , function_cache_key
250
+ func_signature = signature ( self . func )
251
+ cache_key_hashed , cache_key_string = _get_function_cache_keys ( func_name , func_signature , args , kwargs )
252
+ return cache_key_hashed , cache_key_string
229
253
230
254
return wrapper
0 commit comments