so_magic.utils.transformations   A
last analyzed

Complexity

Total Complexity 7

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 94
rs 10
c 0
b 0
f 0
wmc 7

3 Methods

Rating   Name   Duplication   Size   Complexity  
A RuntimeTransformer.transform() 0 2 1
B RuntimeTransformer.__new__() 0 24 5
A TransformerInterface.transform() 0 38 1
1
"""This module provides the Transformer class. Its constructor can be used to create data transformation methods."""
2
import abc
3
import inspect
4
import types
5
6
7
__all__ = ['Transformer']
8
9
10
class TransformerInterface(abc.ABC):
11
    """The interface with a method to transform structured data. Anyone, implementing this has the ability to receive
12
    some kind of data and return some kind of transformed version of them.
13
    """
14
    @abc.abstractmethod
15
    def transform(self, data, **kwargs):
16
        """Takes data and optional keyword arguments and transforms them.
17
        Input data can represent either a single variable of an observation (scalar)
18
        or a vector of observations of the same variable (if N observations then returns a [N x 1] array-like).
19
20
        Example 1:
21
        obs1 = [x1, y1, z1]
22
        fa = f_a(x)
23
        fb = f_b(x)
24
        fc = f_c(x)
25
        feature_vector1 = [fa(x1), fb(y1), fc(z1)]
26
27
        So, each of fa, fb and fc can implement the Transformer interface.
28
29
        Example 2:
30
        obs1 = [x1, y1, z1]
31
        obs2 = [x2, y2, z2]
32
        obs3 = [x3, y3, z3]
33
        obs4 = [x4, y4, z4]
34
        data = [obs1;
35
                obs2;
36
                obs3;
37
                obs4]  shape = (4,3)
38
        fa = f_a(x)
39
        fb = f_b(x)
40
        fc = f_c(x)
41
        feature_vectors = [fa(data[:0], fb(data[:1], fc(data[:2])]  - shape = (4,3)
42
43
        Again each of fa, fb and fc can implement the Transformer interface.
44
45
        Args:
46
            data (object): the input data to transform; the x in an f(x) invocation
47
48
        Raises:
49
            NotImplementedError: [description]
50
        """
51
        raise NotImplementedError
52
53
54
class RuntimeTransformer(TransformerInterface, abc.ABC):
55
    """Examines whether the input object is callable, if it can receive at least one input argument and also
56
    whether it can accept kwargs. Depending on the kwargs check, "configures" the '_transform' method to process
57
     any kwargs at runtime or to ignore them.
58
59
    Delegates all the transformation operation to its '_transform' method provided by its '_callable' field.
60
61
    Args:
62
        a_callable (callable): a callable object used to delegate the transformation operation
63
    """
64
    def __new__(cls, *args, **kwargs):
65
        instance_object = super().__new__(cls)
66
        a_callable = args[0]
67
        if not callable(a_callable):
68
            raise ValueError(f"Expected a callable as argument; instead got '{type(a_callable)}'")
69
        nb_mandatory_arguments = a_callable.__code__.co_argcount  # this counts sums both *args and **kwargs
70
        # use syntax like 'def a(b, *, c=1, d=2): .. to separate pos args from kwargs & to inform 'inspect' lib about it
71
        if nb_mandatory_arguments < 1:
72
            raise ValueError("Expected a callable that receives at least one positional argument; "
73
                             f"instead got a callable that receives '{nb_mandatory_arguments}' arguments.")
74
        signature = inspect.signature(a_callable)
75
        parameters = list(signature.parameters.values())
76
77
        if nb_mandatory_arguments > 1:
78
            def _transform(_self, data, **keyword_args):
79
                return a_callable(data, **keyword_args)
80
        elif nb_mandatory_arguments == len(parameters):
81
            def _transform(_self, data, **_keyword_args_):
82
                return a_callable(data, **_keyword_args_)
83
        else:
84
            raise Exception(f"Something went really bad above! Parameters: [{', '.join(str(_) for _ in parameters)}]")
85
        instance_object._transform = types.MethodType(_transform, instance_object)
86
        instance_object._callable = a_callable
87
        return instance_object
88
89
    def transform(self, data, **kwargs):
90
        return self._transform(data, **kwargs)
91
92
93
class Transformer(RuntimeTransformer): pass
94