Shelved Cache
Shelved Cache (version 0.5.0) is a Python library that provides a persistent cache implementation for `cachetools` objects, storing entries to disk using Python's `shelve` module. It allows for fast, persistent caching of function results across program executions. The library is actively maintained, integrating with standard Python caching mechanisms.
Warnings
- breaking Version 0.5.0 drops support for Python 3.9. Older versions (0.4.0) also dropped support for Python 3.7 and 3.8. Users should ensure they are running on Python 3.10 or newer.
- breaking Version 0.5.0 requires `cachetools` version `^6.0.0`. Older versions of `cachetools` are no longer supported.
- gotcha When decorating multiple functions with `shelved-cache`, each function MUST use a *separate* `PersistentCache` instance and a *different* file name for its persistence store. Reusing the same file for multiple caches will lead to errors.
- gotcha The underlying `shelve` module, which `shelved-cache` uses, does not natively support concurrent read/write access from multiple processes. While multiple simultaneous reads are safe, if one process has the shelf open for writing, no other process should have it open for reading or writing without explicit external locking. This can lead to data corruption or deadlocks in multi-process environments.
- gotcha Users on Windows with Python 3.13 and above might encounter permission errors or unexpected behavior. While earlier Windows issues were addressed, this specific combination has been noted as potentially problematic.
Install
-
pip install shelved-cache
Imports
- PersistentCache
from shelved_cache import PersistentCache
- AsyncPersistentCache
from shelved_cache import AsyncPersistentCache
Quickstart
import cachetools
from shelved_cache import PersistentCache
from cachetools import LRUCache
import os
# Define a unique cache file path
cache_file = "my_function_cache.db"
# --- Cleanup any previous cache files for a clean run ---
# shelve can create multiple files (.db, .bak, .dir, .dat)
for ext in ["", ".bak", ".dir", ".dat"]:
if os.path.exists(cache_file + ext):
os.remove(cache_file + ext)
# Initialize a persistent LRU cache, linking it to a file
pc_for_square = PersistentCache(LRUCache, cache_file, maxsize=100)
@cachetools.cached(pc_for_square)
def square(x):
print(f"Calculating square for {x}...")
return x * x
# First call: calculation happens
result1 = square(5)
print(f"First call: square(5) = {result1}")
# Second call: result is retrieved from cache (no 'Calculating...' output)
result2 = square(5)
print(f"Second call: square(5) = {result2}")
# A different argument will trigger a new calculation and cache entry
result3 = square(10)
print(f"Third call: square(10) = {result3}")
# It's crucial to close the persistent cache to ensure data is written to disk.
pc_for_square.close()
print("\n--- Cache closed and reopened to demonstrate persistence ---")
# Simulate a new application run or process by creating a new PersistentCache instance
# linked to the *same* file.
pc_reopened = PersistentCache(LRUCache, cache_file, maxsize=100)
@cachetools.cached(pc_reopened)
def square_reopened(x):
# This should *not* print if the value was correctly persisted
print(f"Calculating square (reopened) for {x}...")
return x * x
# This call should retrieve from the persisted cache without recalculating
result_reopened = square_reopened(5)
print(f"Reopened call: square_reopened(5) = {result_reopened}")
pc_reopened.close()
# --- Final cleanup of created cache files ---
for ext in ["", ".bak", ".dir", ".dat"]:
if os.path.exists(cache_file + ext):
os.remove(cache_file + ext)