Files
api_utils_converse_v2/cron/cron_dibs.py
T
yatmesh bfbc270b65 Squashed 'utils_v2/' content from commit f03179d
git-subtree-dir: utils_v2
git-subtree-split: f03179d339e69fdcdff2f0cc06e1f352024c55d3
2025-06-12 13:46:57 +05:30

160 lines
6.9 KiB
Python

"""
AUTHOR:
Khushal P Soonderji
DATE:
Saturday, 7th Jun., 2025
OBJECTIVE:
To have a mechanism where multiple instances of a cron script can be running and can have a mechanism to call
dibs on the right to execute a particular activity by announcing their availability.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# My utils:
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# To make a decorator:
from functools import wraps
# For hashing and shortening the hash:
import hashlib
import base64
# for random values:
import random
# For asynchronous activities:
import asyncio
# To work with datatypes:
from typing import Callable
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
def call_dibs_through_redis(
cache: AsyncRedisCache | Callable,
max_dibs: int = 1,
min_random_delay: float = 0.0,
max_random_delay: float = 0.0,
expiry: float = 5.0
):
"""
This decorator factory will be used to decorate functions in cron scripts such that each instance can claim dibs
over the execution of the function. In case other instance(s) have already claimed dibs, this instance's claim will
be rejected.
:param cache: The instance of 'AsyncRedisCache'.
:param max_dibs: The maximum number of instances that should be allowed to execute the wrapped function.
:param min_random_delay: The minimum delay in seconds to sleep before claiming dibs.
:param max_random_delay: The maximum delay in seconds to sleep before claiming dibs.
:param expiry: The time in seconds after which the cached data must be cleared.
:return: The decorator that automatically caches your data.
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# Fetch the cache from a callable if needed:
_cache = cache() if isinstance(cache, Callable) else cache
if not isinstance(_cache, AsyncRedisCache): return None
# In case a random delay has been requested:
if max_random_delay > 0.0:
await asyncio.sleep(min_random_delay + (random.random() * (max_random_delay - min_random_delay)))
# We first use the name of the function and the inputs given to it to generate a key for Redis with the
# simple hashing and shortening by way of base64 strings:
inputs_given = "cron_dibs_" + func.__name__ + str([_ for _ in args]) + str(kwargs)
sha256_hash = hashlib.sha256()
sha256_hash.update(inputs_given.encode("utf-8"))
hashed_key = sha256_hash.digest()
base64_key = base64.b64encode(hashed_key).decode("utf-8")
# We increment a counter in Redis to check what our claim no. is:
claim_no = await _cache.count(key = base64_key, value = 1, expiry = expiry, raise_exception = False)
# If the claim no. is within the limit defined by max. dibs,
# We execute the wrapped function:
if claim_no <= max_dibs: response = await func(*args, **kwargs)
# Otherwise, we don't execute the function:
else: response = f"Other instances claimed the dibs. Claim No: {claim_no}"
# Return the response from the wrapped function.
return response
return wrapper
return decorator
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass