Passed
Push — dev ( 5b3715...a42717 )
by Konstantinos
01:24
created

green_magic.data.command_factories   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 193
Duplicated Lines 16.58 %

Importance

Changes 0
Metric Value
wmc 27
eloc 131
dl 32
loc 193
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A CommandRegistrator.func_decorator() 0 8 2
A MyDecorator.magic_decorator() 0 15 2
A GenericCommandFactory.construct() 0 2 1
A CommandRegistrator.__new__() 0 5 1
A CommandRegistrator.__getitem__() 0 4 2
A NominalAttributeListEncodeCommandFactory.construct() 0 11 1
A FunctionCommandFactory.construct() 0 4 2
A AbstractCommandFactory.construct() 0 3 1
A BaseCommandFactory.register_as_subclass() 6 6 1
A BaseCommandFactory.create() 5 5 2
A MegaCommandFactory.__call__() 0 4 1
A DataManagerCommandFactory.register_as_subclass() 6 6 1
A SelectVariablesCommandFactory.construct() 0 4 1
A CommandFactory.pick() 0 6 3
A DataManagerCommandFactory.create() 5 5 2
A CommandFactory.create() 0 17 2
A MagicCommandFactory.__call__() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
from abc import ABC, abstractmethod
2
import attr
3
from green_magic.utils import Command
4
from green_magic.utils import Subject
5
6
7
class MyDecorator(type):
8
    """Metaclass that provides a decorator able to be invoked both with and without parenthesis.
9
    The wrapper function logic should be implemented by the client code.
10
    """
11
    @classmethod
12
    def magic_decorator(mcs, arg=None):
13
        def decorator(func):
14
            def wrapper(*a, **ka):
15
                ffunc = a[0]
16
                mcs._wrapper(ffunc, *a[1:], **ka)
17
                return ffunc
18
            return wrapper
19
20
        if callable(arg):
21
            _ = decorator(arg)
22
            return _  # return 'wrapper'
23
        else:
24
            _ = decorator
25
            return _  # ... or 'decorator'
26
27
28
class CommandRegistrator(MyDecorator):
29
    """Classes can use this class as metaclass to obtain a single registration point accessible as class attribute
30
    """
31
    def __new__(mcs, *args, **kwargs):
32
        class_object = super().__new__(mcs, *args, **kwargs)
33
        class_object.state = None
34
        class_object.registry = {}
35
        return class_object
36
37
    def __getitem__(self, item):
38
        if item not in self.registry:
39
            raise RuntimeError(f"Key '{item}' fot found in registry: [{', '.join(str(x) for x in self.registry.keys())}]")
40
        return self.registry[item]
41
42
    def func_decorator(cls):
43
        def wrapper(a_callable):
44
            if hasattr(a_callable, '__code__'):  # it a function (def func_name ..)
45
                cls.registry[a_callable.__code__.co_name] = a_callable
46
            else:
47
                raise RuntimeError(f"Expected a function to be decorated; got {type(a_callable)}")
48
            return a_callable
49
        return wrapper
50
51
class AbstractCommandFactory(ABC):
52
    @abstractmethod
53
    def construct(self, *args, **kwargs) -> Command:
54
        raise NotImplementedError
55
56 View Code Duplication
class BaseCommandFactory(AbstractCommandFactory, ABC):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
57
58
    subclasses = {}
59
60
    @classmethod
61
    def register_as_subclass(cls, factory_type):
62
        def wrapper(subclass):
63
            cls.subclasses[factory_type] = subclass
64
            return subclass
65
        return wrapper
66
67
    @classmethod
68
    def create(cls, factory_type, *args, **kwargs):
69
        if factory_type not in cls.subclasses:
70
            raise ValueError('Bad "Factory type" \'{}\''.format(factory_type))
71
        return cls.subclasses[factory_type](*args, **kwargs)
72
73
74
@BaseCommandFactory.register_as_subclass('generic')
75
class GenericCommandFactory(AbstractCommandFactory):
76
    def construct(self, *args, **kwargs) -> Command:
77
        return Command(*args, **kwargs)
78
79
@BaseCommandFactory.register_as_subclass('function')
80
class FunctionCommandFactory(AbstractCommandFactory):
81
    def construct(self, *args, **kwargs) -> Command:
82
        if len(args) < 1:
83
            raise RuntimeError("Will break")
84
        return Command(args[0], '__call__', *args[1:])
85
86
87
@BaseCommandFactory.register_as_subclass('encode_nominal_subsets')
88
class NominalAttributeListEncodeCommandFactory(AbstractCommandFactory):
89
    def construct(self, *args, **kwargs) -> Command:
90
        from green_magic.data.features.phis import ListOfCategoricalPhi, DatapointsAttributePhi
91
        assert len(args) > 0
92
        datapoints = args[0]
93
        attribute = args[1]
94
        new_attribute = args[2]
95
        def _command(_datapoints, _attribute, _new_attribute):
96
            phi = ListOfCategoricalPhi(DatapointsAttributePhi(_datapoints))
97
            new_values = phi(_attribute)
98
            _datapoints.mutator.add_column(_datapoints, new_values, _new_attribute)
99
        return Command(_command, '__call__', datapoints, attribute, new_attribute)
100
101
102
class CommandFactory:
103
    """A factory class able to construct new command objects."""
104
    constructors = {k: v().construct for k, v in BaseCommandFactory.subclasses.items()}
105
    @classmethod
106
    def pick(cls, *args, **kwargs):
107
        decision = {True: 'function', False: 'generic'}
108
        is_function = hasattr(args[0], '__code__')
109
        dec2 = {'function': lambda x: x[0].__code__.co_name, 'generic': lambda x: type(x[0]).__name__ + '-' + x[1]}
110
        return decision[is_function], kwargs.get('name', dec2[decision[is_function]](args))
111
112
    @classmethod
113
    def create(cls, *args, **kwargs) -> Command:
114
        """Call to create a new Command object. The input arguments can be in two formats:
115
116
        1. create(an_object, method, *arguments)
117
        In this case the command is of the form an_object.method(*arguments)
118
119
        2. create(a_function, *arguments)
120
        In this case the command is of the form a_function(*arguments)
121
122
        Returns:
123
            Command: an instance of a command object
124
        """
125
        key, name = cls.pick(*args, **kwargs)
126
        if len(args) < 1:
127
            raise RuntimeError(args)
128
        return cls.constructors[key](*args), name
129
130
131
@attr.s
132
class MagicCommandFactory(Subject):
133
    """Instances of this class act as callable command factories that notify,
134
    subscribed observers/listeners upon new command object creation.
135
136
    Args:
137
        command_factory (CommandFactory, optional): an instance of a CommandFActory
138
    """
139
    command_factory = attr.ib(init=True, default=CommandFactory())
140
141
    def __call__(self, *args, **kwargs):
142
        self._state, self.name = self.command_factory.create(*args, **kwargs)
143
        self.notify()
144
        return self._state
145
146
147 View Code Duplication
class DataManagerCommandFactory(AbstractCommandFactory, ABC):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
148
149
    subclasses = {}
150
151
    @classmethod
152
    def register_as_subclass(cls, factory_type):
153
        def wrapper(subclass):
154
            cls.subclasses[factory_type] = subclass
155
            return subclass
156
        return wrapper
157
158
    @classmethod
159
    def create(cls, factory_type, *args, **kwargs):
160
        if factory_type not in cls.subclasses:
161
            raise ValueError('Bad "Factory type" \'{}\''.format(factory_type))
162
        return cls.subclasses[factory_type](*args, **kwargs)
163
164
#### Register command factories
165
@DataManagerCommandFactory.register_as_subclass('select_variables')
166
class SelectVariablesCommandFactory(DataManagerCommandFactory):
167
168
    def construct(self, *args, **kwargs) -> Command:
169
        def command(variables):
170
            args[0].feature_manager.feature_configuration = variables
171
        return Command(command, '__call__', *args[1:])
172
173
174
@DataManagerCommandFactory.register_as_subclass('select_variables')
175
class SelectVariablesCommandFactory(DataManagerCommandFactory):
176
177
    def construct(self, *args, **kwargs) -> Command:
178
        def command(variables):
179
            args[0].feature_manager.feature_configuration = variables
180
        return Command(command, '__call__', *args[1:])
181
182
#################
183
184
@attr.s
185
class MegaCommandFactory(Subject):
186
    _data_manager = attr.ib(init=True)
187
    command_factory = attr.ib(init=True, default=DataManagerCommandFactory)
188
189
    def __call__(self, command_type, *args, **kwargs):
190
        self._state, self.name = self.command_factory.create(command_type).construct(self._data_manager, *args, **kwargs), command_type
191
        self.notify()
192
        return self._state
193