cache.cache_entry

On this page

CacheEntry

Python · 9 lines
@dataclass
class CacheEntry:
    key: str
    value: Any
    created_at: float
    last_access: float
    hits: int = 0
    ttl: Optional[float] = None
    size_bytes: int = 0

Overview

The simple entry type used exclusively by MultiLevelCache. Unlike IntelligentCacheEntry, it has no notion of tenants, embeddings, or RL state — it exists purely to track a value plus enough metadata (hits, age, idle time, size, TTL) to support eviction-strategy decisions and stats reporting.

A module-level config = CacheConfig() instance is created at import time and used only as a source for the default fallback size_bytes value (config.size_bytes, 1024 by default) when automatic size estimation fails.

[!WARNING] Contains Arabic/English bilingual comments The source file's docstrings and inline comments are bilingual (Arabic and English), reflecting the project's origin. This does not affect behavior, but is worth knowing when reading the raw source alongside this documentation.

Constructor / fields

FieldTypeDefaultMeaning
keystrrequiredThe (already-hashed, in MultiLevelCache usage) cache key
valueAnyrequiredThe cached payload
created_atfloattime.time()Creation timestamp
last_accessfloattime.time()Last-access timestamp
hitsint0Access counter
ttlOptional[float]NoneTime-to-live in seconds; None = never expires
size_bytesint0Estimated size; auto-computed in __post_init__ if left at 0

__post_init__(self) -> None

If size_bytes == 0, calls _calculate_size() and stores the result.

_calculate_size(self) -> int

Attempts len(pickle.dumps(self.value)). If pickling fails for any reason (unpicklable value), falls back to the module-level config.size_bytes default (1024).

[!NOTE] Every CacheEntry is pickled at least once, just to measure it Even values that are never actually written to disk (L1/L2 entries) are serialized with pickle.dumps purely to estimate their size in bytes, unless size_bytes is passed explicitly. For very large or expensive-to-pickle values, this has a real per-set() cost — see Known Issues.

Methods

MethodReturnsBehavior
increment_hits() -> None—hits += 1; last_access = time.time()
is_expired() -> boolboolFalse if ttl is None; else time.time() > created_at + ttl
age() -> floatsecondstime.time() - created_at
idle_time() -> floatsecondstime.time() - last_access
get_stats() -> dictdict{key, hits, age_seconds, idle_seconds, size_bytes, is_expired, ttl}
__repr__() -> strstr`"CacheEntry(key='...', hits=N, age=X.Xs, status=valid

Examples

Python · 11 lines
from zerda.cache import CacheEntry
import time

entry = CacheEntry(key="user:42", value={"name": "Ada"}, ttl=60)
print(entry)                     # CacheEntry(key='user:42', hits=0, age=0.0s, status=valid)

entry.increment_hits()
print(entry.get_stats())

time.sleep(61)
print(entry.is_expired())        # True

Complexity

All methods are O(1) except _calculate_size(), which is O(size of value) due to pickling.

Thread Safety

CacheEntry itself has no locking — it is a plain mutable dataclass. Thread safety is the responsibility of its owner, MultiLevelCache, which guards all access with self.lock.

Best Practices

  • Pass size_bytes explicitly when constructing entries for very large or slow-to-pickle values, to avoid the implicit pickle.dumps size-estimation cost.
  • Don't mutate hits/last_access directly from outside MultiLevelCache — use increment_hits() so both stay in sync.

Related pages