loom.core.cache

class loom.core.cache.CacheGateway(*, alias='default')[source]

Bases: object

Facade over aiocache with msgpack serialization for entity data.

Two distinct usage modes:

Data gateway — configured with MsgspecSerializer. Used by CachedRepository for entity and list storage. All values are msgpack-encoded on write and decoded on read.

Counter gateway — configured without a serializer (raw backend). Used by GenerationalDependencyResolver for generation counter storage. Values are stored as plain Python integers, enabling atomic native increment on both Redis and SimpleMemoryCache.

The gateway auto-detects which mode it is in at construction time and routes incr() accordingly — no manual configuration needed beyond choosing the right aiocache alias.

Parameters:

alias (str) – Registered aiocache alias to retrieve the backend from.

Example:

data_gateway    = CacheGateway(alias=config.aiocache_alias)
counter_gateway = CacheGateway(alias=config.effective_counter_alias)
resolver = GenerationalDependencyResolver(counter_gateway)
static configure(raw_config)[source]

Apply a configuration mapping to the global aiocache registry.

Parameters:

raw_config (Mapping[str, Any]) – Configuration dict compatible with aiocache.caches.set_config.

Return type:

None

classmethod apply_config(config)[source]

Configure aiocache from a CacheConfig.

Equivalent to configure() but also injects config.max_size into every aiocache.SimpleMemoryCache backend entry that does not already declare its own max_size. Entries for other backend types (Redis, Memcached, …) are forwarded unchanged.

Parameters:

config (CacheConfig) – Resolved cache configuration.

Return type:

None

Example:

cache_cfg = CacheConfig(
    aiocache_alias="cache",
    counter_alias="counters",
    max_size=1000,
    aiocache_config={
        "cache":    {"cache": "aiocache.SimpleMemoryCache", ...},
        "counters": {"cache": "aiocache.SimpleMemoryCache"},
    },
)
CacheGateway.apply_config(cache_cfg)
async get_value(key: str, *, type: type[T]) T | None[source]
async get_value(key: str, *, type: None = None) Any

Retrieve a cached value, optionally converting it to the given type.

Parameters:
  • key (str) – Cache key.

  • type (type[T] | None) – Optional target type for msgspec.convert.

Returns:

The cached value (converted to type if given) or None on miss.

Return type:

T | Any | None

async multi_get_values(keys, *, type=None)[source]

Retrieve multiple values in a single round-trip.

Parameters:
  • keys (list[str]) – Cache keys to look up.

  • type (type[T] | None) – Optional target type for msgspec.convert on each value.

Returns:

Values in the same order as keys, with None for misses.

Return type:

list[T | Any | None]

async exists(key)[source]

Check whether a key exists in the cache.

Parameters:

key (str) – Cache key to check.

Returns:

True if the key is present.

Return type:

bool

async set_value(key, value, ttl=None)[source]

Store a value under the given key.

Parameters:
  • key (str) – Cache key.

  • value (Any) – Value to store.

  • ttl (int | None) – Time-to-live in seconds. None means no expiration.

Return type:

None

async multi_set_values(pairs, ttl=None)[source]

Store multiple key-value pairs in a single round-trip.

Parameters:
  • pairs (list[tuple[str, Any]]) – List of (key, value) tuples.

  • ttl (int | None) – Time-to-live in seconds applied to all entries.

Return type:

None

async incr(key, delta=1)[source]

Increment a numeric value at the given key.

Routes to the optimal strategy based on the backend configuration detected at construction time:

  • Raw backend (no serializer): delegates to the backend’s native increment method when available. On Redis this is an atomic INCR / INCRBY command; on SimpleMemoryCache it is asyncio-safe. Falls back to GET+SET for backends that do not expose increment.

  • Serialized backend (MsgspecSerializer): uses a non-atomic GET+SET. Correct for single-process deployments; the asyncio event loop does not preempt between the two awaits within a single coroutine execution.

Parameters:
  • key (str) – Cache key holding an integer value.

  • delta (int) – Amount to increment by. Defaults to 1.

Returns:

The new value after incrementing.

Return type:

int

async delete(key)[source]

Delete a single key from the cache.

Parameters:

key (str) – Cache key to remove.

Returns:

Number of keys actually deleted (0 or 1).

Return type:

int

async delete_many(keys)[source]

Delete multiple keys from the cache.

Parameters:

keys (list[str]) – Cache keys to remove.

Returns:

Number of keys actually deleted.

Return type:

int

async clear()[source]

Remove all entries from the cache backend.

Return type:

None

async close()[source]

Release the underlying cache connection resources.

Return type:

None

class loom.core.cache.CacheBackend(*args, **kwargs)[source]

Bases: Protocol

Abstract cache backend defining the contract for key-value storage operations.

async get_value(key, *, type=None)[source]

Retrieve a value by key, optionally converting it to the given type.

Parameters:
  • key (str) – Cache key to look up.

  • type (type[T] | None) – Optional target type for deserialization.

Returns:

The cached value, converted to type when provided, or None on miss.

Return type:

T | Any | None

async set_value(key, value, ttl=None)[source]

Store a value under the given key with an optional TTL in seconds.

Parameters:
  • key (str) – Cache key.

  • value (Any) – Value to store.

  • ttl (int | None) – Time-to-live in seconds. None means no expiration.

Return type:

None

async multi_get_values(keys, *, type=None)[source]

Retrieve multiple values by their keys in a single round-trip.

Parameters:
  • keys (list[str]) – List of cache keys to look up.

  • type (type[T] | None) – Optional target type for deserialization of each value.

Returns:

A list of values in the same order as keys, with None for misses.

Return type:

list[T | Any | None]

async multi_set_values(pairs, ttl=None)[source]

Store multiple key-value pairs in a single round-trip.

Parameters:
  • pairs (list[tuple[str, Any]]) – List of (key, value) tuples to store.

  • ttl (int | None) – Time-to-live in seconds applied to all entries.

Return type:

None

async exists(key)[source]

Check whether a key exists in the cache.

Parameters:

key (str) – Cache key to check.

Returns:

True if the key is present, False otherwise.

Return type:

bool

async delete(key)[source]

Delete a single key from the cache.

Parameters:

key (str) – Cache key to remove.

Returns:

Number of keys actually deleted (0 or 1).

Return type:

int

async delete_many(keys)[source]

Delete multiple keys from the cache in a single operation.

Parameters:

keys (list[str]) – List of cache keys to remove.

Returns:

Number of keys actually deleted.

Return type:

int

async incr(key, delta=1)[source]

Atomically increment a numeric value stored at the given key.

Parameters:
  • key (str) – Cache key holding an integer value.

  • delta (int) – Amount to increment by. Defaults to 1.

Returns:

The new value after incrementing.

Return type:

int

async close()[source]

Release any resources held by the backend (connections, pools, etc.).

Return type:

None

class loom.core.cache.CacheConfig(*, enabled=True, aiocache_alias='default', counter_alias=None, aiocache_config=<factory>, default_ttl=200, default_list_ttl=120, ttl=<factory>, max_size=None)[source]

Bases: Struct

Configuration for cache behaviour including TTLs and aiocache backend settings.

Parameters:
enabled

Global toggle for cache operations.

Type:

bool

aiocache_alias

Named alias for the aiocache data backend. The data backend must be configured with MsgspecSerializer.

Type:

str

counter_alias

Named alias for the counter backend used by GenerationalDependencyResolver. This backend must have no serializer so that native atomic increment operations (Redis INCR, SimpleMemoryCache.increment) work correctly. Defaults to None, meaning the same alias as aiocache_alias is used (safe for single-process deployments; uses a non-atomic GET+SET fallback automatically).

Type:

str | None

aiocache_config

Raw configuration mapping forwarded to aiocache. Keyed by alias name. Use apply_config to have max_size injected automatically for memory backends.

Type:

dict[str, Any]

default_ttl

Default TTL in seconds for single-entity lookups.

Type:

int

default_list_ttl

Default TTL in seconds for list / index queries.

Type:

int

ttl

Per-entity TTL overrides keyed by entity name. Append _list for list overrides (e.g. {"user": 300, "user_list": 150}).

Type:

dict[str, int]

max_size

Maximum number of entries for aiocache.SimpleMemoryCache backends. Injected automatically when calling apply_config(). Has no effect on Redis or other non-memory backends.

Type:

int | None

Example YAML (Redis + separate counter backend):

cache:
  aiocache_alias: cache
  counter_alias: counters
  default_ttl: 300
  default_list_ttl: 120
  max_size: 1000
  ttl:
    user: 600
    user_list: 300
  aiocache:
    cache:
      cache: aiocache.RedisCache
      endpoint: ${oc.env:REDIS_HOST,redis}
      port: 6379
      namespace: myapp
      serializer:
        class: loom.core.cache.serializer.MsgspecSerializer
    counters:
      cache: aiocache.RedisCache
      endpoint: ${oc.env:REDIS_HOST,redis}
      port: 6379
      namespace: myapp_counters
      # no serializer — raw integer storage, atomic Redis INCR

Example YAML (in-memory for development):

cache:
  aiocache_alias: cache
  counter_alias: counters
  max_size: 500
  aiocache:
    cache:
      cache: aiocache.SimpleMemoryCache
      serializer:
        class: loom.core.cache.serializer.MsgspecSerializer
    counters:
      cache: aiocache.SimpleMemoryCache
      # no serializer
property effective_counter_alias: str

Return the resolved counter alias, falling back to aiocache_alias.

classmethod from_mapping(data)[source]

Create a CacheConfig from a raw dictionary (e.g. parsed TOML/YAML).

Parameters:

data (dict[str, Any]) – Flat or nested mapping with cache configuration values.

Returns:

A validated CacheConfig instance.

Return type:

CacheConfig

ttl_for_single(entity)[source]

Resolve the effective TTL for a single-entity lookup.

Parameters:

entity (str) – Normalized entity name (e.g. "user").

Returns:

TTL value in seconds.

Return type:

int

ttl_for_list(entity)[source]

Resolve the effective TTL for a list or index query.

Parameters:

entity (str) – Normalized entity name (e.g. "user").

Returns:

TTL value in seconds.

Return type:

int

class loom.core.cache.CachedRepository(repository, *, config, cache, dependency_resolver)[source]

Bases: Repository[OutputT, CreateT, UpdateT, IdT], Generic[OutputT, CreateT, UpdateT, IdT]

Cache-aside wrapper with generational invalidation.

Parameters:
property entity_name: str

Normalized name of the cached entity.

async get_by_id(obj_id, profile='default')[source]

Fetch a single entity by its primary key.

Parameters:
  • obj_id (IdT) – Primary key of the entity.

  • profile (str) – Loading profile name for eager-load options.

Returns:

The entity output struct, or None if not found.

Return type:

OutputT | None

async get_by(field, value, profile='default')[source]

Fetch one entity by arbitrary field.

This path intentionally delegates to the wrapped repository without cache-aside behavior for now. Field-based lookups can target mutable columns and the cache invalidation surface is broader than id-based access; keeping it uncached preserves correctness while the lookup cache policy is designed explicitly.

Parameters:
Return type:

OutputT | None

async exists_by(field, value)[source]

Check existence by arbitrary field.

Existence checks are delegated directly to the wrapped repository to avoid stale negative/positive cache entries on mutable fields.

Parameters:
Return type:

bool

async list_paginated(page_params, filter_params=None, profile='default')[source]

Fetch a paginated list of entities.

Parameters:
  • page_params (PageParams) – Pagination parameters (page and limit).

  • filter_params (FilterParams | None) – Optional filter criteria.

  • profile (str) – Loading profile name for eager-load options.

Returns:

A PageResult with the matching items and pagination metadata.

Return type:

PageResult[OutputT]

async list_with_query(query, profile='default')[source]

Fetch entities using a structured QuerySpec.

Supports both offset and cursor pagination, structured filters, and explicit sort directives. The concrete return type depends on query.pagination:

Parameters:
  • query (QuerySpec) – Structured query specification.

  • profile (str) – Loading profile name for eager-load options.

Returns:

A PageResult for offset queries or a CursorResult for cursor queries.

Return type:

PageResult[OutputT] | CursorResult[OutputT]

async create(data)[source]

Persist a new entity.

Parameters:

data (CreateT) – Creation payload struct.

Returns:

The newly created entity output struct.

Return type:

OutputT

async update(obj_id, data)[source]

Apply a partial update to an existing entity.

Parameters:
  • obj_id (IdT) – Primary key of the entity to update.

  • data (UpdateT) – Partial update payload struct.

Returns:

The updated entity output struct, or None if not found.

Return type:

OutputT | None

async delete(obj_id)[source]

Delete an entity by its primary key.

Parameters:

obj_id (IdT) – Primary key of the entity to delete.

Returns:

True if the entity was deleted, False if not found.

Return type:

bool

class loom.core.cache.DependencyResolver(*args, **kwargs)[source]

Bases: Protocol

Protocol for cache dependency tracking and invalidation.

async fingerprint(tags)[source]

Compute a composite fingerprint from the current generation of each tag.

Parameters:

tags (list[str]) – Dependency tag names.

Returns:

A stable hash string representing the combined tag state.

Return type:

str

async bump_from_events(events)[source]

Increment generation counters for all tags affected by mutation events.

Parameters:

events (tuple[MutationEvent, ...]) – Mutation events produced within a transaction.

Return type:

None

entity_tags(entity, entity_id)[source]

Return dependency tags for a single entity lookup.

Parameters:
  • entity (str) – Normalized entity name.

  • entity_id (object | None) – Primary key of the entity, or None.

Returns:

List of tag names that should be tracked for this entity.

Return type:

list[str]

list_tags(entity, filter_fingerprint)[source]

Return dependency tags for a list/index query.

Parameters:
  • entity (str) – Normalized entity name.

  • filter_fingerprint (str) – Hash of the applied filter parameters.

Returns:

List of tag names that should be tracked for this list query.

Return type:

list[str]

class loom.core.cache.GenerationalDependencyResolver(cache)[source]

Bases: DependencyResolver

Generational tags with monotonic counters in cache backend.

Parameters:

cache (CacheBackend)

async fingerprint(tags)[source]

Compute a composite fingerprint from generation counters of all tags.

Parameters:

tags (list[str]) – Dependency tag names.

Returns:

A stable hash representing the combined tag generation state.

Return type:

str

async bump_from_events(events)[source]

Increment generation counters for all tags affected by mutation events.

Parameters:

events (tuple[MutationEvent, ...]) – Mutation events to process.

Return type:

None

entity_tags(entity, entity_id)[source]

Return dependency tags for a single entity lookup.

Parameters:
  • entity (str) – Normalized entity name.

  • entity_id (object | None) – Primary key of the entity, or None.

Returns:

List of tag names for this entity.

Return type:

list[str]

list_tags(entity, filter_fingerprint)[source]

Return dependency tags for a list/index query.

Parameters:
  • entity (str) – Normalized entity name.

  • filter_fingerprint (str) – Hash of the applied filter parameters.

Returns:

List of tag names for this list query.

Return type:

list[str]

class loom.core.cache.MsgspecSerializer[source]

Bases: object

aiocache serializer backed by msgspec msgpack.

dumps(value)[source]

Serialize a Python object to MessagePack bytes for cache storage.

Parameters:

value (Any) – Object to serialize.

Returns:

MessagePack-encoded bytes.

Return type:

bytes

loads(value)[source]

Deserialize MessagePack bytes back into a Python object.

Parameters:

value (bytes | None) – Raw bytes from cache, or None.

Returns:

The decoded Python object, or None if input is None.

Return type:

Any

loom.core.cache.cache_query(*, scope='list', ttl_key=None)[source]

Declarative marker for custom repository read methods.

Parameters:
  • scope (str)

  • ttl_key (str | None)

Return type:

Callable[[F], F]

loom.core.cache.cached(cls)[source]

Declarative marker for repositories that support cache wrapping.

Parameters:

cls (T)

Return type:

T