A persistent caching solution for arbitrary Python functions - like lru_cache on steroids.
flèche is French for 'arrow' and also used in fencing as a fast attack.
- Persistent Caching: Cache function results across runs using various storage backends
- Flexible Storage: Choose from file-based, SQL, in-memory, or custom storage solutions
- Intelligent Hashing: Automatically generates cache keys from function arguments
- Query Support: Search and retrieve cached results with metadata filtering
- Configurable: Control what gets hashed (version, module, code, arguments)
- Multiple Backends: File (pickle, cloudpickle, dill), SQLAlchemy, Bagofholding, and more
- Thread-Safe: Safe for use in multi-threaded environments
- Type-Aware: Works seamlessly with NumPy, Pandas, and custom types
pip install flechefleche is available on conda-forge. Two packages are
published:
fleche-base— the core library only (no optional dependencies)fleche— the full install, which also pulls in the optional dependencies (cloudpickle,dill,sqlalchemy, andbagofholding), enabling the SQL, SSH, alternate-serialization, and Bagofholding features out of the box
# Core library only
conda install -c conda-forge fleche-base
# Full install with all optional dependencies
conda install -c conda-forge fleche# For SQL storage
pip install fleche[sqlalchemy]
# For alternate serialization formats
pip install fleche[cloudpickle,dill]
# For Bagofholding storage
pip install fleche[bagofholding]
# For SshCache (sharing caches across machines over SSH) — requires cloudpickle
pip install fleche[ssh]
# For running cached calls through executorlib executors
pip install fleche[executorlib]
# For documentation
pip install fleche[docs]
# For development and testing
pip install fleche[tests]See the Optional dependencies section of the docs for a full table of extras and the features they enable.
from fleche import fleche
@fleche()
def expensive_function(x, y):
"""This function's results will be cached."""
print(f"Computing for {x}, {y}...")
return x + y
# First call computes the result
result = expensive_function(1, 2) # prints "Computing for 1, 2..."
# Second call retrieves from cache
result = expensive_function(1, 2) # no print - result from cache!
# Different arguments compute again
result = expensive_function(2, 3) # prints "Computing for 2, 3..."@fleche()
def compute(x):
return x ** 2
# Get the cache key (digest) for arguments
digest = compute.digest(5)
# Check if result is cached
if compute.contains(5):
result = compute.load(5)
else:
result = compute(5)
# Get Call object with metadata
call = compute.call(5)@fleche(
version=1, # Versioning for function changes
hash_version=True, # Include version in cache key
hash_module=True, # Include module name in cache key
hash_code=False, # Include function code in cache key
require=None, # Required argument for caching
ignore=None, # Arguments to ignore in cache key
)
def my_function(x):
return x * 2- Default when no
fleche.tomlconfiguration file is present - Transient: data is lost when the process exits
- Stores cache in filesystem using pickle (or cloudpickle/dill)
- Persistent across runs
- XDG Base Directory Specification compliant when configured
- Requires
sqlalchemy - Call storage only — stores call records (function name, arguments, metadata) in a SQL database; a separate value backend (file or memory) is still required for results
- SQLite is the primary tested backend; any SQLAlchemy-supported database should work
- Enables efficient server-side filtering when querying cached calls
- Requires
cloudpickle(pip install fleche[ssh]) — used as the wire protocol between client and remote server; not optional - Forwards every cache operation over a persistent
ssh host python -m fleche remote --servesubprocess - Stack with a local cache to read-through to a shared remote one — see
fleche.remote.SshCache
Implement the Storage interface to create custom backends.
Track function versions to invalidate cache when implementation changes:
@fleche(version=2)
def process_data(data):
# Incrementing version invalidates all v1 cache entries
return data * 2Skip certain arguments when generating cache keys:
@fleche(ignore=['verbose', 'debug'])
def compute(x, y, verbose=False, debug=False):
return x + y # Cache key only uses x and yFind cached results matching specific criteria (only after issuing a corresponding call):
@fleche()
def fetch_data(user_id, date):
return {"user": user_id, "date": date}
# Issue a call to cache the result
fetch_data(user_id=123, date='2024-01-01')
# Get all cached results for fetch_data
all_results = list(fetch_data.query())
# Get results where user_id=123, any date (omitted arguments act as wildcards)
results = list(fetch_data.query(user_id=123))
# Inspect a result
for call in fetch_data.query(user_id=123):
print(call.arguments, call.result)Use the included benchmarks to evaluate performance:
python -m benchmarks.run_benchmarksRun the test suite:
pip install "fleche[tests]"
pytest tests/Contributions are welcome! Please ensure tests pass and code follows the project style.
BSD-3-Clause License - see LICENSE file for details.
- Documentation: See
docs/directory - Tests: See
tests/for usage examples - Benchmarks: See
benchmarks/for performance metrics