https://github.com/arioch3131/omni-cache
Unified Python cache and object-pool manager with pluggable backends, routing, async support, and production-ready decorators.
https://github.com/arioch3131/omni-cache
asyncio backend cache caching decorators in-memory-cache library memcached object-pool observability performance redis routing
Last synced: 23 days ago
JSON representation
Unified Python cache and object-pool manager with pluggable backends, routing, async support, and production-ready decorators.
- Host: GitHub
- URL: https://github.com/arioch3131/omni-cache
- Owner: arioch3131
- License: mit
- Created: 2026-03-01T17:31:41.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-06T21:17:16.000Z (about 1 month ago)
- Last Synced: 2026-05-06T23:28:29.662Z (about 1 month ago)
- Topics: asyncio, backend, cache, caching, decorators, in-memory-cache, library, memcached, object-pool, observability, performance, redis, routing
- Language: Python
- Homepage:
- Size: 507 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Omni-Cache
**Universal cache and pool manager with pluggable backends**
Omni-Cache is a comprehensive caching and object pooling library that provides a unified interface for multiple backends, intelligent routing, and production-ready features. Whether you need simple function memoization or enterprise-grade distributed caching, Omni-Cache scales with your needs.
## Important Notice
**This is a personal project.** While the code is provided as-is and functional, please note:
- There will be **limited to no ongoing maintenance** of this project
- You are **free to fork** this repository and adapt it to your needs
- All usage must comply with the **MIT license** (see [LICENSE](LICENSE))
Feel free to use, modify, and distribute this code according to the license terms.
External changes are not actively managed and may not be reviewed or merged.
## Overview
Omni-Cache follows a modular architecture similar to SmartPool, with clear separation between core services, adapter implementations, and user-facing utilities.
## Features
- **Multiple Backends**: Memory, Redis, Memcached, AdaptiveMemoryPool, and custom adapters
- **Unified Interface**: Single API for all cache and pool operations
- **Intelligent Routing**: Namespace-based routing with automatic fallback
- **Comprehensive Monitoring**: Built-in statistics, health checks, and performance metrics
- **Hot-Reloadable Configuration**: YAML, JSON, TOML, and environment variable support
- **Production-Ready Decorators**: `@cached`, `@pooled`, `@memoize` with advanced features
- **Async Support**: Full async/await compatibility
- **Type Safety**: Complete type hints and runtime validation
- **Enterprise Features**: Connection pooling, retry logic, circuit breakers
## When Should You Use This?
### Perfect For
- High-throughput APIs with repeated expensive reads
- Multi-backend systems (in-memory + Redis + pooled resources)
- Services that need observability on cache/pool behavior
- Projects requiring routing and centralized cache configuration
### Probably Overkill For
- Small scripts with minimal recomputation cost
- Single-threaded workloads with low cache churn
- Short-lived one-off jobs where setup overhead dominates
### Rule of Thumb
If your application repeatedly computes or fetches costly values and you need operational control (routing, monitoring, retries), Omni-Cache is a good fit.
## Installation
```bash
# Basic installation
pip install omni_cache
# With Redis support
pip install omni_cache[redis]
# With all optional dependencies
pip install omni_cache[all]
```
## Quick Start
### Ultra-Simple Usage
```python
from omni_cache import cached
@cached(ttl=300) # Cache for 5 minutes
def expensive_function(x, y):
# This will only run once per unique (x, y) combination
return complex_computation(x, y)
# Use it normally
result = expensive_function(1, 2) # Computed and cached
result = expensive_function(1, 2) # Retrieved from cache
```
### Quick Setup
```python
from omni_cache import setup, cached
# One-line setup with auto-discovery
manager = setup(auto_discover=True)
@cached(ttl=300, namespace="api")
def fetch_user_data(user_id):
return requests.get(f"/api/users/{user_id}").json()
```
## Basic Usage
### Function Caching
```python
from omni_cache import cached, memoize, timed_cache
# Basic caching with TTL
@cached(ttl=300, namespace="database")
def get_user(user_id):
return database.query("SELECT * FROM users WHERE id = ?", user_id)
# Simple memoization
@memoize(maxsize=1000)
def fibonacci(n):
if n < 2: return n
return fibonacci(n-1) + fibonacci(n-2)
# Time-based caching
@timed_cache(seconds=60)
def get_current_weather():
return weather_api.get_current()
# Cache management
get_user.invalidate(123) # Invalidate specific call
get_user.invalidate_all() # Clear all cached results
stats = get_user.cache_info() # Get cache statistics
```
### Object Pooling
```python
from omni_cache import pooled
@pooled(adapter="db_pool", timeout=5.0, max_retries=3)
def query_database(connection, query, params):
# Connection automatically borrowed and returned
return connection.execute(query, params).fetchall()
# Context manager for manual control
from omni_cache import get_global_manager
manager = get_global_manager()
with manager.borrow(adapter="db_pool") as connection:
result = connection.execute("SELECT 1").fetchone()
# Connection automatically returned to pool
```
### Direct Manager Usage
```python
from omni_cache import get_global_manager
manager = get_global_manager()
# Cache operations
manager.set("user:123", user_data, ttl=300)
user = manager.get("user:123")
exists = manager.exists("user:123")
manager.delete("user:123")
# Batch operations
users = manager.get_many(["user:123", "user:456"])
manager.set_many({"user:789": data1, "user:101": data2}, ttl=300)
# Pool operations
obj = manager.get(timeout=5.0, adapter="pool")
manager.put(obj, adapter="pool")
```
## Advanced Usage
### Multi-Backend Setup
```python
from omni_cache import CacheManager, create_adapter, CacheBackend
# Create manager
manager = CacheManager()
# Add memory cache for hot data
memory_adapter = create_adapter(CacheBackend.MEMORY, {
"max_size": 10000,
"eviction_policy": "lru"
})
manager.register_adapter("fast", memory_adapter)
# Add Redis for persistent cache
redis_adapter = create_adapter(CacheBackend.REDIS, {
"host": "localhost",
"port": 6379,
"db": 0
})
manager.register_adapter("persistent", redis_adapter)
# Add SmartPool for database connections
def create_db_connection():
return psycopg2.connect(DATABASE_URL)
pool_adapter = create_adapter(CacheBackend.SMARTPOOL, {
"factory_function": create_db_connection,
"initial_size": 5,
"max_size": 20
})
manager.register_adapter("db_pool", pool_adapter)
```
### Intelligent Routing
```python
# Set up routing rules
manager.add_routing_rule("cache", "fast") # cache:* → memory
manager.add_routing_rule("store", "persistent") # store:* → redis
manager.add_routing_rule("session", "fast") # session:* → memory
# Automatic routing based on key patterns
manager.set("cache:temp_data", data) # → fast (memory)
manager.set("store:user_profile", user) # → persistent (redis)
manager.set("session:abc123", session) # → fast (memory)
# Use with decorators
@cached(ttl=60, namespace="cache") # → routed to memory
def get_temporary_data():
return generate_temp_data()
@cached(ttl=3600, namespace="store") # → routed to redis
def get_user_profile(user_id):
return database.get_user(user_id)
```
### Configuration-Driven Setup
```yaml
# config.yaml
global:
log_level: "INFO"
enable_routing: true
default_cache_adapter: "redis"
health_check_interval: 60
adapters:
memory:
backend: "memory"
enabled: true
extra_config:
max_size: 10000
eviction_policy: "lru"
redis:
backend: "redis"
enabled: true
extra_config:
host: "localhost"
port: 6379
db: 0
db_pool:
backend: "smartpool"
enabled: true
extra_config:
initial_size: 5
max_size: 20
factory_function: "myapp.database.create_connection"
```
```python
from omni_cache import ConfigManager, CacheManager
# Load configuration
config_manager = ConfigManager("config.yaml")
config_manager.enable_hot_reload() # Auto-reload on file changes
# Create manager from configuration
manager = setup(config_file="config.yaml", enable_hot_reload=True)
# Configuration is automatically applied
@cached(ttl=300) # Uses configured routing and backends
def my_function():
return expensive_operation()
```
## Configuration
### Configuration Files
Omni-Cache supports YAML, JSON, and TOML configuration files:
```yaml
# omni_cache.yaml
global:
log_level: "INFO"
default_cache_adapter: "memory"
enable_routing: true
namespace_separator: ":"
health_check_interval: 60.0
debug_mode: false
adapters:
memory:
backend: "memory"
enabled: true
auto_connect: true
extra_config:
max_size: 10000
default_ttl: 3600
eviction_policy: "lru"
cleanup_interval: 60
redis:
backend: "redis"
enabled: true
extra_config:
host: "localhost"
port: 6379
db: 0
password: null
socket_timeout: 5.0
connection_pool_max_connections: 10
```
### Environment Variables
```bash
# Global settings
export OMNI_CACHE_LOG_LEVEL=DEBUG
export OMNI_CACHE_DEFAULT_CACHE_ADAPTER=redis
export OMNI_CACHE_ENABLE_ROUTING=true
# Adapter settings
export OMNI_CACHE_ADAPTERS_REDIS_EXTRA_CONFIG_HOST=redis.example.com
export OMNI_CACHE_ADAPTERS_REDIS_EXTRA_CONFIG_PORT=6380
# Auto-setup
export OMNI_CACHE_AUTO_SETUP=true
```
### Programmatic Configuration
```python
from omni_cache import ConfigManager, GlobalConfig, AdapterConfig
# Create configuration manager
config_manager = ConfigManager()
# Update global configuration
config_manager.update_global_config({
"log_level": "DEBUG",
"enable_routing": True
})
# Add adapter configuration
config_manager.add_adapter_config("redis", {
"backend": "redis",
"enabled": True,
"extra_config": {
"host": "redis.example.com",
"port": 6379
}
})
# Hot reload from file
config_manager.load_config("new_config.yaml")
```
## Available Backends
### Memory (Built-in)
- **No dependencies**: Always available
- **Features**: LRU/FIFO/Random eviction, TTL support, size limits
- **Use case**: Single-process caching, development, testing
```python
memory_adapter = create_adapter(CacheBackend.MEMORY, {
"max_size": 10000,
"eviction_policy": "lru",
"default_ttl": 3600
})
```
### Redis (Optional)
- **Dependency**: `pip install redis`
- **Features**: Distributed caching, persistence, pub/sub
- **Use case**: Multi-process/server caching, session storage
```python
redis_adapter = create_adapter(CacheBackend.REDIS, {
"host": "localhost",
"port": 6379,
"db": 0,
"password": "secret"
})
```
### SmartPool (Optional)
- **Dependency**: The `smartpool` package
- **Features**: Dynamic sizing, object lifecycle management, adaptive pooling
- **Use case**: Database connections, expensive object pooling
```python
def create_connection():
return psycopg2.connect(DATABASE_URL)
pool_adapter = create_adapter(CacheBackend.SMARTPOOL, {
"factory_function": create_connection,
"initial_size": 5,
"max_size": 20,
"min_size": 2
})
```
### Custom Backends
Create your own adapters by implementing the appropriate base class:
```python
from omni_cache.adapters.base import BaseCacheAdapter
class MyCustomAdapter(BaseCacheAdapter):
def _do_connect(self) -> bool:
# Implementation
return True
def _do_disconnect(self) -> bool:
# Implementation
return True
def _do_health_check(self) -> bool:
# Implementation
return True
def _set_internal(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
# Your cache set implementation
return True
def _get_internal(self, key: str) -> Any:
# Your cache get implementation
return None
def _delete_internal(self, key: str) -> bool:
# Your cache delete implementation
return True
def _clear_internal(self) -> bool:
# Your cache clear implementation
return True
```
### Adapter Scaffold Script
For simple adapters, you can generate a full boilerplate with:
```bash
python scripts/new_adapter.py my_backend --dependency my-lib
```
This generates:
- `src/omni_cache/adapters/my_backend/{__init__.py,config.py,my_backend.py,factory.py}`
- `tests/unit/adapters/my_backend/{test_my_backend_config.py,test_my_backend_factory.py,test_my_backend_adapter.py}`
Then complete the integration by:
1. Exporting the adapter in `src/omni_cache/adapters/__init__.py`
2. Registering the factory in `src/omni_cache/core/factories/factory_registry.py`
3. Exporting the factory in `src/omni_cache/core/factories/__init__.py`
4. Adding dependency and entry point in `pyproject.toml`
## Monitoring and Statistics
### Cache Statistics
```python
from omni_cache import get_global_manager
from omni_cache.utils.decorators import get_cache_stats
# Function-level statistics
@cached(ttl=300)
def my_function():
return expensive_operation()
stats = my_function.cache_info()
print(f"Hit rate: {stats['hit_rate']:.2%}")
# Manager-level statistics
manager = get_global_manager()
global_stats = manager.get_global_stats()
print(f"Total hits: {global_stats['cache'].hits}")
print(f"Total misses: {global_stats['cache'].misses}")
# Adapter-specific statistics
redis_stats = manager.get_adapter_stats("redis")
```
### Health Monitoring
```python
# Health checks
manager = get_global_manager()
# Check specific adapter
redis_adapter = manager.get_adapter("redis")
is_healthy = redis_adapter.health_check()
# Get backend information
info = redis_adapter.get_backend_info()
print(f"Connection state: {info['state']}")
print(f"Last health check: {info['last_health_check']}")
# Global health monitoring runs automatically
# Check logs for health status updates
```
### Performance Profiling
```python
# Enable detailed logging and profiling
from omni_cache import setup
manager = setup(
config_file="config.yaml",
log_level="DEBUG" # Shows detailed operation logs
)
# Use profile_operations in configuration
global_config = {
"profile_operations": True,
"enable_detailed_logging": True
}
```
## Decorator Features
### Advanced Caching Options
```python
from omni_cache import cached, cache_key
# Custom key generation
@cache_key(lambda user_id, include_inactive: f"user:{user_id}:active:{not include_inactive}")
@cached(ttl=300)
def get_user_data(user_id, include_inactive=False):
return database.get_user(user_id, include_inactive)
# Ignore specific arguments
@cached(
ttl=300,
ignore_args={0}, # Ignore first argument (self)
ignore_kwargs={"debug"} # Ignore debug flag
)
def process_data(self, data, debug=False):
return expensive_processing(data)
# Custom serialization
import pickle
@cached(
ttl=300,
serializer=pickle.dumps,
deserializer=pickle.loads
)
def get_complex_object():
return ComplexObject()
# Event callbacks
@cached(
ttl=300,
on_hit=lambda key, value: logger.info(f"Cache hit: {key}"),
on_miss=lambda key: logger.info(f"Cache miss: {key}"),
on_error=lambda exc: logger.error(f"Cache error: {exc}")
)
def monitored_function():
return get_data()
```
### Retry Logic with Caching
```python
from omni_cache import retry_with_cache
@retry_with_cache(
max_retries=3,
retry_delay=1.0,
exponential_backoff=True,
cache_failures=True, # Cache failures to avoid repeated attempts
failure_ttl=60 # Cache failures for 60 seconds
)
def unreliable_api_call():
response = requests.get("https://unreliable-api.com/data")
return response.json()
```
### Async Support
```python
from omni_cache import async_cached
import aiohttp
@async_cached(ttl=300, namespace="api")
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
# Usage
data = await fetch_data("https://api.example.com/data")
```
## Utilities
### Cache Management
```python
from omni_cache.utils.decorators import clear_cache, invalidate_cache
# Clear by namespace
cleared_count = clear_cache(namespace="user_data")
# Invalidate by pattern
invalidate_cache(pattern="user:*")
# Clear specific adapter
clear_cache(adapter="redis")
# Clear everything
clear_cache()
```
### Discovery and Information
```python
from omni_cache import discover_backends, get_version_info, info
# See available backends
backends = discover_backends()
print(f"Available: {list(backends.keys())}")
# Version information
version_info = get_version_info()
print(f"Version: {version_info['version']}")
print(f"Redis support: {version_info['capabilities']['redis_adapter']}")
# Print detailed information
info() # Prints comprehensive system info
```
## Real-World Examples
### Web Application Cache
```python
from flask import Flask
from omni_cache import setup, cached, pooled
app = Flask(__name__)
# Setup caching
setup(config_file="web_cache.yaml", auto_discover=True)
@cached(ttl=300, namespace="api")
def get_user_profile(user_id):
return database.get_user_profile(user_id)
@cached(ttl=60, namespace="analytics")
def get_page_stats():
return analytics.get_daily_stats()
@pooled(adapter="db_pool", timeout=5.0)
def execute_query(connection, query, params):
return connection.execute(query, params).fetchall()
@app.route('/users/')
def user_profile(user_id):
profile = get_user_profile(user_id)
return jsonify(profile)
```
### Data Processing Pipeline
```python
from omni_cache import cached, memoize, get_global_manager
import pandas as pd
# Setup manager with multiple backends
manager = setup(config_file="pipeline.yaml")
@cached(ttl=3600, namespace="raw_data", adapter="persistent")
def load_dataset(filename):
"""Cache expensive data loading operations"""
return pd.read_csv(filename)
@memoize(maxsize=1000)
def calculate_statistics(data_hash):
"""Memoize statistical calculations"""
return data.describe()
@cached(ttl=1800, namespace="processed", adapter="fast")
def process_batch(batch_id):
"""Cache processed results"""
raw_data = load_dataset(f"batch_{batch_id}.csv")
return expensive_processing(raw_data)
# Pipeline execution
for batch_id in range(100):
result = process_batch(batch_id) # Cached on subsequent runs
stats = calculate_statistics(hash(str(result)))
```
### Microservices Communication
```python
from omni_cache import cached, async_cached, retry_with_cache
import aiohttp
# Cache service responses
@async_cached(ttl=300, namespace="service_a")
async def call_service_a(endpoint, params):
async with aiohttp.ClientSession() as session:
async with session.get(f"http://service-a/{endpoint}", params=params) as response:
return await response.json()
@retry_with_cache(max_retries=3, cache_failures=True, failure_ttl=60)
@cached(ttl=60, namespace="service_b")
def call_service_b(data):
response = requests.post("http://service-b/process", json=data)
return response.json()
# Circuit breaker pattern with cached fallbacks
@cached(ttl=3600, namespace="fallback")
def get_fallback_data(key):
return default_responses.get(key)
def resilient_service_call(key):
try:
return call_service_a(key)
except Exception:
return get_fallback_data(key)
```
## Documentation
Build the documentation locally:
```bash
bash scripts/build_docs.sh
```
Or with Sphinx directly:
```bash
cd docs
make html
```
Generated docs are available in `docs/_build/html/`.
## Examples
Example programs are available in [`examples/`](examples/) with usage patterns for:
- Web applications
- Data processing pipelines
- Microservice communication
- Adapter and routing configurations
Start with [`examples/README.md`](examples/README.md) for an index.
## Testing
### Test Configuration
```python
# test_config.yaml
global:
log_level: "DEBUG"
default_cache_adapter: "memory"
adapters:
memory:
backend: "memory"
enabled: true
extra_config:
max_size: 100 # Small cache for testing
```
### Unit Tests
```python
import pytest
from omni_cache import setup, cached, temporary_config
def test_caching():
# Setup test environment
setup(config_file="test_config.yaml")
call_count = 0
@cached(ttl=60)
def test_function(x):
nonlocal call_count
call_count += 1
return x * 2
# Test caching behavior
result1 = test_function(5)
result2 = test_function(5)
assert result1 == result2 == 10
assert call_count == 1 # Function called only once
def test_with_temporary_config():
with temporary_config({"debug_mode": True}):
# Test with debug configuration
pass
# Original configuration restored
```
### Run Test Suites
```bash
# Unit tests
pytest tests/unit/
# Integration tests
pytest tests/integration/
# Performance tests
pytest tests/performance/
```
## Migration Guide
### From functools.lru_cache
```python
# Before (functools)
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_function(x):
return complex_computation(x)
# After (omni-cache)
from omni_cache import memoize
@memoize(maxsize=128)
def expensive_function(x):
return complex_computation(x)
# Benefits: TTL support, statistics, invalidation, persistence
```
### From Redis Direct Usage
```python
# Before (direct Redis)
import redis
r = redis.Redis()
def get_user(user_id):
key = f"user:{user_id}"
cached = r.get(key)
if cached:
return json.loads(cached)
user = database.get_user(user_id)
r.setex(key, 300, json.dumps(user))
return user
# After (omni-cache)
from omni_cache import cached
@cached(ttl=300, namespace="user", adapter="redis")
def get_user(user_id):
return database.get_user(user_id)
# Benefits: Automatic serialization, routing, fallback, monitoring
```
## Project Maintenance Policy
There is no active maintenance process for this repository.
Issues or pull requests may remain unanswered. If you need long-term evolution,
forking is the recommended path.
### Development Setup
```bash
# Navigate to project directory
cd omni-cache
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# Install development dependencies
pip install -e .[dev]
# Run tests
pytest
# Run linting
flake8 src/
mypy src/
# Run benchmarks (if available)
python scripts/benchmark.py
```
## Project Structure
```
omni_cache/
├── core/ # Core interfaces and manager
├── adapters/ # Backend implementations
├── utils/ # Decorators and utilities
└── __init__.py # Public API
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Inspired by Django's cache framework
- Built on the shoulders of Redis, Memcached, and other great projects
- Thanks to all users
## Resources
- **Documentation**: See `docs/` directory and inline docstrings
- **GitHub**: This repository
- **Issues**: Please file issues in this repository
- **Examples**: See `examples/` directory