esd-services-api-client 2.0.3__tar.gz → 2.0.5__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.
Files changed (47) hide show
  1. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/PKG-INFO +1 -1
  2. esd_services_api_client-2.0.5/esd_services_api_client/_version.py +1 -0
  3. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/README.md +29 -10
  4. esd_services_api_client-2.0.5/esd_services_api_client/nexus/configurations/__init__.py +0 -0
  5. esd_services_api_client-2.0.5/esd_services_api_client/nexus/configurations/algorithm_configuration.py +37 -0
  6. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/core/app_core.py +16 -0
  7. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/core/app_dependencies.py +17 -0
  8. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/input/__init__.py +1 -0
  9. esd_services_api_client-2.0.5/esd_services_api_client/nexus/input/_functions.py +69 -0
  10. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/input/input_processor.py +3 -38
  11. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/input/input_reader.py +17 -4
  12. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/pyproject.toml +1 -1
  13. esd_services_api_client-2.0.3/esd_services_api_client/_version.py +0 -1
  14. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/LICENSE +0 -0
  15. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/README.md +0 -0
  16. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/__init__.py +0 -0
  17. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/beast/__init__.py +0 -0
  18. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/beast/v3/__init__.py +0 -0
  19. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/beast/v3/_connector.py +0 -0
  20. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/beast/v3/_models.py +0 -0
  21. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/README.md +0 -0
  22. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/__init__.py +0 -0
  23. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/_auth.py +0 -0
  24. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/_base.py +0 -0
  25. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/_connector.py +0 -0
  26. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/boxer/_models.py +0 -0
  27. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/common/__init__.py +0 -0
  28. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/crystal/__init__.py +0 -0
  29. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/crystal/_api_versions.py +0 -0
  30. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/crystal/_connector.py +0 -0
  31. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/crystal/_models.py +0 -0
  32. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/__init__.py +0 -0
  33. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/abstractions/__init__.py +0 -0
  34. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/abstractions/logger_factory.py +0 -0
  35. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/abstractions/nexus_object.py +0 -0
  36. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/abstractions/socket_provider.py +0 -0
  37. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/algorithms/__init__.py +0 -0
  38. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/algorithms/_baseline_algorithm.py +0 -0
  39. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/algorithms/distributed.py +0 -0
  40. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/algorithms/minimalistic.py +0 -0
  41. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/algorithms/recursive.py +0 -0
  42. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/core/__init__.py +0 -0
  43. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/exceptions/__init__.py +0 -0
  44. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/exceptions/_nexus_error.py +0 -0
  45. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/exceptions/input_reader_error.py +0 -0
  46. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/exceptions/startup_error.py +0 -0
  47. {esd_services_api_client-2.0.3 → esd_services_api_client-2.0.5}/esd_services_api_client/nexus/input/payload_reader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esd-services-api-client
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: Python clients for ESD services
5
5
  Home-page: https://github.com/SneaksAndData/esd-services-api-client
6
6
  License: Apache 2.0
@@ -0,0 +1 @@
1
+ __version__ = '2.0.5'
@@ -15,6 +15,7 @@ Example usage:
15
15
  ```python
16
16
  import asyncio
17
17
  import json
18
+ import os
18
19
  import socketserver
19
20
  import threading
20
21
  from dataclasses import dataclass
@@ -31,6 +32,9 @@ from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFact
31
32
  from esd_services_api_client.nexus.abstractions.socket_provider import (
32
33
  ExternalSocketProvider,
33
34
  )
35
+ from esd_services_api_client.nexus.configurations.algorithm_configuration import (
36
+ NexusConfiguration,
37
+ )
34
38
  from esd_services_api_client.nexus.core.app_core import Nexus
35
39
  from esd_services_api_client.nexus.algorithms import MinimalisticAlgorithm
36
40
  from esd_services_api_client.nexus.input import InputReader, InputProcessor
@@ -47,6 +51,16 @@ async def my_on_complete_func_2(**kwargs):
47
51
  pass
48
52
 
49
53
 
54
+ @dataclass
55
+ class MyAlgorithmConfiguration(NexusConfiguration):
56
+ @classmethod
57
+ def from_environment(cls) -> "NexusConfiguration":
58
+ return MyAlgorithmConfiguration.from_json(os.getenv("NEXUS__MY_CONFIGURATION"))
59
+
60
+ c1: str
61
+ c2: str
62
+
63
+
50
64
  @dataclass
51
65
  class MyAlgorithmPayload(AlgorithmPayload, DataClassJsonMixin):
52
66
  x: Optional[list[int]] = None
@@ -130,11 +144,11 @@ class XReader(InputReader[MyAlgorithmPayload]):
130
144
  *readers: "InputReader"
131
145
  ):
132
146
  super().__init__(
133
- socket_provider.socket("x"),
134
- store,
135
- metrics_provider,
136
- logger_factory,
137
- payload,
147
+ socket=socket_provider.socket("x"),
148
+ store=store,
149
+ metrics_provider=metrics_provider,
150
+ logger_factory=logger_factory,
151
+ payload=payload,
138
152
  *readers
139
153
  )
140
154
 
@@ -165,11 +179,11 @@ class YReader(InputReader[MyAlgorithmPayload2]):
165
179
  *readers: "InputReader"
166
180
  ):
167
181
  super().__init__(
168
- socket_provider.socket("y"),
169
- store,
170
- metrics_provider,
171
- logger_factory,
172
- payload,
182
+ socket=socket_provider.socket("y"),
183
+ store=store,
184
+ metrics_provider=metrics_provider,
185
+ logger_factory=logger_factory,
186
+ payload=payload,
173
187
  *readers
174
188
  )
175
189
 
@@ -196,6 +210,7 @@ class MyInputProcessor(InputProcessor):
196
210
  y: YReader,
197
211
  metrics_provider: MetricsProvider,
198
212
  logger_factory: LoggerFactory,
213
+ my_conf: MyAlgorithmConfiguration,
199
214
  ):
200
215
  super().__init__(
201
216
  x,
@@ -205,7 +220,10 @@ class MyInputProcessor(InputProcessor):
205
220
  payload=None,
206
221
  )
207
222
 
223
+ self.conf = my_conf
224
+
208
225
  async def process_input(self, **_) -> Dict[str, PandasDataFrame]:
226
+ self._logger.info("Config: {config}", config=self.conf.to_json())
209
227
  inputs = await self._read_input()
210
228
  return {
211
229
  "x_ready": inputs["x"].assign(c=[-1, 1]),
@@ -250,6 +268,7 @@ async def main():
250
268
  .add_reader(YReader)
251
269
  .use_processor(MyInputProcessor)
252
270
  .use_algorithm(MyAlgorithm)
271
+ .inject_configuration(MyAlgorithmConfiguration)
253
272
  .inject_payload(MyAlgorithmPayload, MyAlgorithmPayload2)
254
273
  )
255
274
 
@@ -0,0 +1,37 @@
1
+ """
2
+ Configuration class primitive for injections.
3
+ """
4
+ from abc import abstractmethod, ABC
5
+ from dataclasses import dataclass
6
+
7
+ from dataclasses_json import DataClassJsonMixin
8
+
9
+
10
+ # Copyright (c) 2023. ECCO Sneaks & Data
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+
25
+
26
+ @dataclass
27
+ class NexusConfiguration(DataClassJsonMixin, ABC):
28
+ """
29
+ Base class for algorithm configurations
30
+ """
31
+
32
+ @classmethod
33
+ @abstractmethod
34
+ def from_environment(cls) -> "NexusConfiguration":
35
+ """
36
+ Instantiates this configuration from the environment variable.
37
+ """
@@ -46,6 +46,9 @@ from esd_services_api_client.crystal import (
46
46
  from esd_services_api_client.nexus.algorithms._baseline_algorithm import (
47
47
  BaselineAlgorithm,
48
48
  )
49
+ from esd_services_api_client.nexus.configurations.algorithm_configuration import (
50
+ NexusConfiguration,
51
+ )
49
52
  from esd_services_api_client.nexus.core.app_dependencies import (
50
53
  ServiceConfigurator,
51
54
  )
@@ -163,6 +166,19 @@ class Nexus:
163
166
  self._configurator = self._configurator.with_payload((await get_payload()))
164
167
  return self
165
168
 
169
+ def inject_configuration(
170
+ self, *configuration_types: Type[NexusConfiguration]
171
+ ) -> "Nexus":
172
+ """
173
+ Adds custom configuration class instances to the DI container.
174
+ """
175
+ for config_type in configuration_types:
176
+ self._configurator = self._configurator.with_configuration(
177
+ config_type.from_environment()
178
+ )
179
+
180
+ return self
181
+
166
182
  async def _submit_result(
167
183
  self,
168
184
  result: Optional[PandasDataFrame] = None,
@@ -32,6 +32,9 @@ from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFact
32
32
  from esd_services_api_client.nexus.abstractions.socket_provider import (
33
33
  ExternalSocketProvider,
34
34
  )
35
+ from esd_services_api_client.nexus.configurations.algorithm_configuration import (
36
+ NexusConfiguration,
37
+ )
35
38
  from esd_services_api_client.nexus.exceptions.startup_error import (
36
39
  FatalStartupConfigurationError,
37
40
  )
@@ -42,6 +45,7 @@ from esd_services_api_client.nexus.input.payload_reader import (
42
45
  )
43
46
 
44
47
 
48
+ @final
45
49
  class MetricsModule(Module):
46
50
  """
47
51
  Metrics provider module.
@@ -63,6 +67,7 @@ class MetricsModule(Module):
63
67
  return metrics_class(**metrics_settings)
64
68
 
65
69
 
70
+ @final
66
71
  class LoggerFactoryModule(Module):
67
72
  """
68
73
  Logger factory module.
@@ -77,6 +82,7 @@ class LoggerFactoryModule(Module):
77
82
  return LoggerFactory()
78
83
 
79
84
 
85
+ @final
80
86
  class CrystalReceiverClientModule(Module):
81
87
  """
82
88
  Crystal receiver module.
@@ -93,6 +99,7 @@ class CrystalReceiverClientModule(Module):
93
99
  )
94
100
 
95
101
 
102
+ @final
96
103
  class QueryEnabledStoreModule(Module):
97
104
  """
98
105
  QES module.
@@ -107,6 +114,7 @@ class QueryEnabledStoreModule(Module):
107
114
  return QueryEnabledStore.from_string(os.getenv("NEXUS__QES_CONNECTION_STRING"))
108
115
 
109
116
 
117
+ @final
110
118
  class StorageClientModule(Module):
111
119
  """
112
120
  Storage client module.
@@ -203,3 +211,12 @@ class ServiceConfigurator:
203
211
  lambda binder: binder.bind(payload.__class__, to=payload, scope=singleton)
204
212
  )
205
213
  return self
214
+
215
+ def with_configuration(self, config: NexusConfiguration) -> "ServiceConfigurator":
216
+ """
217
+ Adds the specified payload instance to the DI container.
218
+ """
219
+ self._injection_binds.append(
220
+ lambda binder: binder.bind(config.__class__, to=config, scope=singleton)
221
+ )
222
+ return self
@@ -20,3 +20,4 @@
20
20
 
21
21
  from esd_services_api_client.nexus.input.input_processor import *
22
22
  from esd_services_api_client.nexus.input.input_reader import *
23
+ from esd_services_api_client.nexus.input._functions import *
@@ -0,0 +1,69 @@
1
+ """
2
+ Utility functions to handle input processing.
3
+ """
4
+
5
+ # Copyright (c) 2023. 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 Dict, Union, Type
22
+ import azure.core.exceptions
23
+ import deltalake
24
+ from pandas import DataFrame as PandasDataFrame
25
+
26
+ from esd_services_api_client.nexus.exceptions.input_reader_error import (
27
+ FatalInputReaderError,
28
+ TransientInputReaderError,
29
+ )
30
+ from esd_services_api_client.nexus.input.input_reader import InputReader
31
+
32
+
33
+ def resolve_reader_exc_type(
34
+ ex: BaseException,
35
+ ) -> Union[Type[FatalInputReaderError], Type[TransientInputReaderError]]:
36
+ """
37
+ Resolve base exception into a specific Nexus exception.
38
+ """
39
+ match type(ex):
40
+ case azure.core.exceptions.HttpResponseError, deltalake.PyDeltaTableError:
41
+ return TransientInputReaderError
42
+ case azure.core.exceptions.AzureError, azure.core.exceptions.ClientAuthenticationError:
43
+ return FatalInputReaderError
44
+ case _:
45
+ return FatalInputReaderError
46
+
47
+
48
+ async def resolve_readers(*readers: InputReader) -> Dict[str, PandasDataFrame]:
49
+ """
50
+ Concurrently resolve `data` property of all readers by invoking their `read` method.
51
+ """
52
+
53
+ def get_result(alias: str, completed_task: asyncio.Task) -> PandasDataFrame:
54
+ reader_exc = completed_task.exception()
55
+ if reader_exc:
56
+ raise resolve_reader_exc_type(reader_exc)(alias, reader_exc)
57
+
58
+ return completed_task.result()
59
+
60
+ async def _read(input_reader: InputReader):
61
+ async with input_reader as instance:
62
+ return await instance.read()
63
+
64
+ read_tasks: dict[str, asyncio.Task] = {
65
+ reader.socket.alias: asyncio.create_task(_read(reader)) for reader in readers
66
+ }
67
+ await asyncio.wait(fs=read_tasks.values())
68
+
69
+ return {alias: get_result(alias, task) for alias, task in read_tasks.items()}
@@ -17,15 +17,11 @@
17
17
  # limitations under the License.
18
18
  #
19
19
 
20
- import asyncio
21
20
  from abc import abstractmethod
22
- from typing import Dict, Union, Type
21
+ from typing import Dict
23
22
 
24
- import deltalake
25
23
  from adapta.metrics import MetricsProvider
26
24
 
27
- import azure.core.exceptions
28
-
29
25
  from pandas import DataFrame as PandasDataFrame
30
26
 
31
27
  from esd_services_api_client.nexus.abstractions.nexus_object import (
@@ -33,10 +29,7 @@ from esd_services_api_client.nexus.abstractions.nexus_object import (
33
29
  TPayload,
34
30
  )
35
31
  from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
36
- from esd_services_api_client.nexus.exceptions.input_reader_error import (
37
- FatalInputReaderError,
38
- TransientInputReaderError,
39
- )
32
+ from esd_services_api_client.nexus.input._functions import resolve_readers
40
33
  from esd_services_api_client.nexus.input.input_reader import InputReader
41
34
 
42
35
 
@@ -56,36 +49,8 @@ class InputProcessor(NexusObject[TPayload]):
56
49
  self._readers = readers
57
50
  self._payload = payload
58
51
 
59
- def _get_exc_type(
60
- self, ex: BaseException
61
- ) -> Union[Type[FatalInputReaderError], Type[TransientInputReaderError]]:
62
- match type(ex):
63
- case azure.core.exceptions.HttpResponseError, deltalake.PyDeltaTableError:
64
- return TransientInputReaderError
65
- case azure.core.exceptions.AzureError, azure.core.exceptions.ClientAuthenticationError:
66
- return FatalInputReaderError
67
- case _:
68
- return FatalInputReaderError
69
-
70
52
  async def _read_input(self) -> Dict[str, PandasDataFrame]:
71
- def get_result(alias: str, completed_task: asyncio.Task) -> PandasDataFrame:
72
- reader_exc = completed_task.exception()
73
- if reader_exc:
74
- raise self._get_exc_type(reader_exc)(alias, reader_exc)
75
-
76
- return completed_task.result()
77
-
78
- async def _read(input_reader: InputReader):
79
- async with input_reader as instance:
80
- return await instance.read()
81
-
82
- read_tasks: dict[str, asyncio.Task] = {
83
- reader.socket.alias: asyncio.create_task(_read(reader))
84
- for reader in self._readers
85
- }
86
- await asyncio.wait(fs=read_tasks.values())
87
-
88
- return {alias: get_result(alias, task) for alias, task in read_tasks.items()}
53
+ return await resolve_readers(*self._readers)
89
54
 
90
55
  @abstractmethod
91
56
  async def process_input(self, **kwargs) -> Dict[str, PandasDataFrame]:
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Input reader.
3
3
  """
4
+ import functools
4
5
 
5
6
  # Copyright (c) 2023. ECCO Sneaks & Data
6
7
  #
@@ -43,12 +44,12 @@ class InputReader(NexusObject[TPayload]):
43
44
 
44
45
  def __init__(
45
46
  self,
46
- socket: DataSocket,
47
47
  store: QueryEnabledStore,
48
48
  metrics_provider: MetricsProvider,
49
49
  logger_factory: LoggerFactory,
50
50
  payload: TPayload,
51
- *readers: "InputReader"
51
+ *readers: "InputReader",
52
+ socket: Optional[DataSocket] = None,
52
53
  ):
53
54
  super().__init__(metrics_provider, logger_factory)
54
55
  self.socket = socket
@@ -58,6 +59,16 @@ class InputReader(NexusObject[TPayload]):
58
59
  self._payload = payload
59
60
 
60
61
  @property
62
+ def alias(self) -> str:
63
+ """
64
+ Alias to identify this reader's output
65
+ """
66
+ if self.socket:
67
+ return self.socket.alias
68
+
69
+ return self._metric_name
70
+
71
+ @functools.cached_property
61
72
  def data(self) -> Optional[PandasDataFrame]:
62
73
  """
63
74
  Data read by this reader.
@@ -92,8 +103,10 @@ class InputReader(NexusObject[TPayload]):
92
103
  on_finish_message_template="Finished reading {entity} from path {data_path} in {elapsed:.2f}s seconds",
93
104
  template_args={
94
105
  "entity": self._metric_name.upper(),
95
- "data_path": self.socket.data_path,
96
- },
106
+ }
107
+ | {"data_path": self.socket.data_path}
108
+ if self.socket
109
+ else {},
97
110
  )
98
111
  async def _read(**_) -> PandasDataFrame:
99
112
  if not self._data:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "esd-services-api-client"
3
- version = "2.0.3"
3
+ version = "2.0.5"
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.0.3'