b53ef86ef8
git-subtree-dir: utils_v2 git-subtree-split: 7f273565196085feb05ee3328aa2e80d3d721fc3
160 lines
6.9 KiB
Python
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
|