Skip to main content

Persistent JSON caching for Python with async support - cache function results and object state effortlessly.

Project description

Cacherator

Persistent JSON caching for Python with async support - Cache function results and object state effortlessly.

Python 3.7+ License: MIT

Overview

Cacherator is a Python library that provides persistent JSON-based caching for class state and function results. It enables developers to cache expensive operations with minimal configuration, supporting both synchronous and asynchronous functions.

Key Features

  • Zero-configuration caching - Simple inheritance and decorator pattern
  • Async/await support - Native support for asynchronous functions
  • Persistent storage - Cache survives program restarts
  • TTL (Time-To-Live) - Automatic cache expiration
  • Selective caching - Fine-grained control over what gets cached
  • Cache management - Built-in methods for inspection and clearing
  • Flexible logging - Global and per-instance control

Installation

pip install cacherator

Quick Start

Basic Function Caching

from cacherator import JSONCache, Cached
import time

class Calculator(JSONCache):
    def __init__(self):
        super().__init__(data_id="calc")
    
    @Cached()
    def expensive_calculation(self, x, y):
        time.sleep(2)  # Simulate expensive operation
        return x ** y

calc = Calculator()
result = calc.expensive_calculation(2, 10)  # Takes 2 seconds
result = calc.expensive_calculation(2, 10)  # Instant!

Async Function Caching

class APIClient(JSONCache):
    @Cached(ttl=1)  # Cache for 1 day
    async def fetch_user(self, user_id):
        # Expensive API call
        response = await api.get(f"/users/{user_id}")
        return response.json()

client = APIClient()
user = await client.fetch_user(123)  # API call
user = await client.fetch_user(123)  # Cached!

State Persistence

class GameState(JSONCache):
    def __init__(self, game_id):
        super().__init__(data_id=f"game_{game_id}")
        if not hasattr(self, "score"):
            self.score = 0
            self.level = 1
    
    def add_points(self, points):
        self.score += points
        self.json_cache_save()

# Session 1
game = GameState("player1")
game.add_points(100)

# Session 2 (after restart)
game = GameState("player1")
print(game.score)  # 100 - persisted!

Advanced Usage

Custom TTL Configuration

class WeatherService(JSONCache):
    @Cached(ttl=0.25)  # 6 hours (0.25 days)
    def get_forecast(self, city):
        return fetch_weather(city)
    
    @Cached(ttl=30)  # 30 days
    def get_historical(self, city, year):
        return fetch_historical(city, year)

Excluding Variables from Cache

class DataProcessor(JSONCache):
    def __init__(self):
        self._excluded_cache_vars = ["temp_data", "api_key"]
        super().__init__()
        self.results = {}
        self.temp_data = []  # Won't be cached
        self.api_key = "secret"  # Won't be cached

Cache Management

processor = DataProcessor()

# Get cache statistics
stats = processor.json_cache_stats()
print(stats)
# {'total_entries': 5, 'functions': {'process': 3, 'analyze': 2}}

# Clear specific function cache
processor.json_cache_clear("process")

# Clear all cache
processor.json_cache_clear()

Logging Control

# Three logging levels available
from cacherator import JSONCache, LogLevel

# SILENT: No logging
JSONCache.set_logging(LogLevel.SILENT)

# NORMAL: Errors only (default)
JSONCache.set_logging(LogLevel.NORMAL)

# VERBOSE: All operations including save/load
JSONCache.set_logging(LogLevel.VERBOSE)

# Backward compatible boolean API
JSONCache.set_logging(False)  # SILENT
JSONCache.set_logging(True)   # NORMAL

# Per-instance control
processor = DataProcessor(logging=LogLevel.SILENT)

Configuration

JSONCache Constructor

JSONCache(
    data_id="unique_id",      # Unique identifier (default: class name)
    directory="cache",         # Cache directory (default: "data/cache")
    clear_cache=False,         # Clear existing cache on init
    ttl=999,                   # Default TTL in days
    logging=True               # Enable logging (True/False or LogLevel)
)

@Cached Decorator

@Cached(
    ttl=7,                     # Time-to-live in days (default: class ttl)
    clear_cache=False          # Clear cache for this function
)

Use Cases

API Client with Caching

class GitHubClient(JSONCache):
    def __init__(self):
        super().__init__(data_id="github_client", ttl=1)
    
    @Cached(ttl=0.5)  # 12 hours
    async def get_user(self, username):
        async with aiohttp.ClientSession() as session:
            async with session.get(f"https://api.github.com/users/{username}") as resp:
                return await resp.json()
    
    @Cached(ttl=7)  # 1 week
    async def get_repos(self, username):
        async with aiohttp.ClientSession() as session:
            async with session.get(f"https://api.github.com/users/{username}/repos") as resp:
                return await resp.json()

Database Query Caching

class UserRepository(JSONCache):
    def __init__(self):
        super().__init__(data_id="user_repo", ttl=0.1)  # 2.4 hours
    
    @Cached()
    def get_user_by_id(self, user_id):
        return db.query("SELECT * FROM users WHERE id = ?", user_id)
    
    @Cached(ttl=1)
    def get_user_stats(self, user_id):
        return db.query("SELECT COUNT(*) FROM posts WHERE user_id = ?", user_id)

Machine Learning Model Predictions

class ModelPredictor(JSONCache):
    def __init__(self):
        super().__init__(data_id="ml_predictor")
        self.model = load_model()
    
    @Cached(ttl=30)
    def predict(self, features_hash, features):
        # Cache predictions by feature hash
        return self.model.predict(features)

Best Practices

Recommended Use Cases

  • Expensive API calls and network requests
  • Database queries with relatively static data
  • Heavy computational operations
  • Machine learning model predictions
  • Data transformations and aggregations

When to Use TTL

  • Set short TTL (minutes to hours) for frequently changing data
  • Set long TTL (days to weeks) for stable reference data
  • Consider data freshness requirements for your application

What Not to Cache

  • Non-deterministic functions (random number generation, timestamps)
  • Very fast operations (overhead exceeds benefit)
  • Non-JSON-serializable objects without custom handling
  • Real-time data without appropriate TTL configuration

Performance

Cacherator introduces minimal overhead:

  • Cache hit: ~0.1ms
  • Cache miss: Function execution time + ~1ms
  • Disk I/O: Non-blocking, asynchronous operations

Performance Improvements

  • API calls (100ms - 5s) reduced to ~0.1ms
  • Database queries (10ms - 1s) reduced to ~0.1ms
  • Heavy computations (1s+) reduced to ~0.1ms

Compatibility

  • Python: 3.7 and above
  • Async: Full support for async/await syntax
  • Operating Systems: Windows, macOS, Linux
  • Data Types: All JSON-serializable types plus datetime objects

Changelog

Version 1.1.1

  • Fixed: Critical race condition bug in @Cached() decorator for concurrent async operations
  • Added: Three-level logging system (SILENT, NORMAL, VERBOSE)
  • Added: LogLevel enum for granular logging control
  • Improved: Code refactoring for better maintainability
  • Added: Comprehensive test suite with 39 tests including race condition tests

Troubleshooting

Cache Not Persisting

# Explicitly save cache
obj.json_cache_save()

# Check for serialization errors
obj._excluded_cache_vars = ["problematic_attr"]

Cache Not Being Used

# Verify TTL hasn't expired
obj = MyClass(ttl=30)  # Increase TTL

# Ensure arguments are identical (type matters)
obj.func(1, 2)    # Different from
obj.func(1.0, 2)  # (int vs float)

Large Cache Files

# Exclude large attributes
self._excluded_cache_vars = ["large_data"]

# Use separate cache instances
processor1 = DataProcessor(data_id="dataset1")
processor2 = DataProcessor(data_id="dataset2")

Contributing

Contributions are welcome. Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE file for details.

Resources


Developed by Arved Klöhn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cacherator-1.1.1.tar.gz (20.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cacherator-1.1.1-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file cacherator-1.1.1.tar.gz.

File metadata

  • Download URL: cacherator-1.1.1.tar.gz
  • Upload date:
  • Size: 20.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for cacherator-1.1.1.tar.gz
Algorithm Hash digest
SHA256 3a5a5401371f9d6959e830aece40cbcd21917ac65c6d38b3813a538203fb2874
MD5 fc4e436ceb0a4078609e14d4fbb9e315
BLAKE2b-256 61641e683bdf89373409e31915068a2d5368c0520669e551204507d680337afd

See more details on using hashes here.

File details

Details for the file cacherator-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: cacherator-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for cacherator-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 988176755cbff6f4a0225bbee74a73b809d2914d9b3c35e98bbe1b7c4d926fbf
MD5 b22b0c0d6fee5a763c7edee534512033
BLAKE2b-256 11c81fec711dace2a748cb0833d557430b54a34d249116d8cf47112fdfc6b5a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page