10
10
from __future__ import annotations
11
11
12
12
import hashlib
13
+ import inspect
13
14
import logging
14
15
import os
15
16
import pickle # noqa: S403
@@ -94,7 +95,9 @@ def decorator(func: Callable[P, T]) -> Callable[P, T]:
94
95
if "NO_CACHE" in os .environ :
95
96
_warn_once ("AmpForm cache disabled by NO_CACHE environment variable." )
96
97
return func
98
+ python_version = f"{ sys .version_info .major } .{ sys .version_info .minor } "
97
99
function_identifier = f"{ func .__module__ } .{ func .__name__ } "
100
+ src = inspect .getsource (func )
98
101
dependency_identifiers = _get_dependency_identifiers (func , dependencies or [])
99
102
nonlocal function_name
100
103
if function_name is None :
@@ -103,16 +106,21 @@ def decorator(func: Callable[P, T]) -> Callable[P, T]:
103
106
@wraps (func )
104
107
def wrapped_function (* args : P .args , ** kwargs : P .kwargs ) -> T :
105
108
hashable_object = make_hashable (
106
- function_identifier , * dependency_identifiers , args , kwargs
109
+ function_identifier ,
110
+ src ,
111
+ python_version ,
112
+ * dependency_identifiers ,
113
+ args ,
114
+ kwargs ,
107
115
)
108
116
h = get_readable_hash (hashable_object )
109
117
cache_file = _get_cache_dir () / h [:2 ] / h [2 :]
110
118
if cache_file .exists ():
111
119
with open (cache_file , "rb" ) as f :
112
120
return load_function (f )
113
- result = func (* args , ** kwargs )
114
121
msg = f"No cache file { cache_file } , performing { function_name } ()..."
115
122
_LOGGER .warning (msg )
123
+ result = func (* args , ** kwargs )
116
124
cache_file .parent .mkdir (exist_ok = True , parents = True )
117
125
with open (cache_file , "wb" ) as f :
118
126
dump_function (result , f )
@@ -219,16 +227,27 @@ def to_bytes(obj) -> bytes:
219
227
220
228
221
229
def make_hashable (* args ) -> Hashable :
230
+ """Make a hashable object from any Python object.
231
+
232
+ >>> make_hashable("a", 1, {"b": 2}, {3, 4})
233
+ ('a', 1, frozendict.frozendict({'b': 2}), frozenset({3, 4}))
234
+ >>> make_hashable({"a": {"sub-key": {1, 2, 3}, "b": [4, 5]}})
235
+ frozendict.frozendict({'a': frozendict.frozendict({'sub-key': frozenset({1, 2, 3}), 'b': (4, 5)})})
236
+ >>> make_hashable("already-hashable")
237
+ 'already-hashable'
238
+ """
239
+ if len (args ) == 1 :
240
+ return _make_hashable_impl (args [0 ])
222
241
return tuple (_make_hashable_impl (x ) for x in args )
223
242
224
243
225
244
def _make_hashable_impl (obj ) -> Hashable :
226
245
if isinstance (obj , abc .Mapping ):
227
- return frozendict (obj )
246
+ return frozendict ({ k : _make_hashable_impl ( v ) for k , v in obj . items ()} )
228
247
if isinstance (obj , str ):
229
248
return obj
230
249
if isinstance (obj , abc .Iterable ):
231
- hashable_items = (make_hashable (x ) for x in obj )
250
+ hashable_items = (_make_hashable_impl (x ) for x in obj )
232
251
if isinstance (obj , abc .Sequence ):
233
252
return tuple (hashable_items )
234
253
if isinstance (obj , set ):
0 commit comments