Cache Keys

Cache keys for the @cache_results decorator.

A cache key must be the same every time for equal arguments, in every process, and different for different ones. This module turns a decorated function and its arguments into such a key:

  • normalize() converts arguments into JSON-ready values. Dicts and lists stay JSON objects and arrays, sets are sorted and tagged, classes and dataclasses are tagged with their qualified names.

  • CacheKeyEncoder serializes the values that JSON doesn’t know, such as datetimes, Decimals and UUIDs, and refuses everything else. str() of an arbitrary object is not a safe key: two model rows with the same name would share one entry, and <Foo object at 0x...> changes in every process.

  • make_key() hashes the result into a key that names the function.

This module depends only on Django and the standard library. Projects add types by subclassing CacheKeyEncoder and naming the subclass in the KEY_ENCODER entry of the CACHE_RESULTS setting (see cache_results.conf).

exception cache_results.keys.CacheKeyArgumentError[source]

Bases: TypeError

An argument of a cached function can’t be part of a cache key.

class cache_results.keys.CacheKeyEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)[source]

Bases: DjangoJSONEncoder

Strict JSON encoder for cache keys.

It encodes what django.core.serializers.json.DjangoJSONEncoder knows (datetimes, dates, times, timedeltas, Decimals, UUIDs and lazy translation strings). Any other object raises CacheKeyArgumentError, so that the call runs uncached rather than be cached under an unsafe key. (Sets, classes and dataclasses never reach the encoder: normalize() has already converted them.)

To support another type, subclass it and override default(), then name the subclass in CACHE_RESULTS["KEY_ENCODER"]:

class MyCacheKeyEncoder(CacheKeyEncoder):
    def default(self, o):
        if isinstance(o, Money):
            return {"__money__": [str(o.amount), o.currency]}
        return super().default(o)
default(o)[source]

Encode one value that JSON doesn’t know.

Parameters:

o (object) – The value.

Returns:

A JSON-serializable representation of o.

Return type:

object

Raises:

CacheKeyArgumentError – If o has no safe representation.

cache_results.keys.bind_arguments(func: Callable, args: tuple, kwargs: dict) → tuple[tuple, dict][source]

Bind arguments to the function’s signature, with its defaults applied.

f(1, b=2), f(a=1, b=2) and f(1) (when b defaults to 2) then produce the same key. Otherwise a function and its invalidate() could silently compute different keys depending on how each was called.

Parameters:
  • func (Callable) – The decorated function.

  • args (tuple) – The positional arguments.

  • kwargs (dict) – The keyword arguments.

Returns:

Empty positional arguments and every argument by name, or the arguments unchanged if they don’t fit the signature.

Return type:

tuple[tuple, dict]

cache_results.keys.describe_key_error(error: Exception) → str[source]

Return a short description of why a key couldn’t be made, without argument values.

Parameters:

error (Exception) – The exception that make_key() raised.

Returns:

The description.

Return type:

str

cache_results.keys.dumps(obj: object, encoder: type[CacheKeyEncoder] = <class 'cache_results.keys.CacheKeyEncoder'>) → str[source]

Serialize a normalized value into compact, canonical JSON text.

Parameters:
Returns:

The JSON text, with sorted keys.

Return type:

str

Raises:

CacheKeyArgumentError – If a value can’t be part of a key.

cache_results.keys.make_key(func: Callable, args: tuple, kwargs: dict, encoder: type[CacheKeyEncoder] = <class 'cache_results.keys.CacheKeyEncoder'>, prefix: str = '') → str[source]

Return the cache key of one call of a decorated function.

The key is the prefix, the function’s module and qualified name, and a 32-character SHA-256 hash of its bound, normalized and serialized arguments, e.g. myapp.views.lookup()_3f0c....

Parameters:
  • func (Callable) – The decorated function.

  • args (tuple) – The positional arguments.

  • kwargs (dict) – The keyword arguments.

  • encoder (type[CacheKeyEncoder]) – The JSON encoder for the arguments.

  • prefix (str) – Text that starts every key, e.g. to version them.

Returns:

The cache key.

Return type:

str

Raises:
  • CacheKeyArgumentError – If an argument can’t be part of a key.

  • ValueError – If an argument contains a circular reference, or a value that the encoder refuses, such as a timezone-aware time.

  • RecursionError – If an argument is nested too deeply.

cache_results.keys.normalize(obj: object, encoder: type[CacheKeyEncoder] = <class 'cache_results.keys.CacheKeyEncoder'>) → object[source]

Convert an argument into a JSON-ready value that is the same for equal arguments.

Dicts and lists are kept as JSON objects and arrays, so that {"a": 1} and [("a", 1)] don’t share a key. Sets are sorted by the JSON text of their members, because their iteration order changes from one process to the next, and tagged, so that they don’t share a key with a list. A class, e.g. the cls of a classmethod, is identified by its module and qualified name. A dataclass instance is identified by its class and its fields, so that frozen value objects can be arguments.

Parameters:
  • obj (object) – An argument of the decorated function.

  • encoder (type[CacheKeyEncoder]) – The JSON encoder, used to sort set members.

Returns:

The normalized value.

Return type:

object

Raises:

CacheKeyArgumentError – If a set member can’t be part of a key.