Module FuncNotify.NotifyDecorators

Main decoratory for FuncNotify with all env variables

To use, use @time_func as a decorator or pass arguments to @time_func(args, *kwargs) as a decorator

Expand source code
"""Main decoratory for FuncNotify with all env variables

To use, use @time_func as a decorator or pass arguments to @time_func(*args, **kwargs) as a decorator
"""
import time
import os
import warnings
from collections import deque
from dotenv import dotenv_values

import FuncNotify
from FuncNotify.NotifyMethods import NotifyMethods

ENV_DICT=None

# Main decorator
def time_func(func=None, NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
              multi_target: list=None, multi_env: list=None, *args, **kwargs): 
    """Decorator for how to handle a notify function. Allows for additional arguments in the decorator
    and support for args like emails/api keys. Is able to handle errors.

    Args:
        func (function, optional): In case you want to use time_func as a decorator without argumetns, 
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from FuncNotify.NotifyTypes. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

        

    Returns:
        function: decorator function for timing
    """    
    NotifyObjList=Notify_Obj_Factory(**locals())

    def time_function(func_inner):
        """Inner wrapped function, used for timing and control, necessary to give the
        decorator additional arguments that can be passed in

        Args:
            func_inner (function): passes function and arguments down below to the final timer
        """         
        def timer(*func_args, **func_kwargs):
            """Takes arguments from main function and passes it down

            Returns:
                Object: returns func's output
            """           
            return timer_base(func_inner, NotifyObjList, *func_args, **func_kwargs)
        return timer     
        
    if callable(func): # Checks time_func was used as a decorator (@time_func vs @time_func(NotifyMethod="Slack"))
        return time_function(func)

    return time_function


def timer_base(func, NotifyObjList: list, *args, **kwargs): 
    """ Timer base, depending on the type of object of NotifyObj, it will notify the 
    user of the method and time the function. Errors are raised in the same method
    Leverages a factory that created the object and utilizes the abstract methods

    Args:
        func (function): Any function
        NotifyObjList (list[NotifyMethods]): Object from abstract class that indicates how the user is notified

    Raises:
        ex (Exception): Any Exception can be excepted then raised, this ensures exceptions aren't 
        interuptted but the user is notified

    Returns:
        Object: Returns whatever the function returns
    """    
    try:
        deque(map(lambda NotifyObj: NotifyObj.send_start_MSG(func), NotifyObjList), maxlen=0) # sends message on each item in list
        start = time.time()                                                                   # by iterating through the map
        
        result = func(*args, **kwargs)
        
        end = time.time()
        deque(map(lambda NotifyObj: NotifyObj.send_end_MSG(func, end-start), NotifyObjList), maxlen=0)
    except Exception as ex: 
        deque(map(lambda NotifyObj: NotifyObj.send_error_MSG(func, ex), NotifyObjList), maxlen=0) # noqa: F821 Bizarre bug with flake8, opening an issue
        raise ex

    return result

def Notify_Obj_Factory(NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
              multi_target: list=None, multi_env: list=None, func=None, message: str=None, args=None, kwargs=None)-> list: 
    """Creates a list of NotifyMethods Objects to be used to send messages

    Args:
        func (function, optional): In case you want to use time_func as a decorator without argumetns, 
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from FuncNotify.NotifyTypes. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

        message(bool, optional): Used for message notifications
        

    Returns:
        list: List of NotifyMethods Objects to be used to send messages with
    """    
    
    notify_obj_list=[]
    global ENV_DICT
    
    if update_env or ENV_DICT is None or not use_env:
        ENV_DICT={**os.environ, **dotenv_values(env_path)} if use_env else {} 
    
    if multi_env and multi_target: 
        # NOTE multi_target and multi_env must be corresponding and will only do up shortest of the two lissts
        for target, spec_env_path in zip(multi_target, multi_env):
            spec_environ_dict={**ENV_DICT, **dotenv_values(spec_env_path)} if spec_env_path else ENV_DICT
            target_method=target.get("NotifyMethod", NotifyMethod)
            method_string=target_method if target_method else spec_environ_dict.get("DEFAULTNOTIFY", "NotFound")
            
            notify_obj_list.append(
                Get_notify_obj(method_string,
                               target_dict=target, environ_dict=spec_environ_dict, 
                               obj_args=args, obj_kwargs=kwargs))
    elif multi_target:
        for target in multi_target: # Rewrite as a function for easier reuse
            notify_obj_list.append(
                Get_notify_obj(NotifyMethod=target.get("NotifyMethod", NotifyMethod),
                               target_dict=target, environ_dict=ENV_DICT, 
                               obj_args=args, obj_kwargs=kwargs))
    elif multi_env:
         for spec_env_path in multi_env:
            spec_environ_dict={**ENV_DICT, **dotenv_values(spec_env_path)}
            notify_obj_list.append(
                Get_notify_obj(NotifyMethod=NotifyMethod if NotifyMethod else spec_environ_dict.get("DEFAULTNOTIFY", "NotFound"), 
                               environ_dict=spec_environ_dict, obj_args=args, obj_kwargs=kwargs))
    else:
        notify_obj_list.append(
            Get_notify_obj(NotifyMethod=NotifyMethod if NotifyMethod else ENV_DICT.get("DEFAULTNOTIFY", "NotFound"), 
                           environ_dict=ENV_DICT, obj_args=args, obj_kwargs=kwargs))
    
    return notify_obj_list

def Get_notify_obj(NotifyMethod: str, environ_dict: dict, obj_args, obj_kwargs, target_dict: dict=None):
    """Creates the object and returns it's a function for reusability

    Args:
        NotifyMethod (str): String deciding what the notify type is
        environ_dict (dict): environment variables dictionary
        obj_args (tuple): notify object tuple arguments
        obj_kwargs (dict): notify object dictionary key ward arguments
        target_dict (dict, optional): Specific arguments specified for a user. Defaults to None.

    Returns:
        NotifyMethods: NotifyMethods obbject that allows you to send start, stop and error messages
    """
    def default_notify(*args, **kwargs): # Sends a warning your notify method didn't match 
        warnings.warn(f"Invalid NotifyMethod type '{NotifyMethod}' specified, will use `PrintMethod`, " \
                      f"select a type within these keys: {[key for key in FuncNotify.NotifyTypes]}.")
        return FuncNotify.NotifyTypes["Print"](*args, **kwargs)
    
    if target_dict is None:
        target_dict={}

    return FuncNotify.NotifyTypes.get(NotifyMethod, default_notify)(environ=environ_dict,
                                                                    *obj_args, 
                                                                    **target_dict,
                                                                    **obj_kwargs)
                
 
def custom_message(message: str, NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
                   multi_target: list=None, multi_env: list=None, *args, **kwargs): 
    """Sends custom messages for the multiple targets in .env or specified, utilizes  the same code as time_func to send
    the custom messages which is nice

    Args:
        message (str): Message to be send to everybody
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from NotifyType. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in \
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.
        

    Returns:
        function: decorator function for timing
    """    
    NotifyObjList=Notify_Obj_Factory(**locals())
        
    deque(map(lambda NotifyObj: NotifyObj.send_custom_MSG(message), NotifyObjList), maxlen=0)
    

Functions

def Get_notify_obj(NotifyMethod: str, environ_dict: dict, obj_args, obj_kwargs, target_dict: dict = None)

Creates the object and returns it's a function for reusability

Args

NotifyMethod : str
String deciding what the notify type is
environ_dict : dict
environment variables dictionary
obj_args : tuple
notify object tuple arguments
obj_kwargs : dict
notify object dictionary key ward arguments
target_dict : dict, optional
Specific arguments specified for a user. Defaults to None.

Returns

NotifyMethods
NotifyMethods obbject that allows you to send start, stop and error messages
Expand source code
def Get_notify_obj(NotifyMethod: str, environ_dict: dict, obj_args, obj_kwargs, target_dict: dict=None):
    """Creates the object and returns it's a function for reusability

    Args:
        NotifyMethod (str): String deciding what the notify type is
        environ_dict (dict): environment variables dictionary
        obj_args (tuple): notify object tuple arguments
        obj_kwargs (dict): notify object dictionary key ward arguments
        target_dict (dict, optional): Specific arguments specified for a user. Defaults to None.

    Returns:
        NotifyMethods: NotifyMethods obbject that allows you to send start, stop and error messages
    """
    def default_notify(*args, **kwargs): # Sends a warning your notify method didn't match 
        warnings.warn(f"Invalid NotifyMethod type '{NotifyMethod}' specified, will use `PrintMethod`, " \
                      f"select a type within these keys: {[key for key in FuncNotify.NotifyTypes]}.")
        return FuncNotify.NotifyTypes["Print"](*args, **kwargs)
    
    if target_dict is None:
        target_dict={}

    return FuncNotify.NotifyTypes.get(NotifyMethod, default_notify)(environ=environ_dict,
                                                                    *obj_args, 
                                                                    **target_dict,
                                                                    **obj_kwargs)
def Notify_Obj_Factory(NotifyMethod: str = None, use_env: bool = True, env_path: str = '.env', update_env: bool = False, multi_target: list = None, multi_env: list = None, func=None, message: str = None, args=None, kwargs=None) ‑> list

Creates a list of NotifyMethods Objects to be used to send messages

Args

func : function, optional
In case you want to use time_func as a decorator without argumetns,
NotifyMethod : str, optional
Specifies the type of method used to notify user, selected from FuncNotify.NotifyTypes. Defaults to None.
use_env : str, optional
Whether to load the current env+the env_path. Defaults to True
env_path : str, optional
path to .env file. Input "" for just the current environment. Defaults to ".env".
update_env : bool, optional
whether to update the .env file to current. Always updates on

initialization. Defaults to False.

multi_target : list[dict], optional
A list of dictionary of keyword arguments for every new target. Defaults to ".env".
multi_env : list[str], optional
list of strings of paths to env files. If you use multi_target and multi_env in

conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

message(bool, optional): Used for message notifications

Returns

list
List of NotifyMethods Objects to be used to send messages with
Expand source code
def Notify_Obj_Factory(NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
              multi_target: list=None, multi_env: list=None, func=None, message: str=None, args=None, kwargs=None)-> list: 
    """Creates a list of NotifyMethods Objects to be used to send messages

    Args:
        func (function, optional): In case you want to use time_func as a decorator without argumetns, 
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from FuncNotify.NotifyTypes. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

        message(bool, optional): Used for message notifications
        

    Returns:
        list: List of NotifyMethods Objects to be used to send messages with
    """    
    
    notify_obj_list=[]
    global ENV_DICT
    
    if update_env or ENV_DICT is None or not use_env:
        ENV_DICT={**os.environ, **dotenv_values(env_path)} if use_env else {} 
    
    if multi_env and multi_target: 
        # NOTE multi_target and multi_env must be corresponding and will only do up shortest of the two lissts
        for target, spec_env_path in zip(multi_target, multi_env):
            spec_environ_dict={**ENV_DICT, **dotenv_values(spec_env_path)} if spec_env_path else ENV_DICT
            target_method=target.get("NotifyMethod", NotifyMethod)
            method_string=target_method if target_method else spec_environ_dict.get("DEFAULTNOTIFY", "NotFound")
            
            notify_obj_list.append(
                Get_notify_obj(method_string,
                               target_dict=target, environ_dict=spec_environ_dict, 
                               obj_args=args, obj_kwargs=kwargs))
    elif multi_target:
        for target in multi_target: # Rewrite as a function for easier reuse
            notify_obj_list.append(
                Get_notify_obj(NotifyMethod=target.get("NotifyMethod", NotifyMethod),
                               target_dict=target, environ_dict=ENV_DICT, 
                               obj_args=args, obj_kwargs=kwargs))
    elif multi_env:
         for spec_env_path in multi_env:
            spec_environ_dict={**ENV_DICT, **dotenv_values(spec_env_path)}
            notify_obj_list.append(
                Get_notify_obj(NotifyMethod=NotifyMethod if NotifyMethod else spec_environ_dict.get("DEFAULTNOTIFY", "NotFound"), 
                               environ_dict=spec_environ_dict, obj_args=args, obj_kwargs=kwargs))
    else:
        notify_obj_list.append(
            Get_notify_obj(NotifyMethod=NotifyMethod if NotifyMethod else ENV_DICT.get("DEFAULTNOTIFY", "NotFound"), 
                           environ_dict=ENV_DICT, obj_args=args, obj_kwargs=kwargs))
    
    return notify_obj_list
def custom_message(message: str, NotifyMethod: str = None, use_env: bool = True, env_path: str = '.env', update_env: bool = False, multi_target: list = None, multi_env: list = None, *args, **kwargs)

Sends custom messages for the multiple targets in .env or specified, utilizes the same code as time_func to send the custom messages which is nice

Args

message : str
Message to be send to everybody
NotifyMethod : str, optional
Specifies the type of method used to notify user, selected from NotifyType. Defaults to None.
use_env : str, optional
Whether to load the current env+the env_path. Defaults to True
env_path : str, optional
path to .env file. Input "" for just the current environment. Defaults to ".env".
update_env : bool, optional
whether to update the .env file to current. Always updates on

initialization. Defaults to False.

multi_target : list[dict], optional
A list of dictionary of keyword arguments for every new target. Defaults to ".env".
multi_env : list[str], optional
list of strings of paths to env files. If you use multi_target and multi_env in conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

Returns

function
decorator function for timing
Expand source code
def custom_message(message: str, NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
                   multi_target: list=None, multi_env: list=None, *args, **kwargs): 
    """Sends custom messages for the multiple targets in .env or specified, utilizes  the same code as time_func to send
    the custom messages which is nice

    Args:
        message (str): Message to be send to everybody
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from NotifyType. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in \
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.
        

    Returns:
        function: decorator function for timing
    """    
    NotifyObjList=Notify_Obj_Factory(**locals())
        
    deque(map(lambda NotifyObj: NotifyObj.send_custom_MSG(message), NotifyObjList), maxlen=0)
def time_func(func=None, NotifyMethod: str = None, use_env: bool = True, env_path: str = '.env', update_env: bool = False, multi_target: list = None, multi_env: list = None, *args, **kwargs)

Decorator for how to handle a notify function. Allows for additional arguments in the decorator and support for args like emails/api keys. Is able to handle errors.

Args

func : function, optional
In case you want to use time_func as a decorator without argumetns,
NotifyMethod : str, optional
Specifies the type of method used to notify user, selected from FuncNotify.NotifyTypes. Defaults to None.
use_env : str, optional
Whether to load the current env+the env_path. Defaults to True
env_path : str, optional
path to .env file. Input "" for just the current environment. Defaults to ".env".
update_env : bool, optional
whether to update the .env file to current. Always updates on

initialization. Defaults to False.

multi_target : list[dict], optional
A list of dictionary of keyword arguments for every new target. Defaults to ".env".
multi_env : list[str], optional
list of strings of paths to env files. If you use multi_target and multi_env in

conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

Returns

function
decorator function for timing
Expand source code
def time_func(func=None, NotifyMethod: str=None, use_env: bool=True, env_path: str=".env", update_env: bool=False, 
              multi_target: list=None, multi_env: list=None, *args, **kwargs): 
    """Decorator for how to handle a notify function. Allows for additional arguments in the decorator
    and support for args like emails/api keys. Is able to handle errors.

    Args:
        func (function, optional): In case you want to use time_func as a decorator without argumetns, 
        NotifyMethod (str, optional): Specifies the type of method used to notify user, selected \
        from FuncNotify.NotifyTypes. Defaults to None.
        use_env (str, optional): Whether to load the current env+the env_path. Defaults to True
        env_path (str, optional): path to .env file. Input "" for just the current environment. Defaults to ".env".
        update_env (bool, optional): whether to update the .env file to current. Always updates on 
        initialization. Defaults to False.
        
        multi_target (list[dict], optional): A list of dictionary of keyword arguments for every new target. Defaults to ".env".
        multi_env (list[str], optional): list of strings of paths to `env` files. If you use multi_target and multi_env in
        conjunction you can create a correspondence of new targets with specific .env files to notify. Defaults ot None.

        

    Returns:
        function: decorator function for timing
    """    
    NotifyObjList=Notify_Obj_Factory(**locals())

    def time_function(func_inner):
        """Inner wrapped function, used for timing and control, necessary to give the
        decorator additional arguments that can be passed in

        Args:
            func_inner (function): passes function and arguments down below to the final timer
        """         
        def timer(*func_args, **func_kwargs):
            """Takes arguments from main function and passes it down

            Returns:
                Object: returns func's output
            """           
            return timer_base(func_inner, NotifyObjList, *func_args, **func_kwargs)
        return timer     
        
    if callable(func): # Checks time_func was used as a decorator (@time_func vs @time_func(NotifyMethod="Slack"))
        return time_function(func)

    return time_function
def timer_base(func, NotifyObjList: list, *args, **kwargs)

Timer base, depending on the type of object of NotifyObj, it will notify the user of the method and time the function. Errors are raised in the same method Leverages a factory that created the object and utilizes the abstract methods

Args

func : function
Any function
NotifyObjList : list[NotifyMethods]
Object from abstract class that indicates how the user is notified

Raises

ex (Exception): Any Exception can be excepted then raised, this ensures exceptions aren't interuptted but the user is notified

Returns

Object
Returns whatever the function returns
Expand source code
def timer_base(func, NotifyObjList: list, *args, **kwargs): 
    """ Timer base, depending on the type of object of NotifyObj, it will notify the 
    user of the method and time the function. Errors are raised in the same method
    Leverages a factory that created the object and utilizes the abstract methods

    Args:
        func (function): Any function
        NotifyObjList (list[NotifyMethods]): Object from abstract class that indicates how the user is notified

    Raises:
        ex (Exception): Any Exception can be excepted then raised, this ensures exceptions aren't 
        interuptted but the user is notified

    Returns:
        Object: Returns whatever the function returns
    """    
    try:
        deque(map(lambda NotifyObj: NotifyObj.send_start_MSG(func), NotifyObjList), maxlen=0) # sends message on each item in list
        start = time.time()                                                                   # by iterating through the map
        
        result = func(*args, **kwargs)
        
        end = time.time()
        deque(map(lambda NotifyObj: NotifyObj.send_end_MSG(func, end-start), NotifyObjList), maxlen=0)
    except Exception as ex: 
        deque(map(lambda NotifyObj: NotifyObj.send_error_MSG(func, ex), NotifyObjList), maxlen=0) # noqa: F821 Bizarre bug with flake8, opening an issue
        raise ex

    return result