Examples¶
Here's a small list of possible usage applications.
Remember to make sure to call init_cachify (for global decorators) or create a local instance with init_cachify(is_global=False) when you need an isolated cache.
cached decorator (global usage)¶
from py_cachify import cached, init_cachify
# Configure global Cachify instance for top-level decorators
init_cachify()
@cached(key='example_key', ttl=60)
def expensive_function(x: int) -> int:
print('Executing expensive operation...')
return x ** 2
@cached(key='example_async_function-{arg_a}-{arg_b}')
async def async_expensive_function(arg_a: int, arg_b: int) -> int:
print('Executing async expensive operation...')
return arg_a + arg_b
# Reset the cache for a specific call
expensive_function.reset(10)
cached with default_cache_ttl¶
from py_cachify import cached, init_cachify
# Configure a global default TTL of 300 seconds
init_cachify(default_cache_ttl=300)
# Uses default_cache_ttl=300 because ttl is omitted
@cached(key='profile-{user_id}')
def get_profile(user_id: int) -> dict:
...
# Never expires, even though default_cache_ttl is set
@cached(key='feature-flags', ttl=None)
def get_feature_flags() -> dict:
...
cached with instance-based usage¶
from py_cachify import init_cachify
# Create a dedicated instance that does not affect the global client
local_cachify = init_cachify(is_global=False, prefix='LOCAL-', default_cache_ttl=10)
@local_cachify.cached(key='local-expensive-{x}')
def local_expensive_function(x: int) -> int:
print('Executing local expensive operation...')
return x ** 3
cached multi-layer usage¶
from py_cachify import cached, init_cachify
# Global initialization (used by the top-level @cached)
init_cachify(default_cache_ttl=60)
# Local instance that adds a shorter TTL on top of the global cache
local_cachify = init_cachify(is_global=False, prefix='LOCAL-', default_cache_ttl=5)
@local_cachify.cached(key='local-expensive-{x}') # outer, short-lived layer
@cached(key='global-expensive-{x}') # inner, longer-lived layer
def expensive(x: int) -> int:
print('Executing expensive operation...')
return x * 10
# Reset both layers for a given argument
expensive.reset(42)
cached decorator with encoder/decoder¶
from py_cachify import cached
def encoder(val: 'UnpicklableClass') -> dict:
return {'arg1': val.arg1, 'arg2': val.arg2}
def decoder(val: dict) -> 'UnpicklableClass':
return UnpicklableClass(**val)
@cached(key='create_unpicklable_class-{arg1}-{arg2}', enc_dec=(encoder, decoder))
def create_unpicklable_class(arg1: str, arg2: str) -> 'UnpicklableClass':
return UnpicklableClass(arg1=arg1, arg2=arg2)
lock as a context manager¶
from py_cachify import init_cachify, lock
# Ensure global client is initialized for locks
init_cachify()
# Use it within an asynchronous context
async with lock('resource_key'):
# Your critical section here
print('Critical section code')
# Use it within a synchronous context
with lock('resource_key'):
# Your critical section here
print('Critical section code')
lock as a decorator¶
from py_cachify import init_cachify, lock
# Initialize once at app startup
init_cachify()
@lock(key='critical_function_lock-{arg}', nowait=False, timeout=10)
def critical_function(arg: int) -> None:
# critical code
...
once decorator¶
from datetime import date
from time import sleep
from py_cachify import once
@once(key='long_running_function')
async def long_running_function() -> str:
# Executing long-running operation...
...
@once(key='create-transactions-{for_date.year}-{for_date.month}-{for_date.day}')
def create_transactions(for_date: date) -> None:
# Creating...
...
@once(key='another_long_running_task', return_on_locked='In progress')
def another_long_running_function() -> str:
sleep(10)
return 'Completed'
@once(key='exception_if_more_than_one_is_running', raise_on_locked=True)
def one_more_long_running_function() -> None:
# Executing
...
pool as context manager¶
from py_cachify import init_cachify, pool
# Ensure global client is initialized
init_cachify()
# Use it within an asynchronous context
async with pool(key='worker-pool', max_size=10):
# Your pooled work here
print('Executing within pool capacity')
# Use it within a synchronous context
with pool(key='sync-pool', max_size=5):
# Your pooled work here
print('Synchronous pooled execution')
@pooled decorator¶
from py_cachify import init_cachify, pooled
# Initialize once at app startup
init_cachify()
@pooled(key='api-call-pool-{user_id}', max_size=5)
async def call_external_api(user_id: str) -> dict:
# Limited to 5 concurrent calls per user
return {'user_id': user_id, 'data': 'response'}
# Check pool occupancy
await call_external_api.size(user_id='123')
@pooled with on_full callback¶
from py_cachify import init_cachify, pooled
# Initialize once at app startup
init_cachify()
def handle_full(*args, **kwargs):
# Callback receives the same args/kwargs as the original function
user_id = kwargs.get('user_id')
# Could reschedule, log, or return cached data
return {'user_id': user_id, 'status': 'queued', 'data': None}
@pooled(key='worker-pool-{user_id}', max_size=3, on_full=handle_full)
async def process_task(user_id: str, task_data: str) -> dict:
# Process the task
return {'user_id': user_id, 'task_data': task_data, 'status': 'completed'}
# When pool is full, handle_full is called instead
result = await process_task(user_id='123', task_data='payload')