from abc import ABC from inspect import getfullargspec from typing import Optional, List, Tuple, Any, Callable, Awaitable from apollo_utils.core.utils.singleton import SingleValue NO_DATA = SingleValue() class ArgValueDispatcher(ABC): """ Base class to be inherited to create kwarg (_arg_key) dispatcher with additional functionality that can find index of a needed arg between args of stored functions/handlers if kwarg is not provided. It uses getfullargspec() of inspect library - get info about function arguments, with support for Python 3 features Can be used both for Callable and Awaitable _arg_key - is a value to be firstly searched for through kwargs of a function _name - Function name _handlers - Dict of key: value pairs where value is a function/method _default_handler - Provided default handler if needed _register - Method to add function(s)/method(s) to a _handlers dict as it should be provided as a Tuple as (key, value) pair taken from List of Tuple(s) from mapping instance attr mapping - instance attr provided as List[Tuple(key, value)] Usage example: The example of a DSP Dispatcher: class DspArgDispatcher(ArgValueDispatcher): _arg_key = "dsp" """ _arg_key = None def __init__(self, mapping: List[Tuple[Any, object]], name: Optional[str] = None, func: object = None) -> object: self._name = name self._handlers = {} self._default_handler = func self._register(mapping) def __call__(self, *args, **kwargs): return self.resolve(*args, **kwargs)(*args, **kwargs) def add(self, key: Any, func: [Callable, Awaitable]): if key in self._handlers: raise ValueError(f"Handler for {key} is already added to {self._name} dispatcher.") self._handlers[key] = func def _register(self, mapping: List[Tuple[Any, object]]): for key, f in mapping: self.add(key, f) def resolve(self, *args, **kwargs): for arg_getter in (self._get_key_from_kwargs, self._get_key_from_arg): arg_value = arg_getter(*args, **kwargs) if arg_value != NO_DATA: break handler = self._handlers.get(arg_value) or self._default_handler if not handler: raise NotImplemented(f"Dispatcher {self._name} has no handler for {arg_value}") return handler def _get_arg_index(self, function: object): try: return getfullargspec(function).args.index(self._arg_key) except ValueError: return def _get_key_from_arg(self, *args, **kwargs): for function in self._handlers.values(): index = self._get_arg_index(function) if index is None: return NO_DATA try: result = args[index] if result in self._handlers: return result except IndexError: pass return NO_DATA def _get_key_from_kwargs(self, *args, **kwargs): return kwargs.get(self._arg_key, NO_DATA)