Passed
Push — dev ( 6a1e3c...3c73d5 )
by Konstantinos
01:31
created

CommandGetter.accumulator()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import inspect
2
import attr
3
from green_magic.utils import Command
4
from green_magic.utils import Subject, Observer
5
from green_magic.data.dataset import DatapointsFactory
6
7
8
class MyDecorator(type):
9
    """Metaclass that provides a decorator able to be invoked both with and without parenthesis.
10
    The wrapper function logic should be implemented by the client code.
11
    """
12
    @classmethod
13
    def magic_decorator(mcs, arg=None):
14
        print("DEBUG magic_decorator 1, args:", str(arg))
15
        def decorator(func):
16
            print("DEBUG magic_decorator 2, args:", str(func))
17
            def wrapper(*a, **ka):
18
                print("DEBUG magic_decorator 3, args: [{}]".format(', '.join(str(x) for x in a)))
19
                ffunc = a[0]
20
                mcs._wrapper(ffunc, *a[1:], **ka)
21
                return ffunc
22
            print("DEBUG magic_decorator 4, func: {}".format(str(func)))
23
            return wrapper
24
25
        if callable(arg):
26
            print("Decoration invokation WITHOUT parenthesis")
27
            _ = decorator(arg)
28
            print("OUT: {}".format(str(_)))
29
            print(type(_))
30
            return _  # return 'wrapper'
31
        else:
32
            print("Decoration invokation WITH parenthesis")
33
            _ = decorator
34
            print(f"OUT: {str(_)}")
35
            print(type(_))
36
            return _  # ... or 'decorator'
37
38
    # @classmethod
39
    # def _wrapper(cls, a_callable, *args, **kwargs):
40
    #     print("GGGGGGGG EngineType _wrapper", str(a_callable))
41
    #     if hasattr(a_callable, '__code__'):  # it is a function (def func_name ..)
42
    #         cls.registry[kwargs.get('name', kwargs.get('key', a_callable.__code__.co_name))] = cls.command_factory(
43
    #             function)
44
    #     else:
45
    #         if not hasattr(a_callable, '__call__'):
46
    #             raise RuntimeError(
47
    #                 f"Expected an class definition with a '__call__' instance method defined 1. Got {type(a_callable)}")
48
    #         members = inspect.getmembers(a_callable)
49
    #         if not ('__call__', a_callable.__call__) in members:
50
    #             raise RuntimeError(
51
    #                 f"Expected an class definition with a '__call__' instance method defined 2. Got {type(a_callable)}")
52
    #         instance = a_callable()
53
    #         cls.registry[kwargs.get('name', kwargs.get('key', getattr(instance, 'name', type(
54
    #             a_callable).__name__)))] = cls.command_factory(instance)
55
56
class CommandRegistrator(MyDecorator):
57
    """Classes can use this class as metaclass to obtain a single registration point accessible as class attribute
58
    """
59
    def __new__(mcs, *args, **kwargs):
60
        class_object = super().__new__(mcs, *args, **kwargs)
61
        class_object.state = None
62
        class_object.registry = {}
63
        return class_object
64
65
    def __getitem__(self, item):
66
        if item not in self.registry:
67
            raise RuntimeError(f"Key '{item}' fot found in registry: [{', '.join(str(x) for x in self.registry.keys())}]")
68
        return self.registry[item]
69
70
    def func_decorator(cls):
71
        def wrapper(a_callable):
72
            if hasattr(a_callable, '__code__'):  # it a function (def func_name ..)
73
                print(f"Registering input function {a_callable.__code__.co_name}")
74
                cls.registry[a_callable.__code__.co_name] = a_callable
75
            else:
76
                raise RuntimeError(f"Expected a function to be decorated; got {type(a_callable)}")
77
            return a_callable
78
        return wrapper
79
80
81
class CommandFactory:
82
    """A factory class able to construct new command objects."""
83
    command_constructor = Command
84
    datapoints_factory = DatapointsFactory()
85
    @classmethod
86
    def create(cls, *args) -> Command:
87
        """Call to create a new Command object. The input arguments can be in two formats:
88
89
        1. create(an_object, method, *arguments)
90
        In this case the command is of the form an_object.method(*arguments)
91
92
        2. create(a_function, *arguments)
93
        In this case the command is of the form a_function(*arguments)
94
95
        Returns:
96
            Command: an instance of a command object
97
        """
98
        is_function = hasattr(args[0], '__code__')
99
        if is_function:  # if receiver is a function; creating from function
100
            return cls.command_constructor(args[0], '__call__', *args[1:]), args[0].__code__.co_name
101
        return cls.command_constructor(args[0], args[1], *args[2:]), type(args[0]) + '-' + args[1]
102
103
104
@attr.s
105
class MagicCommandFactory(Subject):
106
    """Instances of this class act as callable command factories that notify,
107
    subscribed observers/listeners upon new command object creation.
108
109
    Args:
110
        command_factory (CommandFactory, optional): an instance of a CommandFActory
111
    """
112
    command_factory = attr.ib(init=True, default=CommandFactory())
113
114
    def __call__(self, *args, **kwargs):
115
        self._state, self.name = self.command_factory.create(*args)
116
        self.notify()
117
        return self._state
118
119
120
@attr.s
121
class CommandsAccumulator(Observer):
122
    """"""
123
    commands = attr.ib(init=False, default={})
124
125
    def update(self, subject: Subject) -> None:
126
        self.commands[getattr(subject, 'name', str(subject.state))] = subject.state
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable str does not seem to be defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable getattr does not seem to be defined.
Loading history...
127
128
@attr.s
129
class CommandGetter:
130
    _commands_accumulator = attr.ib(init=True, default=CommandsAccumulator())
131
132
    @property
133
    def accumulator(self):
134
        return self._commands_accumulator
135
136
    def __getattr__(self, item):
137
        if item not in self._commands_accumulator.commands:
138
            raise KeyError(f"Item '{item}' not found in [{', '.join(str(_) for _ in self._commands_accumulator.commands.keys())}]")
139
        return self._commands_accumulator.commands[item]
140
141
142
@attr.s
143
class CommandsManager:
144
    """[summary]
145
146
    Args:
147
        prototypes (dict, optional): initial prototypes to be supplied
148
        command_factory (callable, optional): a callable that returns an instance of Command
149
    """
150
    _commands_getter = attr.ib(init=True, default=CommandGetter())
151
152
    @property
153
    def command(self):
154
        return self._commands_getter
155
156
    @property
157
    def commands_dict(self):
158
        return self._commands_accumulator.commands
159
160
    def __getattr__(self, item):
161
        if item not in self._commands_accumulator.commands:
162
            raise KeyError(f"Item '{item}' not found in [{', '.join(str(_) for _ in self._commands_accumulator.commands.keys())}]")
163
        return self._commands_accumulator.commands[item]
164