|
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 ..backend_specs import EngineTabularRetriever, EngineTabularIterator, EngineTabularMutator, BackendSpecifications |
|
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( |
|
54
|
|
|
lambda self: {v['interface']: interface_id for interface_id, v in self.backend_interfaces.items()}, |
|
55
|
|
|
takes_self=True)) |
|
56
|
|
|
implementations = attr.ib(init=False, default=attr.Factory(dict)) |
|
57
|
|
|
backends = attr.ib(init=False, default=attr.Factory(dict)) |
|
58
|
|
|
# id of the backend that is currently being registered/built |
|
59
|
|
|
__id: str = attr.ib(init=False, default='') |
|
60
|
|
|
|
|
61
|
|
|
@staticmethod |
|
62
|
|
|
def from_initial_available(backends): |
|
63
|
|
|
engine_backends = EngineBackends(tabular_operators) |
|
64
|
|
|
engine_backends.add(*list(backends)) |
|
65
|
|
|
return engine_backends |
|
66
|
|
|
|
|
67
|
|
|
@property |
|
68
|
|
|
def defined_interfaces(self): |
|
69
|
|
|
return self.backend_interfaces.keys() |
|
70
|
|
|
|
|
71
|
|
|
@property |
|
72
|
|
|
def defined_backend_names(self): |
|
73
|
|
|
return self.implementations.keys() |
|
74
|
|
|
|
|
75
|
|
|
def __iter__(self): |
|
76
|
|
|
return iter((backend_name, interfaces_dict) for backend_name, interfaces_dict in self.implementations.items()) |
|
77
|
|
|
|
|
78
|
|
|
def _get_interface_names(self, backend_id): |
|
79
|
|
|
"""Get the names of the interfaces that the backend has found to implement.""" |
|
80
|
|
|
return self.implementations[backend_id].keys() |
|
81
|
|
|
|
|
82
|
|
|
def add(self, *backend_implementations): |
|
83
|
|
|
for backend_implementation in backend_implementations: |
|
84
|
|
|
self._add(backend_implementation) |
|
85
|
|
|
|
|
86
|
|
|
def _add(self, backend_implementation): |
|
87
|
|
|
self.__id = backend_implementation['backend_id'] |
|
88
|
|
|
implemented_interfaces = backend_implementation['interfaces'] |
|
89
|
|
|
self.implementations[self.__id] =\ |
|
90
|
|
|
{self.name(implementation): implementation for implementation in implemented_interfaces} |
|
91
|
|
|
self.register(backend_implementation) |
|
92
|
|
|
|
|
93
|
|
|
def name(self, interface_implementation): |
|
94
|
|
|
return self._interface_2_name[inspect.getmro(interface_implementation)[1]] |
|
95
|
|
|
|
|
96
|
|
|
def register(self, backend_implementation: dict): |
|
97
|
|
|
for implemented_interface_name in self._get_interface_names(self.__id): |
|
98
|
|
|
self.define_operator(self.__id, implemented_interface_name) |
|
99
|
|
|
# Build |
|
100
|
|
|
backend_specs = BackendSpecifications(self.__id, backend_implementation['backend_name']) |
|
101
|
|
|
backend = EngineBackend.new(self.__id) |
|
102
|
|
|
# init backend attributes |
|
103
|
|
|
backend_specs(backend) |
|
104
|
|
|
backend.datapoints_factory = BroadcastingDatapointsFactory() |
|
105
|
|
|
self.backends[self.__id] = backend |
|
106
|
|
|
|
|
107
|
|
|
def define_operator(self, backend_id, operator_type: str): |
|
108
|
|
|
class_registry = self.backend_interfaces[operator_type]['class_registry'] |
|
109
|
|
|
|
|
110
|
|
|
@attr.s |
|
111
|
|
|
@class_registry.register_as_subclass(backend_id) |
|
112
|
|
|
class _OperatorClass(class_registry): |
|
113
|
|
|
_delegate = attr.ib( |
|
114
|
|
|
default=attr.Factory(lambda: Delegate(self.implementations[backend_id][operator_type]))) |
|
115
|
|
|
|
|
116
|
|
|
def __getattr__(self, name: str): |
|
117
|
|
|
return getattr(self._delegate, name) |
|
118
|
|
|
|
|
119
|
|
|
|
|
120
|
|
|
def magic_backends(): |
|
121
|
|
|
return EngineBackends.from_initial_available(BUILT_IN_BACKENDS_DATA) |
|
122
|
|
|
|