Merge commit 'bfbc270b655b1db87dfd9e3afaf79d9968042da5' as 'utils_v2'
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user