esd-services-api-client 2.1.2__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/PKG-INFO +1 -1
- esd_services_api_client-2.2.0/esd_services_api_client/_version.py +1 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/README.md +62 -37
- esd_services_api_client-2.2.0/esd_services_api_client/nexus/abstractions/algrorithm_cache.py +100 -0
- esd_services_api_client-2.2.0/esd_services_api_client/nexus/abstractions/input_object.py +63 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/abstractions/nexus_object.py +11 -9
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/algorithms/_baseline_algorithm.py +6 -4
- esd_services_api_client-2.2.0/esd_services_api_client/nexus/algorithms/_remote_algorithm.py +118 -0
- esd_services_api_client-2.2.0/esd_services_api_client/nexus/algorithms/forked_algorithm.py +124 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/algorithms/minimalistic.py +5 -1
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/algorithms/recursive.py +5 -1
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/core/app_core.py +5 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/core/app_dependencies.py +20 -1
- esd_services_api_client-2.2.0/esd_services_api_client/nexus/exceptions/cache_errors.py +49 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/exceptions/startup_error.py +15 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/input/__init__.py +0 -1
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/input/input_processor.py +11 -58
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/input/input_reader.py +9 -5
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/pyproject.toml +1 -1
- esd_services_api_client-2.1.2/esd_services_api_client/_version.py +0 -1
- esd_services_api_client-2.1.2/esd_services_api_client/nexus/input/_functions.py +0 -89
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/LICENSE +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/README.md +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/beast/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/beast/v3/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/beast/v3/_connector.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/beast/v3/_models.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/README.md +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/_auth.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/_base.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/_connector.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/boxer/_models.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/common/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/crystal/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/crystal/_api_versions.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/crystal/_connector.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/crystal/_models.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/abstractions/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/abstractions/logger_factory.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/abstractions/socket_provider.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/algorithms/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/algorithms/distributed.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/configurations/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/configurations/algorithm_configuration.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/core/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/exceptions/__init__.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/exceptions/_nexus_error.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/exceptions/input_reader_error.py +0 -0
- {esd_services_api_client-2.1.2 → esd_services_api_client-2.2.0}/esd_services_api_client/nexus/input/payload_reader.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = '2.2.0'
|
@@ -28,7 +28,9 @@ from adapta.storage.query_enabled_store import QueryEnabledStore
|
|
28
28
|
from dataclasses_json import DataClassJsonMixin
|
29
29
|
from injector import inject
|
30
30
|
|
31
|
+
from esd_services_api_client.nexus.abstractions.algrorithm_cache import InputCache
|
31
32
|
from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
|
33
|
+
from esd_services_api_client.nexus.abstractions.nexus_object import AlgorithmResult
|
32
34
|
from esd_services_api_client.nexus.abstractions.socket_provider import (
|
33
35
|
ExternalSocketProvider,
|
34
36
|
)
|
@@ -36,7 +38,6 @@ from esd_services_api_client.nexus.configurations.algorithm_configuration import
|
|
36
38
|
NexusConfiguration,
|
37
39
|
)
|
38
40
|
from esd_services_api_client.nexus.core.app_core import Nexus
|
39
|
-
from esd_services_api_client.nexus.abstractions.nexus_object import AlgorithmResult
|
40
41
|
from esd_services_api_client.nexus.algorithms import MinimalisticAlgorithm
|
41
42
|
from esd_services_api_client.nexus.input import InputReader, InputProcessor
|
42
43
|
|
@@ -127,12 +128,6 @@ class MockRequestHandler(BaseHTTPRequestHandler):
|
|
127
128
|
|
128
129
|
|
129
130
|
class XReader(InputReader[MyAlgorithmPayload, pandas.DataFrame]):
|
130
|
-
async def _context_open(self):
|
131
|
-
pass
|
132
|
-
|
133
|
-
async def _context_close(self):
|
134
|
-
pass
|
135
|
-
|
136
131
|
@inject
|
137
132
|
def __init__(
|
138
133
|
self,
|
@@ -141,7 +136,8 @@ class XReader(InputReader[MyAlgorithmPayload, pandas.DataFrame]):
|
|
141
136
|
logger_factory: LoggerFactory,
|
142
137
|
payload: MyAlgorithmPayload,
|
143
138
|
socket_provider: ExternalSocketProvider,
|
144
|
-
*readers: "InputReader"
|
139
|
+
*readers: "InputReader",
|
140
|
+
cache: InputCache
|
145
141
|
):
|
146
142
|
super().__init__(
|
147
143
|
socket=socket_provider.socket("x"),
|
@@ -149,10 +145,11 @@ class XReader(InputReader[MyAlgorithmPayload, pandas.DataFrame]):
|
|
149
145
|
metrics_provider=metrics_provider,
|
150
146
|
logger_factory=logger_factory,
|
151
147
|
payload=payload,
|
148
|
+
cache=cache,
|
152
149
|
*readers
|
153
150
|
)
|
154
151
|
|
155
|
-
async def _read_input(self) -> pandas.DataFrame:
|
152
|
+
async def _read_input(self, **_) -> pandas.DataFrame:
|
156
153
|
self._logger.info(
|
157
154
|
"Payload: {payload}; Socket path: {socket_path}",
|
158
155
|
payload=self._payload.to_json(),
|
@@ -162,12 +159,6 @@ class XReader(InputReader[MyAlgorithmPayload, pandas.DataFrame]):
|
|
162
159
|
|
163
160
|
|
164
161
|
class YReader(InputReader[MyAlgorithmPayload2, pandas.DataFrame]):
|
165
|
-
async def _context_open(self):
|
166
|
-
pass
|
167
|
-
|
168
|
-
async def _context_close(self):
|
169
|
-
pass
|
170
|
-
|
171
162
|
@inject
|
172
163
|
def __init__(
|
173
164
|
self,
|
@@ -176,7 +167,8 @@ class YReader(InputReader[MyAlgorithmPayload2, pandas.DataFrame]):
|
|
176
167
|
logger_factory: LoggerFactory,
|
177
168
|
payload: MyAlgorithmPayload2,
|
178
169
|
socket_provider: ExternalSocketProvider,
|
179
|
-
*readers: "InputReader"
|
170
|
+
*readers: "InputReader",
|
171
|
+
cache: InputCache
|
180
172
|
):
|
181
173
|
super().__init__(
|
182
174
|
socket=socket_provider.socket("y"),
|
@@ -184,10 +176,11 @@ class YReader(InputReader[MyAlgorithmPayload2, pandas.DataFrame]):
|
|
184
176
|
metrics_provider=metrics_provider,
|
185
177
|
logger_factory=logger_factory,
|
186
178
|
payload=payload,
|
179
|
+
cache=cache,
|
187
180
|
*readers
|
188
181
|
)
|
189
182
|
|
190
|
-
async def _read_input(self) -> pandas.DataFrame:
|
183
|
+
async def _read_input(self, **_) -> pandas.DataFrame:
|
191
184
|
self._logger.info(
|
192
185
|
"Payload: {payload}; Socket path: {socket_path}",
|
193
186
|
payload=self._payload.to_json(),
|
@@ -196,39 +189,59 @@ class YReader(InputReader[MyAlgorithmPayload2, pandas.DataFrame]):
|
|
196
189
|
return pandas.DataFrame([{"a": 10, "b": 12}, {"a": 11, "b": 13}])
|
197
190
|
|
198
191
|
|
199
|
-
class
|
200
|
-
|
201
|
-
|
192
|
+
class XProcessor(InputProcessor[MyAlgorithmPayload, pandas.DataFrame]):
|
193
|
+
@inject
|
194
|
+
def __init__(
|
195
|
+
self,
|
196
|
+
x: XReader,
|
197
|
+
metrics_provider: MetricsProvider,
|
198
|
+
logger_factory: LoggerFactory,
|
199
|
+
my_conf: MyAlgorithmConfiguration,
|
200
|
+
cache: InputCache,
|
201
|
+
):
|
202
|
+
super().__init__(
|
203
|
+
x,
|
204
|
+
metrics_provider=metrics_provider,
|
205
|
+
logger_factory=logger_factory,
|
206
|
+
payload=None,
|
207
|
+
cache=cache,
|
208
|
+
)
|
209
|
+
|
210
|
+
self.conf = my_conf
|
211
|
+
|
212
|
+
async def _process_input(
|
213
|
+
self, x: pandas.DataFrame, **_
|
214
|
+
) -> pandas.DataFrame:
|
215
|
+
self._logger.info("Config: {config}", config=self.conf.to_json())
|
216
|
+
return x.assign(c=[-1, 1])
|
202
217
|
|
203
|
-
async def _context_close(self):
|
204
|
-
pass
|
205
218
|
|
219
|
+
class YProcessor(InputProcessor[MyAlgorithmPayload, pandas.DataFrame]):
|
206
220
|
@inject
|
207
221
|
def __init__(
|
208
222
|
self,
|
209
|
-
x: XReader,
|
210
223
|
y: YReader,
|
211
224
|
metrics_provider: MetricsProvider,
|
212
225
|
logger_factory: LoggerFactory,
|
213
226
|
my_conf: MyAlgorithmConfiguration,
|
227
|
+
cache: InputCache,
|
214
228
|
):
|
215
229
|
super().__init__(
|
216
|
-
x,
|
217
230
|
y,
|
218
231
|
metrics_provider=metrics_provider,
|
219
232
|
logger_factory=logger_factory,
|
220
233
|
payload=None,
|
234
|
+
cache=cache,
|
221
235
|
)
|
222
236
|
|
223
237
|
self.conf = my_conf
|
224
238
|
|
225
|
-
async def
|
239
|
+
async def _process_input(
|
240
|
+
self, y: pandas.DataFrame, **_
|
241
|
+
) -> pandas.DataFrame:
|
226
242
|
self._logger.info("Config: {config}", config=self.conf.to_json())
|
227
|
-
|
228
|
-
|
229
|
-
"x_ready": inputs["x"].assign(c=[-1, 1]),
|
230
|
-
"y_ready": inputs["y"].assign(c=[-1, 1]),
|
231
|
-
}
|
243
|
+
return y.assign(c=[-1, 1])
|
244
|
+
|
232
245
|
|
233
246
|
@dataclass
|
234
247
|
class MyResult(AlgorithmResult):
|
@@ -240,8 +253,8 @@ class MyResult(AlgorithmResult):
|
|
240
253
|
|
241
254
|
def to_kwargs(self) -> dict[str, Any]:
|
242
255
|
pass
|
243
|
-
|
244
|
-
|
256
|
+
|
257
|
+
|
245
258
|
class MyAlgorithm(MinimalisticAlgorithm[MyAlgorithmPayload]):
|
246
259
|
async def _context_open(self):
|
247
260
|
pass
|
@@ -250,11 +263,22 @@ class MyAlgorithm(MinimalisticAlgorithm[MyAlgorithmPayload]):
|
|
250
263
|
pass
|
251
264
|
|
252
265
|
@inject
|
253
|
-
def __init__(
|
254
|
-
|
266
|
+
def __init__(
|
267
|
+
self,
|
268
|
+
metrics_provider: MetricsProvider,
|
269
|
+
logger_factory: LoggerFactory,
|
270
|
+
x_processor: XProcessor,
|
271
|
+
y_processor: YProcessor,
|
272
|
+
cache: InputCache,
|
273
|
+
):
|
274
|
+
super().__init__(
|
275
|
+
metrics_provider, logger_factory, x_processor, y_processor, cache=cache
|
276
|
+
)
|
255
277
|
|
256
|
-
async def _run(
|
257
|
-
|
278
|
+
async def _run(
|
279
|
+
self, x: pandas.DataFrame, y: pandas.DataFrame, **kwargs
|
280
|
+
) -> MyResult:
|
281
|
+
return MyResult(x, y)
|
258
282
|
|
259
283
|
|
260
284
|
async def main():
|
@@ -270,7 +294,8 @@ async def main():
|
|
270
294
|
await Nexus.create()
|
271
295
|
.add_reader(XReader)
|
272
296
|
.add_reader(YReader)
|
273
|
-
.use_processor(
|
297
|
+
.use_processor(XProcessor)
|
298
|
+
.use_processor(YProcessor)
|
274
299
|
.use_algorithm(MyAlgorithm)
|
275
300
|
.inject_configuration(MyAlgorithmConfiguration)
|
276
301
|
.inject_payload(MyAlgorithmPayload, MyAlgorithmPayload2)
|
@@ -0,0 +1,100 @@
|
|
1
|
+
"""
|
2
|
+
Simple in-memory cache for readers and processors
|
3
|
+
"""
|
4
|
+
|
5
|
+
# Copyright (c) 2023-2024. ECCO Sneaks & Data
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
import asyncio
|
21
|
+
from typing import final, Type
|
22
|
+
|
23
|
+
import azure.core.exceptions
|
24
|
+
import deltalake
|
25
|
+
|
26
|
+
from esd_services_api_client.nexus.abstractions.input_object import InputObject
|
27
|
+
from esd_services_api_client.nexus.abstractions.nexus_object import TResult, TPayload
|
28
|
+
from esd_services_api_client.nexus.exceptions.cache_errors import (
|
29
|
+
FatalCachingError,
|
30
|
+
TransientCachingError,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
@final
|
35
|
+
class InputCache:
|
36
|
+
"""
|
37
|
+
In-memory cache for Nexus input readers/processors
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self):
|
41
|
+
self._cache: dict[str, TResult] = {}
|
42
|
+
|
43
|
+
def _resolve_exc_type(
|
44
|
+
self, ex: BaseException
|
45
|
+
) -> Type[FatalCachingError] | Type[TransientCachingError]:
|
46
|
+
"""
|
47
|
+
Resolve base exception into a specific Nexus exception.
|
48
|
+
"""
|
49
|
+
match type(ex):
|
50
|
+
case azure.core.exceptions.HttpResponseError, deltalake.PyDeltaTableError:
|
51
|
+
return TransientCachingError
|
52
|
+
case azure.core.exceptions.AzureError, azure.core.exceptions.ClientAuthenticationError:
|
53
|
+
return FatalCachingError
|
54
|
+
case _:
|
55
|
+
return FatalCachingError
|
56
|
+
|
57
|
+
async def resolve(
|
58
|
+
self,
|
59
|
+
*readers_or_processors: InputObject[TPayload, TResult],
|
60
|
+
**kwargs,
|
61
|
+
) -> dict[str, TResult]:
|
62
|
+
"""
|
63
|
+
Concurrently resolve `data` property of all readers by invoking their `read` method.
|
64
|
+
"""
|
65
|
+
|
66
|
+
def get_result(alias: str, completed_task: asyncio.Task) -> TResult:
|
67
|
+
object_exc = completed_task.exception()
|
68
|
+
if object_exc:
|
69
|
+
raise self._resolve_exc_type(object_exc)(alias) from object_exc
|
70
|
+
|
71
|
+
return completed_task.result()
|
72
|
+
|
73
|
+
async def _execute(nexus_input: InputObject) -> TResult:
|
74
|
+
async with nexus_input as instance:
|
75
|
+
result = await nexus_input.process(**kwargs)
|
76
|
+
|
77
|
+
self._cache[instance.cache_key()] = result
|
78
|
+
|
79
|
+
return result
|
80
|
+
|
81
|
+
cached = {
|
82
|
+
reader_or_processor.__class__.alias(): reader_or_processor.data
|
83
|
+
for reader_or_processor in readers_or_processors
|
84
|
+
if reader_or_processor.cache_key() in self._cache
|
85
|
+
}
|
86
|
+
if len(cached) == len(readers_or_processors):
|
87
|
+
return cached
|
88
|
+
|
89
|
+
read_tasks: dict[str, asyncio.Task] = {
|
90
|
+
reader.__class__.alias(): asyncio.create_task(_execute(reader))
|
91
|
+
for reader in readers_or_processors
|
92
|
+
if reader.cache_key() not in self._cache
|
93
|
+
}
|
94
|
+
|
95
|
+
if len(read_tasks) > 0:
|
96
|
+
await asyncio.wait(fs=read_tasks.values())
|
97
|
+
|
98
|
+
return {
|
99
|
+
alias: get_result(alias, task) for alias, task in read_tasks.items()
|
100
|
+
} | cached
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"""
|
2
|
+
Base class for input reading/processing.
|
3
|
+
"""
|
4
|
+
|
5
|
+
# Copyright (c) 2023-2024. ECCO Sneaks & Data
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
import base64
|
21
|
+
import os
|
22
|
+
from abc import ABC, abstractmethod
|
23
|
+
|
24
|
+
from esd_services_api_client.nexus.abstractions.nexus_object import (
|
25
|
+
TPayload,
|
26
|
+
TResult,
|
27
|
+
NexusObject,
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
class InputObject(NexusObject[TPayload, TResult], ABC):
|
32
|
+
"""
|
33
|
+
Base class for input processing and reader objects.
|
34
|
+
"""
|
35
|
+
|
36
|
+
async def _context_open(self):
|
37
|
+
"""
|
38
|
+
Optional actions to perform on context activation.
|
39
|
+
"""
|
40
|
+
|
41
|
+
async def _context_close(self):
|
42
|
+
"""
|
43
|
+
Optional actions to perform on context closure.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def cache_key(self) -> str:
|
47
|
+
"""
|
48
|
+
Unique identifier for this Nexus object, can be used to in-memory or external caching.
|
49
|
+
"""
|
50
|
+
return f"{base64.b64encode(hex(id(self)).encode('utf-8')).decode('utf-8')}_{os.getpid()}_{self.__class__.__name__}"
|
51
|
+
|
52
|
+
@property
|
53
|
+
def data(self) -> TResult | None:
|
54
|
+
"""
|
55
|
+
Data bound to this object.
|
56
|
+
"""
|
57
|
+
return None
|
58
|
+
|
59
|
+
@abstractmethod
|
60
|
+
async def process(self, **kwargs) -> TResult:
|
61
|
+
"""
|
62
|
+
Executes input processing logic (read or transform)
|
63
|
+
"""
|
@@ -1,8 +1,6 @@
|
|
1
1
|
"""
|
2
2
|
Base classes for all objects used by Nexus.
|
3
3
|
"""
|
4
|
-
import re
|
5
|
-
|
6
4
|
# Copyright (c) 2023-2024. ECCO Sneaks & Data
|
7
5
|
#
|
8
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -20,11 +18,13 @@ import re
|
|
20
18
|
|
21
19
|
|
22
20
|
from abc import ABC, abstractmethod
|
21
|
+
import re
|
23
22
|
from typing import Generic, TypeVar, Union, Any
|
24
23
|
|
25
24
|
import pandas
|
26
25
|
import polars
|
27
26
|
from adapta.metrics import MetricsProvider
|
27
|
+
from dataclasses_json.stringcase import snakecase
|
28
28
|
|
29
29
|
from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
|
30
30
|
|
@@ -92,11 +92,13 @@ class NexusObject(Generic[TPayload, TResult], ABC):
|
|
92
92
|
"""
|
93
93
|
Alias to identify this reader's output
|
94
94
|
"""
|
95
|
-
return
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
95
|
+
return snakecase(
|
96
|
+
re.sub(
|
97
|
+
r"(?<!^)(?=[A-Z])",
|
98
|
+
"_",
|
99
|
+
cls.__name__.lower()
|
100
|
+
.replace("reader", "")
|
101
|
+
.replace("processor", "")
|
102
|
+
.replace("algorithm", ""),
|
103
|
+
)
|
102
104
|
)
|
@@ -19,11 +19,12 @@
|
|
19
19
|
|
20
20
|
|
21
21
|
from abc import abstractmethod
|
22
|
-
from functools import
|
22
|
+
from functools import partial
|
23
23
|
|
24
24
|
from adapta.metrics import MetricsProvider
|
25
25
|
from adapta.utils.decorators import run_time_metrics_async
|
26
26
|
|
27
|
+
from esd_services_api_client.nexus.abstractions.algrorithm_cache import InputCache
|
27
28
|
from esd_services_api_client.nexus.abstractions.nexus_object import (
|
28
29
|
NexusObject,
|
29
30
|
TPayload,
|
@@ -32,7 +33,6 @@ from esd_services_api_client.nexus.abstractions.nexus_object import (
|
|
32
33
|
from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
|
33
34
|
from esd_services_api_client.nexus.input.input_processor import (
|
34
35
|
InputProcessor,
|
35
|
-
resolve_processors,
|
36
36
|
)
|
37
37
|
|
38
38
|
|
@@ -46,9 +46,11 @@ class BaselineAlgorithm(NexusObject[TPayload, AlgorithmResult]):
|
|
46
46
|
metrics_provider: MetricsProvider,
|
47
47
|
logger_factory: LoggerFactory,
|
48
48
|
*input_processors: InputProcessor,
|
49
|
+
cache: InputCache,
|
49
50
|
):
|
50
51
|
super().__init__(metrics_provider, logger_factory)
|
51
52
|
self._input_processors = input_processors
|
53
|
+
self._cache = cache
|
52
54
|
|
53
55
|
@abstractmethod
|
54
56
|
async def _run(self, **kwargs) -> AlgorithmResult:
|
@@ -75,11 +77,11 @@ class BaselineAlgorithm(NexusObject[TPayload, AlgorithmResult]):
|
|
75
77
|
async def _measured_run(**run_args):
|
76
78
|
return await self._run(**run_args)
|
77
79
|
|
78
|
-
results = await
|
80
|
+
results = await self._cache.resolve(*self._input_processors, **kwargs)
|
79
81
|
|
80
82
|
return await partial(
|
81
83
|
_measured_run,
|
82
|
-
**
|
84
|
+
**results,
|
83
85
|
metric_tags=self._metric_tags,
|
84
86
|
metrics_provider=self._metrics_provider,
|
85
87
|
logger=self._logger,
|
@@ -0,0 +1,118 @@
|
|
1
|
+
"""
|
2
|
+
Remotely executed algorithm
|
3
|
+
"""
|
4
|
+
|
5
|
+
# Copyright (c) 2023-2024. ECCO Sneaks & Data
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
|
21
|
+
from abc import abstractmethod
|
22
|
+
from functools import partial
|
23
|
+
|
24
|
+
from adapta.metrics import MetricsProvider
|
25
|
+
from adapta.utils.decorators import run_time_metrics_async
|
26
|
+
|
27
|
+
from esd_services_api_client.crystal import CrystalConnector, AlgorithmConfiguration
|
28
|
+
from esd_services_api_client.nexus.abstractions.algrorithm_cache import InputCache
|
29
|
+
from esd_services_api_client.nexus.abstractions.nexus_object import (
|
30
|
+
NexusObject,
|
31
|
+
TPayload,
|
32
|
+
AlgorithmResult,
|
33
|
+
)
|
34
|
+
from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
|
35
|
+
from esd_services_api_client.nexus.input.input_processor import (
|
36
|
+
InputProcessor,
|
37
|
+
)
|
38
|
+
from esd_services_api_client.nexus.input.payload_reader import AlgorithmPayload
|
39
|
+
|
40
|
+
|
41
|
+
class RemoteAlgorithm(NexusObject[TPayload, AlgorithmResult]):
|
42
|
+
"""
|
43
|
+
Base class for all algorithm implementations.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
metrics_provider: MetricsProvider,
|
49
|
+
logger_factory: LoggerFactory,
|
50
|
+
remote_client: CrystalConnector,
|
51
|
+
remote_name: str,
|
52
|
+
remote_config: AlgorithmConfiguration,
|
53
|
+
*input_processors: InputProcessor,
|
54
|
+
cache: InputCache,
|
55
|
+
):
|
56
|
+
super().__init__(metrics_provider, logger_factory)
|
57
|
+
self._input_processors = input_processors
|
58
|
+
self._remote_client = remote_client
|
59
|
+
self._remote_name = remote_name
|
60
|
+
self._remote_config = remote_config
|
61
|
+
self._cache = cache
|
62
|
+
|
63
|
+
@abstractmethod
|
64
|
+
def _generate_tag(self) -> str:
|
65
|
+
"""
|
66
|
+
Generates a submission tag.
|
67
|
+
"""
|
68
|
+
|
69
|
+
@abstractmethod
|
70
|
+
def _transform_submission_result(
|
71
|
+
self, request_id: str, tag: str
|
72
|
+
) -> AlgorithmResult:
|
73
|
+
"""
|
74
|
+
Called after submitting a remote run. Use this to enrich your output with remote run id and tag.
|
75
|
+
"""
|
76
|
+
|
77
|
+
@abstractmethod
|
78
|
+
async def _run(self, **kwargs) -> AlgorithmPayload:
|
79
|
+
"""
|
80
|
+
Core logic for this algorithm. Implementing this method is mandatory.
|
81
|
+
"""
|
82
|
+
|
83
|
+
@property
|
84
|
+
def _metric_tags(self) -> dict[str, str]:
|
85
|
+
return {"algorithm": self.__class__.alias()}
|
86
|
+
|
87
|
+
async def run(self, **kwargs) -> AlgorithmResult:
|
88
|
+
"""
|
89
|
+
Coroutine that executes the algorithm logic.
|
90
|
+
"""
|
91
|
+
|
92
|
+
@run_time_metrics_async(
|
93
|
+
metric_name="algorthm_run",
|
94
|
+
on_finish_message_template="Launched a new remote {algorithm} in {elapsed:.2f}s seconds",
|
95
|
+
template_args={
|
96
|
+
"algorithm": self.__class__.alias().upper(),
|
97
|
+
},
|
98
|
+
)
|
99
|
+
async def _measured_run(**run_args) -> AlgorithmResult:
|
100
|
+
payload = await self._run(**run_args)
|
101
|
+
tag = self._generate_tag()
|
102
|
+
request_id = self._remote_client.create_run(
|
103
|
+
algorithm=self._remote_name,
|
104
|
+
payload=payload.to_dict(),
|
105
|
+
custom_config=self._remote_config,
|
106
|
+
tag=tag,
|
107
|
+
)
|
108
|
+
return self._transform_submission_result(request_id, tag)
|
109
|
+
|
110
|
+
results = await self._cache.resolve(*self._input_processors, **kwargs)
|
111
|
+
|
112
|
+
return await partial(
|
113
|
+
_measured_run,
|
114
|
+
**results,
|
115
|
+
metric_tags=self._metric_tags,
|
116
|
+
metrics_provider=self._metrics_provider,
|
117
|
+
logger=self._logger,
|
118
|
+
)()
|