Skip to main content

image_cache

Encrypted, size-capped, on-disk LRU cache of a scan's served image layers.

A served archive is stitched from three independently cached layers — frames, masks and vectors — each held under its own key: the scheme version, the layer name, the file's identity (path, mtime, ctime, size) and the conversion parameters, plus the inference fingerprint and the requested class filter for the two overlay layers. The frames and masks values are ZIP bytes and the vectors value is a raw UTF-8 JSON fragment; every value is encrypted at rest with AES-GCM. The encryption key is derived from the pod's RSA private key (the same pod_rsa.pem the cache-column encryption uses), with a distinct salt. Eviction is delegated to diskcache's native LRU + size limit, and the layers are evicted independently of one another.

Classes

FrameCacheIdentity

class FrameCacheIdentity(    path: str,    mtime: float,    ctime: float,    size: int,    modality: str | None,    laterality: str | None,    width: int,    quality: int,):

Everything that determines a scan's served frames.

Shared by all three layer keys: the frames layer is keyed on this alone, and the overlay layers extend it. Grouping the fields keeps the three builders from drifting apart on which inputs they hash.

Arguments

  • path: The resolved scan file path.
  • mtime: The file modification time.
  • ctime: The file inode change time (stat.st_ctime).
  • size: The file size in bytes.
  • modality: The selected modality (or None).
  • laterality: The selected laterality (or None).
  • width: The requested output width.
  • quality: The WebP quality.

Variables

  • static ctime : float
  • static laterality : str | None
  • static modality : str | None
  • static mtime : float
  • static path : str
  • static quality : int
  • static size : int
  • static width : int

ScanImageCache

class ScanImageCache(*, cache_dir: Path, size_limit_bytes: int, pod_key_path: Path):

On-disk LRU cache of a scan's encrypted, independently keyed image layers.

Arguments

  • cache_dir: Directory backing the diskcache (per-pod).
  • size_limit_bytes: Max total size before least-recently-used eviction.
  • pod_key_path: Path to the pod's pod_rsa.pem, used to derive the key.

Open the backing diskcache and record the pod key path for derivation.

Static methods


build_frames_key

def build_frames_key(identity: FrameCacheIdentity)> str:

Return the key for a scan's served frames.

Carries no segmentation input at all — not the fingerprint, not the selection — so one WebP encode serves every overlay selection, and re-running inference cannot invalidate it.

Arguments

  • identity: The frame identity.

Returns The frames-layer cache key.

build_masks_key

def build_masks_key(    identity: FrameCacheIdentity,    *,    segmentation_fingerprint: str | None,    segmentation_classes: Iterable[str] | None,)> str:

Return the key for a scan's rendered mask PNGs.

Arguments

  • identity: The frame identity.
  • segmentation_fingerprint: Identity of the inference the masks are built from. Included because inference can change while the scan file does not (a re-run at a new model version), so the file stat alone would leave a warm entry serving stale masks. Empty when the file carries no inference rows at all, which is still an identity: it partitions "nothing has run yet" away from every result.
  • segmentation_classes: The requested class filter, or None for every available class.

Returns The masks-layer cache key.

build_vectors_key

def build_vectors_key(    identity: FrameCacheIdentity,    *,    segmentation_fingerprint: str | None,    segmentation_classes: Iterable[str] | None,)> str:

Return the key for a scan's raw segmentation vectors.

Keyed on the same material as the masks layer, under a different layer name: the two outputs come from one inference and one class selection, but they are stored apart so a vectors-only request never pays for a PNG encode and a masks-only request never pays to collect instances.

Arguments

  • identity: The frame identity.
  • segmentation_fingerprint: Identity of the inference the vectors come from. None when overlay serving is unavailable.
  • segmentation_classes: The requested class filter, or None for every available class.

Returns The vectors-layer cache key.

Methods


delete

def delete(self, key: str)> None:

Drop an entry, best-effort, so the next request for that key rebuilds.

For an entry a caller has found unusable for a reason its key does not encode: the key would otherwise keep serving it until eviction.

Invalidation is advisory, and the guarantee belongs to the operation rather than to each call site. A caller reaches here having already decided not to serve the entry, so a failed drop changes nothing it can act on — the only consequence is that the next request retries the drop. Raising instead would let a recovery step become the reason a request fails, which is how the code path that exists to degrade gracefully ends up returning nothing at all. A contended lock is retried first (see _retrying), so what reaches this catch is contention that outlasted every attempt, or a fault of another kind.

This catches broadly where peek narrows, and the asymmetry is deliberate: a read that swallowed everything would report a systemic failure as a miss and rebuild every entry on every request, while a failed drop of an entry already known to be unusable costs nothing beyond a warning.

Arguments

  • key: The cache key. An absent key is a no-op.

get_or_build

def get_or_build(    self, key: str, builder: Callable[[], bytes], *, refresh: bool = False,)> bytes:

Return cached plaintext bytes for key, building + storing on miss.

Arguments

  • key: The cache key (see the build_*_key builders).
  • builder: Zero-arg callable producing the plaintext bytes on a miss.
  • refresh: When True, drop any cached entry up front and rebuild.

Returns The plaintext bytes. Storing them is best-effort: by the time the store is written the bytes are already built, so a failure there costs the next request a rebuild and costs this one nothing.

Raises

  • diskcache.Timeout: Propagated from the read alone, and only when the store's lock stayed contended across every retry. A read that reported contention as a miss would put every concurrent request through a full scan decode it then failed to store.

peek

def peek(self, key: str)> bytes | None:

Return a cached entry's plaintext, or None on a miss.

Arguments

  • key: The cache key.

Returns The plaintext bytes, or None when the entry is absent or unreadable.

A corrupt/truncated entry reads as a miss so the caller rebuilds it: a bad AES-GCM tag surfaces as DecryptError (via _AESEncryption.decrypt), a short blob as ValueError (nonce too short). Any non-bytes stored value is treated the same way, as corrupt, without being handed to AES-GCM.

The catch covers those cases alone. Anything else — a key-derivation regression raising RuntimeError, say — leaves this method rather than being reported as a miss, so a systemic failure is never read as "rebuild this one entry". What it then costs is the caller's decision, not this method's: the overlay path, for one, catches per layer so a read failure costs that layer alone.

Raises

  • diskcache.Timeout: The store's lock was still contended after _LOCK_RETRIES attempts. Deliberately not reported as a miss: under sustained contention every request would then rebuild a warm entry — a full scan decode each — and fail to store it.

put

def put(self, key: str, plaintext: bytes)> None:

Encrypt and store an entry.

Arguments

  • key: The cache key.
  • plaintext: The bytes to store.

Raises

  • diskcache.Timeout: The store's lock was still contended after _LOCK_RETRIES attempts. Left to the caller, which holds the built value: get_or_build and the overlay layers serve it and log rather than discarding a rendered payload.