Source code for multiton.multiton

"""Main module."""
from typing import Sequence, Hashable, Union, Tuple, Callable, Type


[docs]def MultitonMetaFactory( *relevant_args: Union[int, Tuple[int, Callable]], **relevant_kwargs: Union[Hashable, Tuple[Hashable, Callable]]) -> Type: """A function that returns a multiton class which will return a previous instance of a object based of some __init__ attributes. Attributes used for the Multiton need to be Hashable, or you need to provide a callable that gets a Hashable value as a tuple of (key, callable). Args: relevant_args (Union[int, Tuple[int, Callable]]): positional arguemnts are indices for the relevant __init__ arguments of your class. indices can also be a tuple of (index, callable) where callable returns a value of hashable type. relevant_kwargs (Union[Hashable, Tuple[Hashable, Callable]]): keyword arguments are the keywords for the relevant __init__ keyword arguments of your class. keyword arguemnts should be None or a callablethat returns a value of hashable type from the keywords value. Raises: TypeError: raised for various missing or malformed arguments, or when one of the values is a non-hashable type. type: any error raised by the users callable. Returns: MultitonMeta: the metaclass that enforces multiton behaviour. """ for i, arg in enumerate(relevant_args): if not isinstance(arg, int): if isinstance(arg, tuple): if not isinstance(arg[0], int): raise ValueError(f"First element of the {i} argument needs to be a integer index") if not callable(arg[1]) and not arg[1] is None: raise ValueError(f"Second element of the {i} argument needs to be a callable") else: raise ValueError(f"The argument {i} needs to either be a int or tuple.") for key, value in relevant_kwargs.items(): if not value is None and not callable(value): raise ValueError(f"The value of keyword {key} needs to be either None or a callable") if relevant_args: relevant_args = [ rel if isinstance(rel, tuple) else (rel, None) for rel in relevant_args ] if relevant_kwargs: relevant_kwargs = [ (key, value) for key, value in relevant_kwargs.items() ] class MultitionMeta(type): __instances = {} @staticmethod def _get_relevant_args(cls, args): if len(args) < max(list(zip(*relevant_args))[0]) + 1: raise TypeError( f"{cls.__name__}.__init__() missing " f"{max(list(zip(*relevant_args))[0]) + 1 - len(args)} " "positional arguments passed to Multiton metaclass") relevant_args_values = [] for i, (key, getter) in enumerate(relevant_args): value = args[key] if getter is not None: try: value = getter(value) except Exception as e: raise type(e)( f"Getter {i}{getter.__name__} for item " f"{key}, value {value} failed.") from e try: hash(value) except TypeError as e: raise TypeError( f"Unhashable type {type(value)}, please pass " "a getter in args by passing a tuple" ", which contains the index as its " "first element and a callable that returns a " "hashable value when passed the value as a " "second.") from e relevant_args_values.append(value) return relevant_args_values @staticmethod def _get_relevant_kwargs(cls, kwargs): relevant_kwargs_values = [] missing_kwargs_keys = [] for i, (key, getter) in enumerate(relevant_kwargs): try: value = kwargs[key] except (KeyError, IndexError): missing_kwargs_keys.append(key) else: if getter is not None: try: value = getter(value) except Exception as e: raise type(e)( f"Getter {getter.__name__} for key {key}, " f"value {value} failed.") from e relevant_kwargs_values.append(value) try: hash(value) except TypeError as e: raise TypeError( f"Unhashable type {type(value)}, please pass " "a getter in keyword arguments by passing a" "tuple, which contains the key as its first " "element and a callable that returns a hashable " "value when passed the value as a second.") from e if len(missing_kwargs_keys) > 0: raise TypeError( f"{cls}.__init__() missing {len(missing_kwargs_keys)}" f" keyword arguments passed to Multiton metaclass: " f"{*missing_kwargs_keys, }") return relevant_kwargs_values def __call__(cls, *args, **kwargs): hashable_elements = [] if relevant_args: relevant_args_values = MultitionMeta._get_relevant_args( cls, args) hashable_elements.append( tuple(zip(relevant_args[0], relevant_args_values))) if relevant_kwargs: relevant_kwargs_values = MultitionMeta._get_relevant_kwargs( cls, kwargs) hashable_elements.append( tuple(zip(relevant_kwargs[0], relevant_kwargs_values))) hashable_elements = tuple(hashable_elements) try: instance = MultitionMeta.__instances[hashable_elements] except KeyError: instance = super(MultitionMeta, cls).__call__(*args, **kwargs) MultitionMeta.__instances[hashable_elements] = instance return instance return MultitionMeta