Cache Framework
OpenViper provides a simple, robust caching abstraction that can be used directly or within your routing and background task layers. The core cache interacts asynchronously with the underlying store, ensuring non-blocking operations.
Getting Started
The simplest way to use the cache is to import the get_cache factory. It will automatically load the backend configured in your settings.
from openviper.cache import get_cache
async def my_view(request):
cache = get_cache()
# Check if we have cached data
if await cache.has_key("my_expensive_data"):
return await cache.get("my_expensive_data")
# Compute and cache
data = await compute_expensive_data()
await cache.set("my_expensive_data", data, ttl=300) # cache for 5 minutes
return data
Configuration
OpenViper uses a CACHES dictionary in settings.py
OPTIONS:
CACHES = {
"default": {
"BACKEND": "openviper.cache.InMemoryCache",
"OPTIONS": {"ttl": 300},
},
"redis": {
"BACKEND": "openviper.cache.RedisCache",
"OPTIONS": {"host": "localhost", "port": 6379, "db": 0},
},
"memcached": {
"BACKEND": "openviper.cache.MemcachedCache",
"OPTIONS": {"host": "localhost", "port": 11211},
},
"file": {
"BACKEND": "openviper.cache.FileCache",
"OPTIONS": {"cache_dir": ".cache/openviper"},
},
"dragonfly": {
"BACKEND": "openviper.cache.DragonflyCache",
"OPTIONS": {"host": "localhost", "port": 6379, "db": 0},
},
}
The ttl option in OPTIONS sets the default time-to-live in seconds
for backends that support it (InMemoryCache uses this as its default TTL).
Thread-safe singleton access is guaranteed via an internal lock.
Custom backends can be registered by dotted path:
CACHES = {
"custom": {
"BACKEND": "myapp.cache.MyCustomCache",
"OPTIONS": {"endpoint": "custom-host:1234"},
},
}
Built-in Backends
OpenViper ships with six cache backends covering local development through high-concurrency production deployments.
InMemoryCache
The default cache. Stores data in a Python dictionary. Suitable for local development or single-process deployments without critical cache persistence needs.
RedisCache
A production-ready asynchronous Redis backend.
Requirements: You must install the redis library (e.g., pip install redis).
CACHES = {
"default": {
"BACKEND": "openviper.cache.RedisCache",
"OPTIONS": {"host": "localhost", "port": 6379, "db": 0},
},
}
All keys are prefixed with ov:cache: by default. The clear() method
uses SCAN and UNLINK to delete only matching keys - it never calls
FLUSHDB.
MemcachedCache
An asynchronous Memcached backend using the aiomcache library.
Requirements: You must install the aiomcache library (e.g., pip install aiomcache).
CACHES = {
"default": {
"BACKEND": "openviper.cache.MemcachedCache",
"OPTIONS": {"host": "localhost", "port": 11211},
},
}
All keys are prefixed with ov:cache: by default. Note that Memcached
does not support prefix-based key iteration, so clear() calls
flush_all which clears the entire Memcached instance. Use separate
Memcached instances or key prefixes to isolate data.
FileCache
A file-system-backed cache that stores each entry as a separate file under a configurable directory. Values are serialized with orjson and expired entries are lazily removed on access.
CACHES = {
"default": {
"BACKEND": "openviper.cache.FileCache",
"OPTIONS": {"cache_dir": ".cache/openviper"},
},
}
Suitable for single-server deployments or development environments where Redis/Memcached are not available. Not recommended for multi-server setups because the cache directory is local to each node. Keys are hex-encoded to prevent directory traversal attacks.
DatabaseCache
A database-backed cache using the OpenViper ORM. Values are serialized with
orjson and stored in the openviper_cache_entries table.
CACHES = {
"db": {
"BACKEND": "openviper.cache.DatabaseCache",
},
}
Supports PostgreSQL (INSERT ... ON CONFLICT upsert), SQLite (INSERT OR
REPLACE), and a fallback ORM-based upsert for other dialects. Expired
entries are lazily cleaned up on access.
DragonflyCache
A Dragonfly backend that inherits from RedisCache and uses the
redis.asyncio client library. Dragonfly is a modern in-memory data store
that speaks the Redis protocol and offers significantly higher multi-threaded
throughput.
Requirements: You must install the redis library (e.g., pip install redis).
CACHES = {
"default": {
"BACKEND": "openviper.cache.DragonflyCache",
"OPTIONS": {"host": "localhost", "port": 6379, "db": 0},
},
}
All keys are prefixed with ov:df: by default to isolate them from Redis
data in the same instance. The clear() method uses SCAN and
UNLINK for safe, non-blocking removal.
Creating Custom Backends
If you need to store your cache in a different system (like AWS ElastiCache or a custom distributed store), you can build a custom backend.
Create a class that inherits from
openviper.cache.base.BaseCache.Implement the required async methods:
get,set,delete,has_key, andclear.
# myapp/cache.py
from typing import Any
from openviper.cache.base import BaseCache
class ElasticacheBackend(BaseCache):
def __init__(self, *, endpoint: str, port: int = 6379):
# ... initialize connection
pass
async def get(self, key: str, default: Any = None) -> Any:
# ... read from ElastiCache
pass
async def set(self, key: str, value: Any, ttl: int | None = None) -> None:
# ... write to ElastiCache
pass
async def delete(self, key: str) -> None:
pass
async def has_key(self, key: str) -> bool:
pass
async def clear(self) -> None:
pass
Point your settings to the custom backend using a dotted module path:
# settings.py
CACHES = {
"default": {
"BACKEND": "myapp.cache.ElasticacheBackend",
"OPTIONS": {"endpoint": "my-cluster.xxxxxx.use1.cache.amazonaws.com"},
},
}
When get_cache() is called, OpenViper will dynamically import and instantiate your class.
API Reference
openviper.cache
- get_cache(alias='default') BaseCache
Return the cache backend for the given alias, creating it on first access. Instances are stored in
cache_instancesand reused on subsequent calls. Thread-safe viacache_lock."default"alias with noCACHESsetting returns anInMemoryCache.Unknown non-default aliases raise
ValueError.
- cache_instances
Module-level
dict[str, BaseCache]holding instantiated backends. Populated byget_cache().
- cache_lock
threading.Lockguarding concurrent access tocache_instances.
openviper.cache.base
- class BaseCache
Abstract base class for all cache backends. Concrete subclasses must call
validate_cache_key(key)before any operation.- async get(key, default=None) -> Any
Fetch a value from the cache. Return default on miss.
- async set(key, value, ttl=None) -> None
Store a value. ttl is time-to-live in seconds;
Nonemeans no expiry.
- async delete(key) -> None
Remove a value from the cache.
- async clear() -> None
Remove all values from the cache.
- async has_key(key) -> bool
Check whether a key exists. Default calls
get()and checks forNone; backends with a cheaper existence check should override.
- async keys(prefix="") -> list[str]
Return all cache keys, optionally filtered by prefix. Default returns
[]; backends that can enumerate keys should override. The in-memory implementation performs a single-pass sweep: expired entries are evicted and matching keys are collected in one iteration over the data store.
- async close() -> None
Release backend resources (connections, file handles). Backends that hold persistent connections (e.g.
RedisCache,MemcachedCache) override this method to ensure clean shutdown. The default implementation is a no-op for backends that do not own connections.
openviper.cache.memory
- class InMemoryCache(BaseCache)
In-memory cache backed by a
dict. Thread-safe for concurrent async access via anasyncio.Lock. When no ttl is provided toset(),CACHES['default']['OPTIONS']['ttl']is used as the default.
openviper.cache.redis
- class RedisCache(BaseCache)
Redis-backed cache using
redis.asynciowith orjson serialization. Requires theredispackage. All keys are prefixed withkey_prefix(default"ov:cache:").- __init__(*, key_prefix='ov:cache:', **kwargs)
Initialise the Redis client. Keyword arguments are forwarded to
redis.asyncio.Redis().
openviper.cache.memcached
- class MemcachedCache(BaseCache)
Memcached-backed cache using
aiomcachewith orjson serialization. Requires theaiomcachepackage. All keys are prefixed withkey_prefix(default"ov:cache:").- __init__(*, key_prefix='ov:cache:', host='localhost', port=11211, **kwargs)
Initialise the aiomcache client. Keyword arguments are forwarded to
aiomcache.Client().
Note
clear()callsflush_allwhich clears the entire Memcached instance. Use separate instances or key prefixes to isolate data.
openviper.cache.file
- class FileCache(BaseCache)
File-system-backed cache using async I/O with orjson serialization. Each entry is stored as a separate file under
cache_dir. Keys are hex-encoded to prevent directory traversal attacks. Expired entries are lazily removed on access.- __init__(*, cache_dir='.cache/openviper', key_prefix='ov:cache:', **kwargs)
Initialise the file cache with a directory path and optional prefix.
openviper.cache.db_backend
- class DatabaseCache(BaseCache)
Database-backed cache using the OpenViper ORM with orjson serialization. Supports PostgreSQL, SQLite, and fallback ORM-based upsert.
openviper.cache.dragonfly
- class DragonflyCache(RedisCache)
Dragonfly-backed cache inheriting from
RedisCache. Usesredis.asynciowith orjson serialization. Requires theredispackage (Dragonfly speaks the Redis protocol). All keys are prefixed withkey_prefix(default"ov:df:").- __init__(*, key_prefix='ov:df:', host='localhost', port=6379, db=0, **kwargs)
Initialise the Dragonfly cache. Delegates to
RedisCache.__init__with Dragonfly-specific defaults.
Note
clear()usesSCANandUNLINKto delete only matching keys - it never callsFLUSHDB.
openviper.cache.redis
- class RedisCache(BaseCache, *, key_prefix='ov:cache:', **kwargs)
Redis-backed cache using
redis.asynciowith orjson serialization. Requires theredispackage (pip install redis).All keys are prefixed with key_prefix to isolate this cache from other data in the same Redis database.
clear()only deletes keys matching the prefix viaSCAN+UNLINK- it never callsFLUSHDB.
- DEFAULT_KEY_PREFIX
Default Redis key prefix:
"ov:cache:".
openviper.cache.db_backend
- class DatabaseCache(BaseCache)
Database-backed cache using the OpenViper ORM with orjson serialization. Supports PostgreSQL (
INSERT ... ON CONFLICT), SQLite (INSERT OR REPLACE), and a fallback ORM-based upsert for other dialects.
openviper.cache.db
openviper.cache.validation
- validate_cache_key(key) str
Validate and return a cache key. Raises
ValueErrorif the key is empty, exceedsCACHE_KEY_MAX_LENcharacters, or contains whitespace.
- CACHE_KEY_MAX_LEN
Maximum allowed cache key length:
250.
- CACHE_KEY_RE
Compiled regex
^\\S+$used to reject whitespace in cache keys.