Passed
Push — mpeta ( 92227f...db1c73 )
by Konstantinos
01:53
created

CommandFactory.pick()   A

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
7
8
9
class BaseCommandFactory(CommandFactoryInterface, ABC):
10
    """Facility that stores concrete implementations of CommandFactory."""
11
    subclasses = {}
12
13
    @classmethod
14
    def register_as_subclass(cls, factory_type):
15
        def wrapper(subclass):
16
            cls.subclasses[factory_type] = subclass
17
            return subclass
18
        return wrapper
19
20
    @classmethod
21
    def create(cls, factory_type, *args, **kwargs):
22
        if factory_type not in cls.subclasses:
23
            raise ValueError('Bad "Factory type" \'{}\''.format(factory_type))
24
        return cls.subclasses[factory_type](*args, **kwargs)
25
26
27
@BaseCommandFactory.register_as_subclass('generic')
28
class GenericCommandFactory(CommandFactoryInterface):
29
    """Command Factory that constructs a command given all the necessary arguments.
30
31
    Assumes the 1st argument is the 'receiver' (see Command module),
32
    2nd is the method to call on the receiver and the rest are the method's runtime arguments.
33
    """
34
    def construct(self, *args, **kwargs) -> Command:
35
        return Command(*args, **kwargs)
36
37
38
@BaseCommandFactory.register_as_subclass('function')
39
class FunctionCommandFactory(CommandFactoryInterface):
40
    def construct(self, *args, **kwargs) -> Command:
41
        if len(args) < 1:
42
            raise RuntimeError("Will break")
43
        return Command(args[0], '__call__', *args[1:])
44
45
46
class CommandFactory:
47
    """A factory class able to construct new command objects."""
48
    constructors = {k: v().construct for k, v in BaseCommandFactory.subclasses.items()}
49
50
    @classmethod
51
    def pick(cls, *args, **kwargs):
52
        decision = {True: 'function', False: 'generic'}
53
        is_function = hasattr(args[0], '__code__')
54
        dec2 = {'function': lambda x: x[0].__code__.co_name, 'generic': lambda x: type(x[0]).__name__ + '-' + x[1]}
55
        return decision[is_function], kwargs.get('name', dec2[decision[is_function]](args))
56
57
    @classmethod
58
    def create(cls, *args, **kwargs) -> Tuple[Command, str]:
59
60
        key, name = cls.pick(*args, **kwargs)
61
        if len(args) < 1:
62
            raise RuntimeError(args)
63
        return cls.constructors[key](*args), name
64
65
66
@attr.s
67
class MagicCommandFactory(Subject):
68
    """Instances of this class act as callable command factories that notify,
69
    subscribed observers/listeners upon new command object creation.
70
71
    Args:
72
        command_factory (CommandFactory, optional): an instance of a CommandFActory
73
    """
74
    command_factory = attr.ib(init=True, default=CommandFactory())
75
76
    def __call__(self, *args, **kwargs):
77
        self._state, self.name = self.command_factory.create(*args, **kwargs)
78
        self.notify()
79
        return self._state
80