Key Constructors¶
A KeyConstructor describes how to build a cache key from a function
call. Each attribute is a CacheKeyField that pulls a piece of data
out of the call and turns it into a deterministic string. The
constructor joins those slices into the final key.
Anatomy of a cache key¶
Every key produced by a KeyConstructor has three parts:
- The function id is the function's dotted path
(
myapp.views.get_user) or whatever value is passed asMeta.key_identifier. - The namespace is an optional
Meta.namespaceplaced in front of the function id. - The partition is the part built from key fields with
partition=True. Entries that share the same partition prefix can be wiped together withdelete_by_prefix(...). - The suffix is the part built from the non-partition key fields.
Declaring a constructor¶
from restflow.caching import (
KeyConstructor, ArgsKeyField, ConstantKeyField, QueryParamsKeyField,
)
class UserPayloadKey(KeyConstructor):
user = ArgsKeyField("user_id", partition=True)
version = ConstantKeyField("v", "1")
page = QueryParamsKeyField(["page", "size"])
class Meta:
namespace = "users"
version = 1
max_key_suffix_length = 250
hash_suffix_on_overflow = True
Meta is optional. The full set of options:
| Option | Default | Effect |
|---|---|---|
namespace |
"" |
String prefix in front of every generated key. |
version |
1 |
Bumping invalidates every key produced by this constructor. |
key_identifier |
"" |
Replaces the per-function identifier. Useful when two functions should share a cache. |
max_key_suffix_length |
settings default (250) |
Maximum suffix length before overflow handling kicks in. |
hash_suffix_on_overflow |
settings default (False) |
When True, suffixes longer than max_key_suffix_length are replaced with their SHA-256 digest. When False, the suffix is truncated. |
max_key_suffix_length and hash_suffix_on_overflow fall back to the
values in
RESTFLOW_SETTINGS["CACHE_SETTINGS"] (see Settings)
when not set on Meta.
InlineKeyConstructor¶
Build the constructor class from a plain dict of fields without writing a subclass.
from restflow.caching import InlineKeyConstructor, ArgsKeyField
UserKey = InlineKeyConstructor(
fields={"user": ArgsKeyField("user_id", partition=True)},
namespace="users",
)
@cache_result(UserKey, ttl=60)
def get_user(user_id: int): ...
Calls with the same fields and Meta values return the cached class,
so repeated decorator runs on the same module do not pile up new
subclasses.
DefaultKeyConstructor¶
When @cache_result is used without a key constructor, the wrapper
uses DefaultKeyConstructor, which captures every positional and
keyword argument. Each unique combination of arguments produces a
separate cache entry, with no namespace and no partition.
from restflow.caching import cache_result
@cache_result(ttl=60)
def expensive_op(a: int, b: int):
return a + b
Cache key fields¶
The available CacheKeyField subclasses, each with their key
parameters.
ConstantKeyField¶
A fixed key: value pair on every call. Useful for tagging keys with
values that do not depend on call arguments, like an environment
label, an app version, or a feature flag.
class UserKey(KeyConstructor):
env = ConstantKeyField("env", "production")
user = ArgsKeyField("user_id", partition=True)
ArgsKeyField¶
Captures function arguments by name.
class UserKey(KeyConstructor):
user = ArgsKeyField("user_id", partition=True)
lang = ArgsKeyField("lang")
argumentsaccepts"*"for every bound argument, a single name, or a list of names.pathapplies a dotted attribute path to each captured value before stringification, soArgsKeyField("user", path="id")recordsuser.idrather than the user object itself.normalizerruns on each resolved value, useful for coercion to primitives. If the normalizer raises, the original value is used.
RequestValueKeyField¶
Reads a value off the request object using a dotted path.
pathis the dotted path applied to the request, like"user.id"or"META.HTTP_X_TENANT".request_argdefaults to"request". Change it when the wrapped function takes the request under a different name.view_self_request_fallbackisTrueby default, which lets DRF viewset methods that receiveselfresolveself.request.
QueryParamsKeyField¶
Captures values from the request's query string.
paramsaccepts"*"for every parameter, a single name, or a list of names. Multi-value parameters are recorded sorted, so?tag=a&tag=band?tag=b&tag=aproduce the same key.request_argandview_self_request_fallbackwork the same way as onRequestValueKeyField.
DjangoModelKeyField¶
Fingerprints a Django model's schema. Useful when the cached payload depends on the model's shape, so a migration that adds, removes, or retypes a field invalidates the cache automatically. The payload is always hashed.
DrfSerializerKeyField¶
Fingerprints a DRF serializer's shape. Useful when the cached response would change if the serializer changes, since adding or removing a field invalidates the cache automatically. Walks nested serializers and list serializers. The payload is always hashed.
Common parameters on every key field¶
Every CacheKeyField accepts the same three keyword arguments:
| Parameter | Default | Effect |
|---|---|---|
partition |
False |
When True, the field's contribution moves into the cache key prefix. Entries that share a prefix can be wiped with delete_by_prefix(...). |
hash_value |
False |
When True, the stringified payload is replaced with its SHA-256 digest. Useful for long or sensitive payloads. |
sort_lists |
True |
When True, list and tuple items are sorted before stringification, so f([1, 2]) and f([2, 1]) share a cache entry. Set to False when list order is meaningful. Dict keys are always sorted. |
Partition vs suffix¶
Choose partition=True for a field to wipe a group of related entries
together. Typical examples:
- A user id, so that
delete_by_prefix(user_id=42)drops every cached entry tagged with that user for the given function. - A tenant id, for multi-tenant isolation.
- A locale, to drop everything for a language at once.
Choose the default (partition=False) for fields that produce many
distinct cache entries within the same partition, like page numbers
or sort orders.
Example:
class BaseKeyConstructor(KeyConstructor):
user_id = ArgsKeyField("user_id", partition=True)
class Meta:
identifier = "user_data"
version = 1
class Constructor1(BaseKeyConstructor):
part = ConstantKeyField("part", "a")
class Constructor2(BaseKeyConstructor):
part = ConstantKeyField("part", "b")
# both share the same prefix `restflow.cache.user_data::user_id:42`
# so `delete_by_prefix(user_id=42)` will drop both
Where to next¶
- cache_result: apply a constructor to a function and use the resulting wrapper.
- Invalidation Rules: connect Django model signals to cache invalidation.
- Settings: tune
MAX_KEY_SUFFIX_LENGTHandHASH_SUFFIX_ON_OVERFLOWglobally.