diff --git a/diskcache/core.py b/diskcache/core.py index 7a3d23b..d84cfbc 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -401,9 +401,7 @@ def args_to_key(base, args, kwargs, typed, ignore): if kwargs: kwargs = {key: val for key, val in kwargs.items() if key not in ignore} sorted_items = sorted(kwargs.items()) - - for item in sorted_items: - key += item + key += tuple(sorted_items) if typed: key += tuple(type(arg) for arg in args) @@ -414,6 +412,53 @@ def args_to_key(base, args, kwargs, typed, ignore): return key +def key_to_args(key): + """Get the args and kwargs from a cache key's tail (i.e. everything + following the base of the key). + + :param tuple key: cache key tuple + :return: tuple of args, kwargs + :rtype: tuple pair of tuple and dict + + """ + kwargs_ranges = [] + type_range = None + last_kwargs_start_idx = last_typed_start_idx = None + for idx, val in enumerate(key): + if last_kwargs_start_idx is not None and not isinstance(val, tuple): + kwargs_ranges.append((last_kwargs_start_idx, idx)) + last_kwargs_start_idx = None + if last_kwargs_start_idx is None and val is None: + last_kwargs_start_idx = idx + 1 + + val_is_type = isinstance(val, type) + if last_typed_start_idx is not None and not val_is_type: + last_typed_start_idx = None + if last_typed_start_idx is None and val_is_type: + last_typed_start_idx = idx + + if last_kwargs_start_idx is not None: + kwargs_ranges.append((last_kwargs_start_idx, len(key))) + elif last_typed_start_idx is not None: + type_range = (last_typed_start_idx, len(key)) + + if type_range is not None: + num_args_kwargs = type_range[1] - type_range[0] + kwargs_range = [ + (start, end) + for start, end in kwargs_ranges + if (start - 1) + (end - start) == num_args_kwargs + ][-1] + else: + kwargs_range = kwargs_ranges[-1] + + start, end = kwargs_range + args = key[: start - 1] + kwargs = key[start:end] + + return (args, dict(kwargs)) + + class Cache: """Disk and file backed cache.""" diff --git a/tests/test_core.py b/tests/test_core.py index 788afef..af0feca 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1405,3 +1405,27 @@ def test(*args, **kwargs): assert len(cache) == 3 for key in cache: assert cache[key] == 6 + + +@pytest.mark.parametrize('typed', [False, True]) +def test_memoize_args_to_key_invertible(cache, typed): + @cache.memoize(typed=typed) + def test(*args, **kwargs): + return (args, kwargs) + + cache.clear() + assert test() + assert test(0) + assert test(a=0) + assert test(0, 1, 2, a=0, b=1, c=2) + assert test(None, 'fake kwarg start') + assert test(None, 'fake kwarg start', a=0) + assert test(bool, bytes, float, int, str) + for key in cache: + args, kwargs = dc.core.key_to_args(key[1:]) + assert (args, kwargs) == cache[key] + if typed: + expected_key_len = 2 + (2 if typed else 1) * ( + len(args) + len(kwargs) + ) + assert expected_key_len == len(key)