Completed
Push — appveyor ( 280314...2c0e2c )
by Konstantinos
02:09
created

RuntimeTransformer.__new__()   B

Complexity

Conditions 5

Size

Total Lines 25
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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