netrun-cache
Redis and in-memory caching patterns for Netrun Systems portfolio.
Features
- RedisCache: Distributed Redis caching with namespace isolation, TTL management, and statistics
- MemoryCache: In-memory LRU cache with TTL support (fallback when Redis unavailable)
- CacheManager: Multi-layer cache with L1 (memory) and L2 (Redis) tiers
- Decorators:
@cachedfor automatic function result caching - Type Safety: Pydantic v2 models for configuration and statistics
- Optional Dependencies: Redis support is optional, works with memory cache only
Installation
Basic (memory cache only)
pip install netrun-cache
With Redis support
pip install netrun-cache[redis]
All features
pip install netrun-cache[all]
Quick Start
Memory Cache (No Redis Required)
from netrun.cache import MemoryCache
# Create cache instance
cache = MemoryCache(namespace="users", default_ttl=3600, max_size=1000)
await cache.initialize()
# Basic operations
await cache.set("user:123", {"name": "John", "email": "john@example.com"})
user = await cache.get("user:123")
await cache.delete("user:123")
# Batch operations
users = {"user:1": {"name": "Alice"}, "user:2": {"name": "Bob"}}
await cache.set_many(users, ttl=1800)
cached_users = await cache.get_many(["user:1", "user:2"])
# Atomic counters
await cache.increment("page_views")
await cache.decrement("inventory:item_123")
# Statistics
stats = await cache.get_stats()
print(f"Hit rate: {stats.hit_rate_percent}%")
print(f"Cached keys: {stats.cached_keys}")
Redis Cache
from netrun.cache import RedisCache, CacheConfig
# Using configuration object
config = CacheConfig(
namespace="api",
default_ttl=3600,
redis_host="localhost",
redis_port=6379,
redis_db=0,
redis_password="your_password"
)
cache = RedisCache(config=config)
await cache.initialize()
# Or simple initialization
cache = RedisCache(namespace="api", default_ttl=3600)
await cache.initialize()
# Same interface as MemoryCache
await cache.set("product:456", {"name": "Widget", "price": 29.99})
product = await cache.get("product:456")
# Complex key hashing
prompt = "What is the capital of France?"
key = cache._hash_key(prompt)
await cache.set(key, "Paris")
# Clear namespace
await cache.clear_namespace()
Multi-Layer Cache
from netrun.cache import CacheManager, CacheConfig
# L1 (memory) + L2 (Redis) caching
config = CacheConfig(
namespace="api",
default_ttl=3600, # L2 (Redis) TTL
l1_max_size=100, # L1 cache size
l1_ttl=300, # L1 TTL (5 minutes)
redis_host="localhost"
)
cache = CacheManager(config)
await cache.initialize()
# Automatically checks L1, then L2
user = await cache.get("user:789")
# Write-through to both layers
await cache.set("user:789", {"name": "Charlie"})
# Get statistics for both layers
stats = await cache.get_stats()
print(f"L1 hit rate: {stats['l1'].hit_rate_percent}%")
print(f"L2 hit rate: {stats['l2'].hit_rate_percent}%")
Decorator Pattern
from netrun.cache import cached
# Cache function results (memory backend)
@cached(ttl=3600, namespace="api", backend="memory")
async def get_user(user_id: str):
# Expensive database operation
return await db.fetch_user(user_id)
# Cache with Redis backend
@cached(ttl=1800, namespace="products", backend="redis", redis_host="localhost")
async def get_product(product_id: str):
return await db.fetch_product(product_id)
# Multi-layer caching
@cached(ttl=3600, namespace="search", backend="multi_layer")
async def search_items(query: str, limit: int = 10):
return await search_engine.query(query, limit)
# Use decorated functions normally
user = await get_user("123") # Cache miss, fetches from DB
user = await get_user("123") # Cache hit, returns cached value
Configuration
CacheConfig Model
from netrun.cache import CacheConfig, CacheBackend
config = CacheConfig(
backend=CacheBackend.MULTI_LAYER,
namespace="cache",
default_ttl=3600,
max_size=1000,
# Redis configuration
redis_url="redis://:password@localhost:6379/0", # Or individual params
redis_host="localhost",
redis_port=6379,
redis_db=0,
redis_password="secret",
socket_timeout=5,
socket_connect_timeout=5,
# Multi-layer configuration
l1_max_size=100,
l1_ttl=300,
)
CacheStats Model
stats = await cache.get_stats()
print(f"Namespace: {stats.namespace}")
print(f"Hits: {stats.hits}")
print(f"Misses: {stats.misses}")
print(f"Total requests: {stats.total_requests}")
print(f"Hit rate: {stats.hit_rate_percent}%")
print(f"Cached keys: {stats.cached_keys}")
print(f"Backend: {stats.backend}")
print(f"Connected: {stats.connected}")
Advanced Usage
Namespace Isolation
# Create isolated cache namespaces
user_cache = MemoryCache(namespace="users")
product_cache = MemoryCache(namespace="products")
# Keys don't conflict
await user_cache.set("123", {"name": "Alice"})
await product_cache.set("123", {"name": "Widget"})
# Clear specific namespace
await user_cache.clear_namespace() # Only clears user_cache
Key Hashing for Complex Data
from netrun.cache import RedisCache
cache = RedisCache(namespace="llm")
# Hash complex prompts for consistent keys
prompt = {
"model": "gpt-4",
"messages": [{"role": "user", "content": "Hello"}],
"temperature": 0.7
}
key = cache._hash_key(prompt) # SHA256 hash
await cache.set(key, "AI response here")
SET NX (Set if Not Exists)
# Distributed locking pattern
acquired = await cache.set("lock:resource_123", "locked", ttl=30, nx=True)
if acquired:
try:
# Critical section
await process_resource()
finally:
await cache.delete("lock:resource_123")
Graceful Shutdown
# Close connections properly
await cache.close()
API Reference
MemoryCache
initialize()- Initialize cacheget(key, track_stats=True)- Get valueset(key, value, ttl=None, nx=False)- Set valuedelete(key)- Delete keyexists(key)- Check if key existsexpire(key, ttl)- Update TTLget_many(keys)- Batch getset_many(items, ttl=None)- Batch setincrement(key, amount=1)- Atomic incrementdecrement(key, amount=1)- Atomic decrementclear_namespace()- Clear all keys in namespaceget_stats()- Get statisticsreset_stats()- Reset statisticsclose()- Close cache
RedisCache
Same API as MemoryCache, plus:
_hash_key(data)- Generate SHA256 hash for complex keys
CacheManager
Same API as MemoryCache/RedisCache with multi-layer behavior.
Performance Characteristics
MemoryCache
- Get: O(1) - OrderedDict lookup
- Set: O(1) - OrderedDict insert with LRU eviction
- Memory: Up to
max_sizeitems in RAM - Concurrency: Thread-safe with asyncio locks
RedisCache
- Get: O(1) - Redis GET
- Set: O(1) - Redis SETEX
- Network: Single round-trip per operation
- Batch: Pipeline reduces network overhead
- Scalability: Distributed across Redis cluster
CacheManager
- Get: L1 hit = O(1), L1 miss + L2 hit = O(1) + network
- Set: Write-through to both layers
- Hit Rate: Typically 80-95% L1, 99%+ combined
Development
Running Tests
cd /data/workspace/github/Netrun_Service_Library_v2/packages/netrun-cache
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
python -m pytest tests/ -v
# With coverage
python -m pytest tests/ -v --cov=netrun.cache --cov-report=term-missing
Test Requirements
- Unit tests work without Redis (use MemoryCache or fakeredis)
- Integration tests require Redis server running
- Use
@pytest.mark.integrationfor Redis-dependent tests
License
MIT License - Copyright (c) 2025 Netrun Systems
Links
- Homepage: https://netrunsystems.com
- Documentation: https://docs.netrunsystems.com/cache
- Repository: https://github.com/netrunsystems/netrun-cache
- Issues: https://github.com/netrunsystems/netrun-cache/issues
Metadata
Release files for netrun-cache 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| netrun_cache-1.0.0.tar.gz | 28.4 kB | Without provenance | Details for netrun_cache-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| netrun_cache-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for netrun_cache-1.0.0-py3-none-any.whl |
Total release size: 46.7 kB
Release files / netrun_cache-1.0.0.tar.gz
| Download URL | netrun_cache-1.0.0.tar.gz |
|---|---|
| Size | 28.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dab9ec9933b7d98c2440731456f039354f513756c8ff4c11d1b198b192ab7c2f
|
|
BLAKE2b-256 checksum How to use checksums |
6bcf11fa77b0510b89d4a9f11971a181cdac81488cca436c0469f147f21ee225
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|
Release files / netrun_cache-1.0.0-py3-none-any.whl
| Download URL | netrun_cache-1.0.0-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3dfa0606622362e4ebbbc2f889a5ff2e1dd41280778f8a7362533a440431d7e
|
|
BLAKE2b-256 checksum How to use checksums |
59437f16024853452d6f743f8d58fe10c9f58bc171964c8ad9d894c7edd20cab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|
Security for netrun-cache 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.