Passed
Push — mpeta ( d5c584...10dc57 )
by Konstantinos
01:37
created

EngineBackends.add()   A

Complexity

Conditions 2

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
import inspect
2
import types
3
import attr
4
5
from so_magic.data.magic_datapoints_factory import BroadcastingDatapointsFactory
6
from so_magic.data.interfaces import TabularRetriever, TabularIterator, TabularMutator
7
from so_magic.data.backend.backend import EngineBackend
8
from so_magic.data.backend.backend_specs import BackendSpecifications, EngineTabularRetriever, EngineTabularIterator, EngineTabularMutator
9
from .client_code import BACKEND
10
11
12
# INFRASTRUCTURE
13
14
def with_self(function):
15
    def _function(_self, *args, **kwargs):
16
        return function(*args, **kwargs)
17
    return _function
18
19
20
class Delegate:
21
    def __init__(self, tabular_operator):
22
        for _member_name, member in inspect.getmembers(
23
                tabular_operator, predicate=lambda x: any([inspect.ismethod(x), inspect.isfunction(x)])):
24
            if isinstance(member, types.FunctionType):  # if no decorator is used
25
                setattr(self, member.__name__, types.MethodType(member, self))
26
            if isinstance(member, types.MethodType):  # if @classmethod is used
27
                setattr(self, member.__name__, types.MethodType(with_self(member), self))
28
29
30
tabular_operators = {
31
    'retriever': {
32
        'interface': TabularRetriever,
33
        'class_registry': EngineTabularRetriever,
34
    },
35
    'iterator': {
36
        'interface': TabularIterator,
37
        'class_registry': EngineTabularIterator,
38
    },
39
    'mutator': {
40
        'interface': TabularMutator,
41
        'class_registry': EngineTabularMutator,
42
    }
43
}
44
45
BUILT_IN_BACKENDS_DATA = [
46
    BACKEND,
47
]
48
49
50
@attr.s
51
class EngineBackends:
52
    backend_interfaces = attr.ib()
53
    _interface_2_name = attr.ib(init=False, default=attr.Factory(lambda self: {v['interface']: interface_id for interface_id, v in self.backend_interfaces.items()}, takes_self=True))
54
    implementations = attr.ib(init=False, default=attr.Factory(dict))
55
    backends = attr.ib(init=False, default=attr.Factory(dict))
56
    # id of the backend that is currently being registered/built
57
    __id: str = attr.ib(init=False, default='')
58
59
    @staticmethod
60
    def from_initial_available(backends):
61
        engine_backends = EngineBackends(tabular_operators)
62
        engine_backends.add(*list(backends))
63
        return engine_backends
64
    
65
    @property
66
    def defined_interfaces(self):
67
        return self.backend_interfaces.keys()
68
    
69
    @property
70
    def defined_backend_names(self):
71
        return self.implementations.keys()
72
73
    def __iter__(self):
74
        return iter((backend_name, interfaces_dict) for backend_name, interfaces_dict in self.implementations.items())
75
76
    def _get_interface_names(self, backend_id):
77
        """Get the names of the interfaces that the backend has found to implement."""
78
        return self.implementations[backend_id].keys()
79
80
    def add(self, *backend_implementations):
81
        for backend_implementation in backend_implementations:
82
            self._add(backend_implementation)
83
84
    def _add(self, backend_implementation):
85
        self.__id = backend_implementation['backend_id']
86
        implemented_interfaces = backend_implementation['interfaces']
87
        self.implementations[self.__id] = {self.name(implementation): implementation for implementation in implemented_interfaces}
88
        self.register(backend_implementation)
89
90
    def name(self, interface_implementation):
91
        return self._interface_2_name[inspect.getmro(interface_implementation)[1]]
92
93
    def register(self, backend_implementation: dict):
94
        for implemented_interface_name in self._get_interface_names(self.__id):
95
            self.define_operator(self.__id, implemented_interface_name)
96
        # Build
97
        backend_specs = BackendSpecifications(self.__id, backend_implementation['backend_name'])
98
        backend = EngineBackend.new(self.__id)
99
        # init backend attributes
100
        backend_specs(backend)
101
        backend.datapoints_factory = BroadcastingDatapointsFactory()
102
        self.backends[self.__id] = backend
103
104
    def define_operator(self, backend_id, operator_type: str):
105
        class_registry = self.backend_interfaces[operator_type]['class_registry']
106
107
        @attr.s
108
        @class_registry.register_as_subclass(backend_id)
109
        class OperatorClass(class_registry):
110
            _delegate = attr.ib(
111
                default=attr.Factory(lambda: Delegate(self.implementations[backend_id][operator_type])))
112
113
            def __getattr__(self, name: str):
114
                return getattr(self._delegate, name)
115
    
116
117
def magic_backends():
118
    return EngineBackends.from_initial_available(BUILT_IN_BACKENDS_DATA)
119