mrkeyoor.com_
Mon 21 Sept 21:58 UTC
PyPIUtilsupdated 21 Sept 2026

diskcache review

diskcache stores Python cache entries in a local directory backed by SQLite and separate value files. Cache supplies expiry, tags, eviction policies, transactions, statistics, and memoization; FanoutCache shards writes across databases, while DjangoCache plugs the same storage into Django. Deque, Index, locks, and throttles extend it into persistent cross-process coordination. Version 5.6.3 fixes peek() for values large enough to use a file. Our install had no direct dependencies, though pip-audit found one known vulnerability.

Verdict

diskcache 5.6.3 installed in 0.2 seconds and used 1 MB in our sandbox, but pip-audit found 1 known vulnerability. It fits a single host that needs persistent cross-process caching; distributed or async systems should use a server-backed alternative.

We installed it

Lab card: what happened when we installed diskcacheScreenshot of diskcache documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport diskcache in 0.13s · pure Python · requires Python >=3
Known vulns1(pip-audit)

Answers from our run

Does diskcache install cleanly?

Yes. In a fresh container with an empty cache, pip install diskcache finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported 1 known vulnerability.

What does diskcache need to run?

Python >=3, and nothing compiled: it is pure Python. In our run import diskcache succeeded in 0.13s.

diskcache or cachetools: which should you use?

cachetools: Use it for bounded TTL, LRU, or LFU caches that fit inside one process's memory. diskcache 5.6.3 installed in 0.2 seconds and used 1 MB in our sandbox, but pip-audit found 1 known vulnerability.

When should you not use diskcache?

Several machines must see one consistent cache; a local directory gives every host an independent copy, so Redis or Memcached fits that topology

API stability5/5Cache keeps a dict-like surface around get, set, delete, expiry, tags, and transactions, and the 5.x line has been unchanged for years. FanoutCache, DjangoCache, Deque, Index, and the recipe primitives are separately named rather than packed into changing option modes. That maturity makes existing code predictable. It also reflects low activity, so the score describes compatibility history rather than a promise that future Python or SQLite changes will receive quick fixes.
Docs4/5The project publishes a tutorial, API reference, benchmarks, Django guidance, case studies, and substantial docstrings. The material explains eviction settings, transactions, memoization, tags, file-backed values, sharding, and synchronization recipes. Several examples expose behavior that a short README would miss. The weakness is age: the documentation site shows 2023-era material and the PyPI classifiers stop at older CPython releases, leaving current interpreter and security questions unanswered.
Maintenance2/5PyPI shows 5.6.3 was uploaded on August 31, 2023, and GitHub reports the repository's last push on August 10, 2024. The latest tagged fix corrected peek() for a file-backed large value, while later commits adjusted partial indexes. The repository is not archived, but GitHub reports 75 open issues and pull requests combined and there has been no newer package release. Our fresh pip-audit also found one known vulnerability, which raises the cost of that inactivity.
Ecosystem4/5The package receives roughly 7.7 million downloads a week and its repository has 2,906 stars. It includes a Django backend and uses ordinary Python mapping and sequence conventions, so adoption does not require a service protocol or framework-specific client. FanoutCache and the synchronization recipes cover several single-host use cases. The boundary is physical: there is no shared server, remote protocol, or multi-host consistency model, and async integrations must wrap its blocking calls.

Use it if

  • One machine needs a persistent cache larger than RAM and operating Redis would add needless deployment work
  • Sibling Python processes need to share cached results, counters, locks, or a small persistent queue through local storage
  • A Django application needs a file-backed cache with controlled eviction and sharding instead of Django's basic file backend
  • Expensive Python function results should survive restarts and support per-call or tag-based invalidation
Skip it if

Setup reality

Our clean Python 3.12 install of diskcache 5.6.3 completed in 0.2 seconds. One package occupied 1 MB and there were zero direct dependencies. It is pure Python, requires Python 3 or newer, and import diskcache worked in 0.13 seconds. The wheel does not ship py.typed. pip-audit reported one known vulnerability, so check the current advisory and your exposure before approving the package.

Opening Cache creates a SQLite database and supporting files under the directory you provide. Put that directory on writable, persistent local storage. A container's temporary layer loses the cache during replacement, while a shared network filesystem can conflict with SQLite locking assumptions. The documented defaults include a 1 GiB size limit and least-recently-stored eviction. Choose those values deliberately. Large values become individual files, so inode use matters too.

Expiry cleanup is lazy rather than timer-driven. A read-heavy cache can retain expired files until a write culls entries or a maintenance job calls expire(). The default Disk serializer pickles general Python objects. Limit directory permissions, or supply a serializer such as JSONDisk when values come from a narrower data model. If a generated identifier or deployment depends on cached state, remember that this is disposable storage and can be cleared by cull(), eviction, or an operator.

Cache is documented as thread-safe and process-safe on one host. Writes still contend on SQLite; FanoutCache spreads keys across shards, then converts timeout failures into default results instead of raising them. Check return values and record miss rates. Use atomic incr(), decr(), or transact() for shared updates because a read followed by a write can lose another process's change. Async applications must move blocking calls to a worker thread or choose an async network cache.

Patterns

Write expiring entries store-and-read

from diskcache import Cache

with Cache('/var/tmp/report-cache') as cache:
    cache.set('report:42', {'ready': True}, expire=300)
    report = cache.get('report:42', default=None)
    cache.delete('report:42')

A context manager closes the connection. Long-running workers can retain one Cache instance and close it during shutdown.

Invalidate a tagged group tag-and-evict

from diskcache import Cache

cache = Cache('/var/tmp/page-cache', tag_index=True)
cache.set('page:/', home_html, tag='pages', expire=60)
cache.set('page:/about', about_html, tag='pages', expire=60)
cache.evict('pages')

tag_index makes tag eviction efficient. Without it, evict must scan cache rows.

Persist function results memoize-results

from diskcache import Cache

cache = Cache('/var/tmp/query-cache')

@cache.memoize(expire=3600, tag='monthly', typed=True)
def totals(account_id, month):
    return run_query(account_id, month)

value = totals(42, '2026-08')

Arguments and return values must be serializable by the configured Disk. typed=True keeps values such as 3 and 3.0 distinct.

Remove one memoized result invalidate-one-call

key = totals.__cache_key__(42, '2026-08')
cache.delete(key)

fresh = totals.__wrapped__(42, '2026-08')

The decorated function exposes its cache-key builder and original callable, which supports precise invalidation and bypassing.

Spread writes across SQLite files shard-writes

from diskcache import FanoutCache

cache = FanoutCache(
    '/var/tmp/shared-cache',
    shards=8,
    timeout=1.0,
    size_limit=4 * 2**30,
)
ok = cache.set('job:7', result, expire=600)

FanoutCache returns False when a set times out. Treat the return value as an operational signal instead of assuming the write succeeded.

Use counters and a transaction update-atomically

from diskcache import Cache

cache = Cache('/var/tmp/counters')
cache.incr('hits', delta=1, default=0)

with cache.transact():
    cache.set('state', 'ready')
    cache.incr('writes', delta=1, default=0)

incr and decr avoid the lost update possible with separate get and set calls. Keep transactions short because they hold the write lock.

Guard a cross-process job coordinate-processes

from diskcache import Cache, Lock

cache = Cache('/var/tmp/locks')
with Lock(cache, 'daily-import', expire=600):
    run_import()

An expiry prevents a dead process from leaving the lock indefinitely. This coordinates processes sharing one cache directory, not separate hosts.

Limit calls across workers throttle-calls

from diskcache import Cache, throttle

cache = Cache('/var/tmp/rate-limit')

@throttle(cache, count=10, seconds=1)
def send_request(payload):
    return client.post(payload)

The recipe polls shared cache state and may block the caller. Do not call it directly on an asyncio event loop.

Store a large file without loading it stream-file-value

from diskcache import Cache

cache = Cache('/var/tmp/files')
with open('report.pdf', 'rb') as source:
    cache.set('report:42', source, read=True, expire=86400)

handle = cache.get('report:42', read=True)
if handle:
    with handle:
        consume(handle)

read=True returns an open file handle for file-backed data. Close it promptly so eviction and cleanup can remove the file.

Push and pull queued work make-persistent-queue

from diskcache import Cache

queue = Cache('/var/tmp/jobs')
queue.push({'kind': 'resize', 'id': 7}, prefix='jobs')
key, job = queue.pull(prefix='jobs', default=(None, None))

pull removes the item atomically and has no acknowledgement phase. A worker crash after pulling can lose that job.

Use the Django backend configure-django

CACHES = {
    'default': {
        'BACKEND': 'diskcache.DjangoCache',
        'LOCATION': '/var/lib/myapp/cache',
        'TIMEOUT': 300,
        'SHARDS': 8,
        'DATABASE_TIMEOUT': 0.5,
        'OPTIONS': {'size_limit': 2**32},
    }
}

LOCATION must be writable and persistent. DjangoCache uses FanoutCache, so database timeout failures can surface as ordinary cache misses.

Measure and clean the cache run-maintenance

from diskcache import Cache

cache = Cache('/var/tmp/report-cache', statistics=True)
hits, misses = cache.stats()
print(hits, misses, cache.volume())
cache.expire()
cache.cull()
warnings = cache.check(fix=True)

Statistics add a write to lookups when enabled. Schedule expire and cull if a workload performs many reads and few writes.

Alternatives

PackageRegistryPick it when
cachetoolsPyPIUse it for bounded TTL, LRU, or LFU caches that fit inside one process's memory
redisPyPIUse its client when processes on several hosts need one cache server and atomic remote operations
joblibPyPIUse it to persist expensive scientific function results with NumPy-aware hashing and storage
sqlitedictPyPIUse it when you want a persistent SQLite mapping without expiry or eviction semantics

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.