Passed
Push — mpeta ( ff9ea9...522f25 )
by Konstantinos
01:42
created

so_magic.data.backend.engine_command_factory   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 90
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 90
rs 10
c 0
b 0
f 0
wmc 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
A GenericCommandFactory.construct() 0 10 1
A CommandFactory.pick() 0 6 3
A MagicCommandFactory.__call__() 0 4 1
A FunctionCommandFactory.construct() 0 15 2
A CommandFactory.create() 0 7 2
1
from abc import ABC
2
import attr
3
from typing import Tuple
4
5
from so_magic.utils import Subject, Command
6
from ..command_factory_interface import CommandFactoryInterface, CommandFactoryType
7
8
9
class BaseCommandFactory(metaclass=CommandFactoryType):
10
    pass
11
12
13
@BaseCommandFactory.register_as_subclass('generic')
14
class GenericCommandFactory(CommandFactoryInterface):
15
    """Command Factory that constructs a command given all the necessary arguments.
16
17
    Assumes the 1st argument is the 'receiver' (see Command module),
18
    2nd is the method to call on the receiver and the rest are the method's runtime arguments.
19
    """
20
    def construct(self, *args, **kwargs) -> Command:
21
        """Construct a command object (Command class instance).
22
23
        Assumes the 1st argument is the 'receiver' (see Command module),
24
        2nd is the method to call on the receiver and the rest are the method's runtime arguments.
25
26
        Returns:
27
            Command: the command object
28
        """
29
        return Command(*args, **kwargs)
30
31
32
@BaseCommandFactory.register_as_subclass('function')
33
class FunctionCommandFactory(CommandFactoryInterface):
34
    """Command Factory that constructs a command assuming the 1st argument is a python function.
35
36
    Assumes that the function (1st argument) acts as the the 'receiver' (see Command module),
37
    2nd is the method to call on the receiver and the rest are the method's runtime arguments.
38
    """
39
    def construct(self, *args, **kwargs) -> Command:
40
        """Construct a command object (Command class instance).
41
42
        Assumes that the 1st argument is a python function and that it acts as the the 'receiver' (see Command module).
43
        The rest are the function's runtime arguments.
44
45
        Raises:
46
            RuntimeError: [description]
47
48
        Returns:
49
            Command: [description]
50
        """
51
        if len(args) < 1:
52
            raise RuntimeError("Will break")
53
        return Command(args[0], '__call__', *args[1:])
54
55
56
class CommandFactory:
57
    """A factory class able to construct new command objects."""
58
    constructors = {k: v().construct for k, v in BaseCommandFactory.subclasses.items()}
59
60
    @classmethod
61
    def pick(cls, *args, **kwargs):
62
        decision = {True: 'function', False: 'generic'}
63
        is_function = hasattr(args[0], '__code__')
64
        dec2 = {'function': lambda x: x[0].__code__.co_name, 'generic': lambda x: type(x[0]).__name__ + '-' + x[1]}
65
        return decision[is_function], kwargs.get('name', dec2[decision[is_function]](args))
66
67
    @classmethod
68
    def create(cls, *args, **kwargs) -> Tuple[Command, str]:
69
70
        key, name = cls.pick(*args, **kwargs)
71
        if len(args) < 1:
72
            raise RuntimeError(args)
73
        return cls.constructors[key](*args), name
74
75
76
@attr.s
77
class MagicCommandFactory(Subject):
78
    """Instances of this class act as callable command factories that notify,
79
    subscribed observers/listeners upon new command object creation.
80
81
    Args:
82
        command_factory (CommandFactory, optional): an instance of a CommandFActory
83
    """
84
    command_factory = attr.ib(init=True, default=CommandFactory())
85
86
    def __call__(self, *args, **kwargs):
87
        self._state, self.name = self.command_factory.create(*args, **kwargs)
88
        self.notify()
89
        return self._state
90