esd-services-api-client 2.2.0__tar.gz → 2.2.2__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.2.0 → esd_services_api_client-2.2.2}/PKG-INFO +1 -1
- esd_services_api_client-2.2.2/esd_services_api_client/_version.py +1 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/crystal/_connector.py +4 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/crystal/_models.py +12 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/nexus_object.py +7 -1
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/_baseline_algorithm.py +10 -4
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/minimalistic.py +4 -1
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/core/app_core.py +9 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/core/app_dependencies.py +2 -0
- esd_services_api_client-2.2.2/esd_services_api_client/nexus/telemetry/__init__.py +0 -0
- esd_services_api_client-2.2.2/esd_services_api_client/nexus/telemetry/recorder.py +105 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/pyproject.toml +1 -1
- esd_services_api_client-2.2.0/esd_services_api_client/_version.py +0 -1
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/LICENSE +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/README.md +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/beast/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/beast/v3/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/beast/v3/_connector.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/beast/v3/_models.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/README.md +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/_auth.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/_base.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/_connector.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/boxer/_models.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/common/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/crystal/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/crystal/_api_versions.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/README.md +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/algrorithm_cache.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/input_object.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/logger_factory.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/abstractions/socket_provider.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/_remote_algorithm.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/distributed.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/forked_algorithm.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/algorithms/recursive.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/configurations/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/configurations/algorithm_configuration.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/core/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/exceptions/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/exceptions/_nexus_error.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/exceptions/cache_errors.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/exceptions/input_reader_error.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/exceptions/startup_error.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/input/__init__.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/input/input_processor.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/input/input_reader.py +0 -0
- {esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/nexus/input/payload_reader.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = '2.2.2'
|
@@ -37,6 +37,7 @@ from esd_services_api_client.crystal._models import (
|
|
37
37
|
AlgorithmRequest,
|
38
38
|
AlgorithmConfiguration,
|
39
39
|
RequestLifeCycleStage,
|
40
|
+
ParentRequest,
|
40
41
|
)
|
41
42
|
|
42
43
|
T = TypeVar("T") # pylint: disable=C0103
|
@@ -154,6 +155,7 @@ class CrystalConnector:
|
|
154
155
|
algorithm: str,
|
155
156
|
payload: Dict,
|
156
157
|
custom_config: Optional[AlgorithmConfiguration] = None,
|
158
|
+
parent_request: Optional[ParentRequest] = None,
|
157
159
|
tag: Optional[str] = None,
|
158
160
|
) -> str:
|
159
161
|
"""
|
@@ -162,6 +164,7 @@ class CrystalConnector:
|
|
162
164
|
:param algorithm: Name of a connected algorithm.
|
163
165
|
:param payload: Algorithm payload.
|
164
166
|
:param custom_config: Customized config for this run.
|
167
|
+
:param parent_request: Parent request for this run.
|
165
168
|
:param tag: Client-side submission identifier.
|
166
169
|
:return: Request identifier assigned to the job by Crystal.
|
167
170
|
"""
|
@@ -176,6 +179,7 @@ class CrystalConnector:
|
|
176
179
|
algorithm_name=algorithm,
|
177
180
|
algorithm_parameters=payload,
|
178
181
|
custom_configuration=custom_config,
|
182
|
+
parent_request=parent_request,
|
179
183
|
tag=tag,
|
180
184
|
).to_dict()
|
181
185
|
|
@@ -133,6 +133,17 @@ class AlgorithmConfiguration(DataClassJsonMixin):
|
|
133
133
|
speculative_attempts: Optional[int] = None
|
134
134
|
|
135
135
|
|
136
|
+
@dataclass_json(letter_case=LetterCase.CAMEL)
|
137
|
+
@dataclass
|
138
|
+
class ParentRequest(DataClassJsonMixin):
|
139
|
+
"""
|
140
|
+
Used to specify crystal parent job for a new crystal job.
|
141
|
+
"""
|
142
|
+
|
143
|
+
request_id: Optional[str] = None
|
144
|
+
algorithm_name: Optional[str] = None
|
145
|
+
|
146
|
+
|
136
147
|
@dataclass_json(letter_case=LetterCase.CAMEL)
|
137
148
|
@dataclass
|
138
149
|
class AlgorithmRequest(DataClassJsonMixin):
|
@@ -143,4 +154,5 @@ class AlgorithmRequest(DataClassJsonMixin):
|
|
143
154
|
algorithm_parameters: Dict
|
144
155
|
algorithm_name: Optional[str] = None
|
145
156
|
custom_configuration: Optional[AlgorithmConfiguration] = None
|
157
|
+
parent_request: Optional[ParentRequest] = None
|
146
158
|
tag: Optional[str] = None
|
@@ -53,7 +53,7 @@ TResult = TypeVar( # pylint: disable=C0103
|
|
53
53
|
)
|
54
54
|
|
55
55
|
|
56
|
-
class
|
56
|
+
class NexusCoreObject(ABC):
|
57
57
|
"""
|
58
58
|
Base class for all Nexus objects.
|
59
59
|
"""
|
@@ -87,6 +87,12 @@ class NexusObject(Generic[TPayload, TResult], ABC):
|
|
87
87
|
Optional actions to perform on context closure.
|
88
88
|
"""
|
89
89
|
|
90
|
+
|
91
|
+
class NexusObject(Generic[TPayload, TResult], NexusCoreObject, ABC):
|
92
|
+
"""
|
93
|
+
Base class for all Nexus objects.
|
94
|
+
"""
|
95
|
+
|
90
96
|
@classmethod
|
91
97
|
def alias(cls) -> str:
|
92
98
|
"""
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""
|
2
2
|
Base algorithm
|
3
3
|
"""
|
4
|
-
|
5
4
|
# Copyright (c) 2023-2024. ECCO Sneaks & Data
|
6
5
|
#
|
7
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -17,7 +16,6 @@
|
|
17
16
|
# limitations under the License.
|
18
17
|
#
|
19
18
|
|
20
|
-
|
21
19
|
from abc import abstractmethod
|
22
20
|
from functools import partial
|
23
21
|
|
@@ -51,6 +49,14 @@ class BaselineAlgorithm(NexusObject[TPayload, AlgorithmResult]):
|
|
51
49
|
super().__init__(metrics_provider, logger_factory)
|
52
50
|
self._input_processors = input_processors
|
53
51
|
self._cache = cache
|
52
|
+
self._inputs: dict = {}
|
53
|
+
|
54
|
+
@property
|
55
|
+
def inputs(self) -> dict:
|
56
|
+
"""
|
57
|
+
Inputs generated for this algorithm run.
|
58
|
+
"""
|
59
|
+
return self._inputs
|
54
60
|
|
55
61
|
@abstractmethod
|
56
62
|
async def _run(self, **kwargs) -> AlgorithmResult:
|
@@ -77,11 +83,11 @@ class BaselineAlgorithm(NexusObject[TPayload, AlgorithmResult]):
|
|
77
83
|
async def _measured_run(**run_args):
|
78
84
|
return await self._run(**run_args)
|
79
85
|
|
80
|
-
|
86
|
+
self._inputs = await self._cache.resolve(*self._input_processors, **kwargs)
|
81
87
|
|
82
88
|
return await partial(
|
83
89
|
_measured_run,
|
84
|
-
**
|
90
|
+
**self._inputs,
|
85
91
|
metric_tags=self._metric_tags,
|
86
92
|
metrics_provider=self._metrics_provider,
|
87
93
|
logger=self._logger,
|
@@ -45,5 +45,8 @@ class MinimalisticAlgorithm(BaselineAlgorithm[TPayload], ABC):
|
|
45
45
|
cache: InputCache,
|
46
46
|
):
|
47
47
|
super().__init__(
|
48
|
-
metrics_provider,
|
48
|
+
metrics_provider,
|
49
|
+
logger_factory,
|
50
|
+
*input_processors,
|
51
|
+
cache=cache,
|
49
52
|
)
|
@@ -58,6 +58,7 @@ from esd_services_api_client.nexus.input.payload_reader import (
|
|
58
58
|
AlgorithmPayloadReader,
|
59
59
|
AlgorithmPayload,
|
60
60
|
)
|
61
|
+
from esd_services_api_client.nexus.telemetry.recorder import TelemetryRecorder
|
61
62
|
|
62
63
|
|
63
64
|
def is_transient_exception(exception: Optional[BaseException]) -> Optional[bool]:
|
@@ -244,9 +245,11 @@ class Nexus:
|
|
244
245
|
"""
|
245
246
|
Activates the run sequence.
|
246
247
|
"""
|
248
|
+
|
247
249
|
self._injector = Injector(self._configurator.injection_binds)
|
248
250
|
|
249
251
|
algorithm: BaselineAlgorithm = self._injector.get(self._algorithm_class)
|
252
|
+
telemetry_recorder: TelemetryRecorder = self._injector.get(TelemetryRecorder)
|
250
253
|
|
251
254
|
async with algorithm as instance:
|
252
255
|
self._algorithm_run_task = asyncio.create_task(
|
@@ -266,6 +269,12 @@ class Nexus:
|
|
266
269
|
if len(on_complete_tasks) > 0:
|
267
270
|
await asyncio.wait(on_complete_tasks)
|
268
271
|
|
272
|
+
# record telemetry
|
273
|
+
async with telemetry_recorder as recorder:
|
274
|
+
await recorder.record(
|
275
|
+
run_id=self._run_args.request_id, **algorithm.inputs
|
276
|
+
)
|
277
|
+
|
269
278
|
# dispose of QES instance gracefully as it might hold open connections
|
270
279
|
qes = self._injector.get(QueryEnabledStore)
|
271
280
|
qes.close()
|
@@ -44,6 +44,7 @@ from esd_services_api_client.nexus.input.input_reader import InputReader
|
|
44
44
|
from esd_services_api_client.nexus.input.payload_reader import (
|
45
45
|
AlgorithmPayload,
|
46
46
|
)
|
47
|
+
from esd_services_api_client.nexus.telemetry.recorder import TelemetryRecorder
|
47
48
|
|
48
49
|
|
49
50
|
@final
|
@@ -195,6 +196,7 @@ class ServiceConfigurator:
|
|
195
196
|
StorageClientModule(),
|
196
197
|
ExternalSocketsModule(),
|
197
198
|
CacheModule(),
|
199
|
+
type(f"{TelemetryRecorder.__name__}Module", (Module,), {})(),
|
198
200
|
]
|
199
201
|
|
200
202
|
@property
|
File without changes
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""
|
2
|
+
Telemetry recording module.
|
3
|
+
"""
|
4
|
+
import asyncio
|
5
|
+
import os
|
6
|
+
from functools import partial
|
7
|
+
from typing import final
|
8
|
+
|
9
|
+
import pandas as pd
|
10
|
+
from adapta.metrics import MetricsProvider
|
11
|
+
from adapta.process_communication import DataSocket
|
12
|
+
from adapta.storage.blob.base import StorageClient
|
13
|
+
from adapta.storage.models.format import (
|
14
|
+
DictJsonSerializationFormat,
|
15
|
+
DataFrameParquetSerializationFormat,
|
16
|
+
)
|
17
|
+
from injector import inject, singleton
|
18
|
+
|
19
|
+
from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
|
20
|
+
from esd_services_api_client.nexus.abstractions.nexus_object import NexusCoreObject
|
21
|
+
|
22
|
+
|
23
|
+
@final
|
24
|
+
@singleton
|
25
|
+
class TelemetryRecorder(NexusCoreObject):
|
26
|
+
"""
|
27
|
+
Class for instantiating a telemetry recorder that will save all algorithm inputs (run method arguments) to a persistent location.
|
28
|
+
"""
|
29
|
+
|
30
|
+
async def _context_open(self):
|
31
|
+
pass
|
32
|
+
|
33
|
+
async def _context_close(self):
|
34
|
+
pass
|
35
|
+
|
36
|
+
@inject
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
storage_client: StorageClient,
|
40
|
+
metrics_provider: MetricsProvider,
|
41
|
+
logger_factory: LoggerFactory,
|
42
|
+
):
|
43
|
+
super().__init__(metrics_provider, logger_factory)
|
44
|
+
self._storage_client = storage_client
|
45
|
+
self._telemetry_base_path = os.getenv("NEXUS__TELEMETRY_PATH")
|
46
|
+
|
47
|
+
async def record(self, run_id: str, **telemetry_args):
|
48
|
+
"""
|
49
|
+
Record all data in telemetry args for the provided run_id.
|
50
|
+
"""
|
51
|
+
|
52
|
+
async def _record(
|
53
|
+
entity_to_record: pd.DataFrame | dict,
|
54
|
+
entity_name: str,
|
55
|
+
**_,
|
56
|
+
) -> None:
|
57
|
+
self._logger.debug(
|
58
|
+
"Recording telemetry for {entity_name} in the run {run_id}",
|
59
|
+
entity_name=entity_name,
|
60
|
+
run_id=run_id,
|
61
|
+
)
|
62
|
+
if not isinstance(entity_to_record, dict) and not isinstance(
|
63
|
+
entity_to_record, pd.DataFrame
|
64
|
+
):
|
65
|
+
self._logger.warning(
|
66
|
+
"Unsupported data type: {telemetry_entity_type}. Telemetry recording skipped.",
|
67
|
+
telemetry_entity_type=type(entity_to_record),
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
self._storage_client.save_data_as_blob(
|
71
|
+
data=entity_to_record,
|
72
|
+
blob_path=DataSocket(
|
73
|
+
alias="telemetry",
|
74
|
+
data_path=f"{self._telemetry_base_path}/{entity_name}/{run_id}",
|
75
|
+
data_format="null",
|
76
|
+
).parse_data_path(),
|
77
|
+
serialization_format=DictJsonSerializationFormat
|
78
|
+
if isinstance(entity_to_record, dict)
|
79
|
+
else DataFrameParquetSerializationFormat,
|
80
|
+
overwrite=True,
|
81
|
+
)
|
82
|
+
|
83
|
+
telemetry_tasks = [
|
84
|
+
asyncio.create_task(
|
85
|
+
partial(
|
86
|
+
_record,
|
87
|
+
entity_to_record=telemetry_value,
|
88
|
+
entity_name=telemetry_key,
|
89
|
+
run_id=run_id,
|
90
|
+
)()
|
91
|
+
)
|
92
|
+
for telemetry_key, telemetry_value in telemetry_args.items()
|
93
|
+
]
|
94
|
+
|
95
|
+
done, pending = await asyncio.wait(telemetry_tasks)
|
96
|
+
if len(pending) > 0:
|
97
|
+
self._logger.warning(
|
98
|
+
"Some telemetry recording operations did not complete within specified time. This run might lack observability coverage."
|
99
|
+
)
|
100
|
+
for done_telemetry_task in done:
|
101
|
+
telemetry_exc = done_telemetry_task.exception()
|
102
|
+
if telemetry_exc:
|
103
|
+
self._logger.warning(
|
104
|
+
"Telemetry recoding failed", exception=telemetry_exc
|
105
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "esd-services-api-client"
|
3
|
-
version = "2.2.
|
3
|
+
version = "2.2.2"
|
4
4
|
description = "Python clients for ESD services"
|
5
5
|
authors = ["ECCO Sneaks & Data <esdsupport@ecco.com>"]
|
6
6
|
maintainers = ['GZU <gzu@ecco.com>', 'JRB <ext-jrb@ecco.com>', 'VISA <visa@ecco.com>']
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = '2.2.0'
|
File without changes
|
File without changes
|
{esd_services_api_client-2.2.0 → esd_services_api_client-2.2.2}/esd_services_api_client/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|