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
|
|
|
|