Skip to main content

Cache

A disk cache backed by SQLite, shared safely across threads and processes.

Usage

Source

Cache(
    directory,
    timeout=None,
    *,
    size_limit=None,
    cull_limit=None,
    serializer="msgpack",
    pickle=False,
    types=None,
    clock=None,
    max_connections=None,
    sqlite_cache_size=None,
    wal_size_limit=None,
    wal_checkpoint="manual",
    on_decode_error=None,
    durable=False
)

Every thread and process that opens the same directory shares one cache. The API follows diskcache.Cache; the deliberate differences are listed in the Divergences guide.

Parameters

directory: str | os.PathLike[str]

The cache directory. It’s created (owner-only, 0700) if it doesn’t exist.

timeout: float | None = None

Seconds a call waits for a lock or a pending fork before raising Timeout (default 60). It has to exceed work in flight plus a fork: see the Concurrency and Forks guide.

size_limit: int | None = None

The disk budget in bytes, database plus value files (default 1 GiB, minimum 1 MiB). Stored in the cache, so it applies to every process.

cull_limit: int | None = None

How many least-recently-stored entries a set() evicts when the cache is over size_limit (default 10).

serializer: str = "msgpack"

'msgpack' (the default) or 'json', for values that aren’t plain str or bytes.

pickle: bool = False

Allow pickle for values the serializer can’t represent exactly. Without it, such values raise TypeError and pickled values are never read. Once the encoder has used a registration (types, or the global registry) for any part of a value, that value is never pickled: it is stored through the registration, or raises.

types: TypeRegistry | None = None

A TypeRegistry consulted before the process-wide diskcache_oxide.registry, for a cache that needs its own encoding of a type. Register types at import time, in every process that opens the directory, since a row written through a registration can only be read where it is registered.

clock: Callable[[], float] | None = None

A function returning the current time in seconds, used instead of the native clock. Tests pass clock=lambda: time.time() to follow a patched time.time.

max_connections: int | None = None

The most SQLite connections this process opens for the directory, shared by every handle on it (default 8).

sqlite_cache_size: int | None = None

SQLite’s page cache per connection, in 4 KiB pages (default 256, 1 MiB).

wal_size_limit: int | None = None

The write-ahead log is trimmed back to half this many bytes whenever it resets (default 64 MiB, minimum 1 MiB).

wal_checkpoint: Literal["manual", "auto"] = "manual"

'manual' (the default) leaves checkpoints to checkpoint() and close(). 'auto' also checkpoints after writes once the log is over wal_size_limit.

on_decode_error: Literal["raise", "miss"] | Callable[[str, Exception], object] | None = None

What a read does with a stored value it can’t decode. 'raise' (the default, unless durable=True, where it’s 'miss') raises. 'miss' treats it as a miss, logs a warning on the diskcache_oxide logger, and deletes the row if no build could ever read it (a class removed since it was pickled, malformed bytes). A row another version or host might read (ForeignValueError) is left in place. A callable does the same as 'miss', but is called with (key, exc) instead of the warning, after the row is dealt with; whatever it raises is logged. UnsafeValueError, Timeout, MemoryError and other errors that aren’t about the stored bytes raise, though under durable=True a Timeout, OSError or RecursionError from a pickle hook is a miss, with the row kept, and so is a RecursionError from a value too deep for this thread’s stack. Covers get, cache[key] and pop, which decodes before it deletes, so it keeps a row it can’t read. Under 'raise', pop still deletes a value no build could read, then raises.

durable: bool = False
Keep the cache from taking a request down, for an app whose every caller can fall back to its source of truth (default False). An outage on a single-key call (a Timeout, a full disk, an I/O error, a damaged database) doesn’t raise: get, pop and in give the default or False, set, add, touch and delete give False, and incr and decr give default + delta. It’s reported on the diskcache_oxide logger, at most once a minute per kind of failure. Bugs still raise (TypeError, ValueError, UnsafeValueError), as do MemoryError and KeyboardInterrupt, a RecursionError from encoding a value too deep for this thread’s stack, the constructor, admin calls (len, volume, clear, expire, cull, checkpoint, iteration) and anything inside a transaction held on this cache. This isn’t durability in the database sense: this option can drop writes, and a dropped set, delete, pop or incr means stale data until the entry expires, or forever if it has no expiry. See “A cache that can’t take a request down” in the Getting Started guide before you use it.

Raises

ValueError

A setting is out of range, such as a size_limit under 1 MiB.

TypeError

An argument has the wrong type, including a bool for timeout.

FormatMismatchError

The directory holds a database in a format this version can’t read.

LayoutMismatchError

The directory is a FanoutCache root or one of its shards. Also a ValueError, and a FormatMismatchError.

Timeout
Another process held the database locked, or a fork stayed pending, for longer than timeout.

Examples

from diskcache_oxide import Cache

cache = Cache('/var/cache/my-app', timeout=1.0)  # every worker opens the same directory

cache.set('user:42', {'name': 'Sam', 'roles': {'admin'}}, expire=300)
cache.get('user:42')             # the dict, with roles still a set; None on a miss
cache['greeting'] = 'hello'      # never expires
if cache.add('job:nightly', 1, expire=3600):  # exactly one process wins
    run_nightly_job()
cache.close()                    # or `with Cache(...) as cache:`