esd-services-api-client 2.0.0__py3-none-any.whl → 2.0.2__py3-none-any.whl

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 (29) hide show
  1. esd_services_api_client/_version.py +1 -1
  2. esd_services_api_client/crystal/_connector.py +7 -7
  3. esd_services_api_client/nexus/README.md +280 -0
  4. esd_services_api_client/nexus/__init__.py +18 -0
  5. esd_services_api_client/nexus/abstractions/__init__.py +18 -0
  6. esd_services_api_client/nexus/abstractions/logger_factory.py +64 -0
  7. esd_services_api_client/nexus/abstractions/nexus_object.py +64 -0
  8. esd_services_api_client/nexus/abstractions/socket_provider.py +51 -0
  9. esd_services_api_client/nexus/algorithms/__init__.py +22 -0
  10. esd_services_api_client/nexus/algorithms/_baseline_algorithm.py +56 -0
  11. esd_services_api_client/nexus/algorithms/distributed.py +53 -0
  12. esd_services_api_client/nexus/algorithms/minimalistic.py +44 -0
  13. esd_services_api_client/nexus/algorithms/recursive.py +58 -0
  14. esd_services_api_client/nexus/core/__init__.py +18 -0
  15. esd_services_api_client/nexus/core/app_core.py +259 -0
  16. esd_services_api_client/nexus/core/app_dependencies.py +205 -0
  17. esd_services_api_client/nexus/exceptions/__init__.py +20 -0
  18. esd_services_api_client/nexus/exceptions/_nexus_error.py +30 -0
  19. esd_services_api_client/nexus/exceptions/input_reader_error.py +51 -0
  20. esd_services_api_client/nexus/exceptions/startup_error.py +48 -0
  21. esd_services_api_client/nexus/input/__init__.py +22 -0
  22. esd_services_api_client/nexus/input/input_processor.py +94 -0
  23. esd_services_api_client/nexus/input/input_reader.py +109 -0
  24. esd_services_api_client/nexus/input/payload_reader.py +83 -0
  25. {esd_services_api_client-2.0.0.dist-info → esd_services_api_client-2.0.2.dist-info}/METADATA +5 -2
  26. esd_services_api_client-2.0.2.dist-info/RECORD +43 -0
  27. esd_services_api_client-2.0.0.dist-info/RECORD +0 -21
  28. {esd_services_api_client-2.0.0.dist-info → esd_services_api_client-2.0.2.dist-info}/LICENSE +0 -0
  29. {esd_services_api_client-2.0.0.dist-info → esd_services_api_client-2.0.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,44 @@
1
+ """
2
+ Simple algorithm with a single train/predict/solve iteration.
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
+ from abc import ABC
21
+
22
+ from adapta.metrics import MetricsProvider
23
+ from injector import inject
24
+
25
+ from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
26
+ from esd_services_api_client.nexus.algorithms._baseline_algorithm import (
27
+ BaselineAlgorithm,
28
+ )
29
+ from esd_services_api_client.nexus.input import InputProcessor
30
+
31
+
32
+ class MinimalisticAlgorithm(BaselineAlgorithm, ABC):
33
+ """
34
+ Simple algorithm base class.
35
+ """
36
+
37
+ @inject
38
+ def __init__(
39
+ self,
40
+ input_processor: InputProcessor,
41
+ metrics_provider: MetricsProvider,
42
+ logger_factory: LoggerFactory,
43
+ ):
44
+ super().__init__(input_processor, metrics_provider, logger_factory)
@@ -0,0 +1,58 @@
1
+ """
2
+ Algorithm that uses its output to alter the input for the next iteration, until a certain condition is met.
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
+
21
+ from abc import abstractmethod
22
+
23
+ from adapta.metrics import MetricsProvider
24
+ from injector import inject
25
+ from pandas import DataFrame as PandasDataFrame
26
+
27
+ from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
28
+ from esd_services_api_client.nexus.algorithms._baseline_algorithm import (
29
+ BaselineAlgorithm,
30
+ )
31
+ from esd_services_api_client.nexus.input import InputProcessor
32
+
33
+
34
+ class RecursiveAlgorithm(BaselineAlgorithm):
35
+ """
36
+ Recursive algorithm base class.
37
+ """
38
+
39
+ @inject
40
+ def __init__(
41
+ self,
42
+ input_processor: InputProcessor,
43
+ metrics_provider: MetricsProvider,
44
+ logger_factory: LoggerFactory,
45
+ ):
46
+ super().__init__(input_processor, metrics_provider, logger_factory)
47
+
48
+ @abstractmethod
49
+ async def _is_finished(self, **kwargs) -> bool:
50
+ """ """
51
+
52
+ async def run(self, **kwargs) -> PandasDataFrame:
53
+ result = await self._run(
54
+ **(await self._input_processor.process_input(**kwargs))
55
+ )
56
+ if self._is_finished(**result):
57
+ return result
58
+ return await self.run(**result)
@@ -0,0 +1,18 @@
1
+ """
2
+ Import index.
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
+ #
@@ -0,0 +1,259 @@
1
+ """
2
+ Nexus Core.
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
+ import os
22
+ import platform
23
+ import signal
24
+ import sys
25
+ import traceback
26
+ from typing import final, Type, Optional, Coroutine
27
+
28
+ import backoff
29
+ import urllib3.exceptions
30
+ import azure.core.exceptions
31
+ from adapta.process_communication import DataSocket
32
+ from adapta.storage.blob.base import StorageClient
33
+ from adapta.storage.models.format import DataFrameJsonSerializationFormat
34
+ from injector import Injector
35
+
36
+ from pandas import DataFrame as PandasDataFrame
37
+
38
+ import esd_services_api_client.nexus.exceptions
39
+ from esd_services_api_client.crystal import (
40
+ add_crystal_args,
41
+ extract_crystal_args,
42
+ CrystalConnector,
43
+ AlgorithmRunResult,
44
+ CrystalEntrypointArguments,
45
+ )
46
+ from esd_services_api_client.nexus.algorithms._baseline_algorithm import (
47
+ BaselineAlgorithm,
48
+ )
49
+ from esd_services_api_client.nexus.core.app_dependencies import (
50
+ ServiceConfigurator,
51
+ )
52
+ from esd_services_api_client.nexus.input.input_processor import InputProcessor
53
+ from esd_services_api_client.nexus.input.input_reader import InputReader
54
+ from esd_services_api_client.nexus.input.payload_reader import (
55
+ AlgorithmPayloadReader,
56
+ AlgorithmPayload,
57
+ )
58
+
59
+
60
+ def is_transient_exception(exception: Optional[BaseException]) -> Optional[bool]:
61
+ """
62
+ Check if the exception is retryable.
63
+ """
64
+ if not exception:
65
+ return None
66
+ match type(exception):
67
+ case esd_services_api_client.nexus.exceptions.FatalNexusError:
68
+ return False
69
+ case esd_services_api_client.nexus.exceptions.TransientNexusError:
70
+ return True
71
+ case _:
72
+ return False
73
+
74
+
75
+ async def graceful_shutdown():
76
+ """
77
+ Gracefully stops the event loop.
78
+ """
79
+ for task in asyncio.all_tasks():
80
+ if task is not asyncio.current_task():
81
+ task.cancel()
82
+
83
+ asyncio.get_event_loop().stop()
84
+
85
+
86
+ def attach_signal_handlers():
87
+ """
88
+ Signal handlers for the event loop graceful shutdown.
89
+ """
90
+ if platform.system() != "Windows":
91
+ asyncio.get_event_loop().add_signal_handler(
92
+ signal.SIGTERM, lambda: asyncio.create_task(graceful_shutdown())
93
+ )
94
+
95
+
96
+ @final
97
+ class Nexus:
98
+ """
99
+ Nexus is the object that manages everything related to running algorithms through Crystal.
100
+ It takes care of result submission, signal handling, result recording, post-processing, metrics, logging etc.
101
+ """
102
+
103
+ def __init__(self, args: CrystalEntrypointArguments):
104
+ self._configurator = ServiceConfigurator()
105
+ self._injector: Optional[Injector] = None
106
+ self._algorithm_class: Optional[Type[BaselineAlgorithm]] = None
107
+ self._run_args = args
108
+ self._algorithm_run_task: Optional[asyncio.Task] = None
109
+ self._on_complete_tasks: list[Coroutine] = []
110
+
111
+ attach_signal_handlers()
112
+
113
+ @property
114
+ def algorithm_class(self) -> Type[BaselineAlgorithm]:
115
+ """
116
+ Class of the algorithm used by this Nexus instance.
117
+ """
118
+ return self._algorithm_class
119
+
120
+ def on_complete(self, coro: Coroutine) -> "Nexus":
121
+ """
122
+ Attaches a coroutine to run on algorithm completion.
123
+ """
124
+ self._on_complete_tasks.append(coro)
125
+ return self
126
+
127
+ def add_reader(self, reader: Type[InputReader]) -> "Nexus":
128
+ """
129
+ Adds an input data reader for the algorithm.
130
+ """
131
+ self._configurator = self._configurator.with_input_reader(reader)
132
+ return self
133
+
134
+ def use_processor(self, input_processor: Type[InputProcessor]) -> "Nexus":
135
+ """
136
+ Initialises an input processor for the algorithm.
137
+ """
138
+ self._configurator = self._configurator.with_input_processor(input_processor)
139
+ return self
140
+
141
+ def use_algorithm(self, algorithm: Type[BaselineAlgorithm]) -> "Nexus":
142
+ """
143
+ Algorithm to use for this Nexus instance
144
+ """
145
+ self._algorithm_class = algorithm
146
+ return self
147
+
148
+ async def inject_payload(self, *payload_types: Type[AlgorithmPayload]) -> "Nexus":
149
+ """
150
+ Adds payloads processed into the specified types to the DI container
151
+ """
152
+ for payload_type in payload_types:
153
+
154
+ async def get_payload() -> payload_type: # pylint: disable=W0640
155
+ async with AlgorithmPayloadReader(
156
+ payload_uri=self._run_args.sas_uri,
157
+ payload_type=payload_type, # pylint: disable=W0640
158
+ ) as reader:
159
+ return reader.payload
160
+
161
+ # pylint warnings are silenced here because closure is called inside the same loop it is defined, thus each value of a loop variable is used
162
+
163
+ self._configurator = self._configurator.with_payload((await get_payload()))
164
+ return self
165
+
166
+ async def _submit_result(
167
+ self,
168
+ result: Optional[PandasDataFrame] = None,
169
+ ex: Optional[BaseException] = None,
170
+ ) -> None:
171
+ @backoff.on_exception(
172
+ wait_gen=backoff.expo,
173
+ exception=(
174
+ azure.core.exceptions.HttpResponseError,
175
+ urllib3.exceptions.HTTPError,
176
+ ),
177
+ max_time=10,
178
+ raise_on_giveup=True,
179
+ )
180
+ def save_result(data: PandasDataFrame) -> str:
181
+ """
182
+ Saves blob and returns the uri
183
+
184
+ :param: path: path to save the blob
185
+ :param: output_consumer_df: Formatted dataframe into ECCO format
186
+ :param: storage_client: Azure storage client
187
+
188
+ :return: blob uri
189
+ """
190
+ storage_client = self._injector.get(StorageClient)
191
+ output_path = f"{os.getenv('NEXUS__ALGORITHM_OUTPUT_PATH')}/{self._run_args.request_id}.json"
192
+ blob_path = DataSocket(
193
+ data_path=output_path, alias="output", data_format="null"
194
+ ).parse_data_path()
195
+ storage_client.save_data_as_blob(
196
+ data=data,
197
+ blob_path=blob_path,
198
+ serialization_format=DataFrameJsonSerializationFormat,
199
+ overwrite=True,
200
+ )
201
+ return storage_client.get_blob_uri(blob_path=blob_path)
202
+
203
+ receiver = self._injector.get(CrystalConnector)
204
+
205
+ match is_transient_exception(ex):
206
+ case None:
207
+ receiver.submit_result(
208
+ result=AlgorithmRunResult(sas_uri=save_result(result)),
209
+ run_id=self._run_args.request_id,
210
+ algorithm=os.getenv("CRYSTAL__ALGORITHM_NAME"),
211
+ debug=os.getenv("IS_LOCAL_RUN") == "1",
212
+ )
213
+ case True:
214
+ sys.exit(1)
215
+ case False:
216
+ receiver.submit_result(
217
+ result=AlgorithmRunResult(
218
+ message=f"{type(ex)}: {ex})", cause=traceback.format_exc()
219
+ ),
220
+ run_id=self._run_args.request_id,
221
+ algorithm=os.getenv("CRYSTAL__ALGORITHM_NAME"),
222
+ debug=os.getenv("IS_LOCAL_RUN") == "1",
223
+ )
224
+ case _:
225
+ sys.exit(1)
226
+
227
+ async def activate(self):
228
+ """
229
+ Activates the run sequence.
230
+ """
231
+ self._injector = Injector(self._configurator.injection_binds)
232
+
233
+ algorithm: BaselineAlgorithm = self._injector.get(self._algorithm_class)
234
+
235
+ async with algorithm as instance:
236
+ self._algorithm_run_task = asyncio.create_task(
237
+ instance.run(**self._run_args.__dict__)
238
+ )
239
+ await self._algorithm_run_task
240
+ ex = self._algorithm_run_task.exception()
241
+ on_complete_tasks = [
242
+ asyncio.create_task(on_complete_task)
243
+ for on_complete_task in self._on_complete_tasks
244
+ ]
245
+
246
+ await self._submit_result(
247
+ self._algorithm_run_task.result() if not ex else None,
248
+ self._algorithm_run_task.exception(),
249
+ )
250
+ if len(on_complete_tasks) > 0:
251
+ await asyncio.wait(on_complete_tasks)
252
+
253
+ @classmethod
254
+ def create(cls) -> "Nexus":
255
+ """
256
+ Creates a Nexus instance with Crystal CLI arguments parsed into input.
257
+ """
258
+ parser = add_crystal_args()
259
+ return Nexus(extract_crystal_args(parser.parse_args()))
@@ -0,0 +1,205 @@
1
+ """
2
+ Dependency injections.
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 json
21
+ import os
22
+ from pydoc import locate
23
+ from typing import final, Type
24
+
25
+ from adapta.metrics import MetricsProvider
26
+ from adapta.storage.blob.base import StorageClient
27
+ from adapta.storage.query_enabled_store import QueryEnabledStore
28
+ from injector import Module, singleton, provider
29
+
30
+ from esd_services_api_client.crystal import CrystalConnector
31
+ from esd_services_api_client.nexus.abstractions.logger_factory import LoggerFactory
32
+ from esd_services_api_client.nexus.abstractions.socket_provider import (
33
+ ExternalSocketProvider,
34
+ )
35
+ from esd_services_api_client.nexus.exceptions.startup_error import (
36
+ FatalStartupConfigurationError,
37
+ )
38
+ from esd_services_api_client.nexus.input.input_processor import InputProcessor
39
+ from esd_services_api_client.nexus.input.input_reader import InputReader
40
+ from esd_services_api_client.nexus.input.payload_reader import (
41
+ AlgorithmPayload,
42
+ )
43
+
44
+
45
+ class MetricsModule(Module):
46
+ """
47
+ Metrics provider module.
48
+ """
49
+
50
+ @singleton
51
+ @provider
52
+ def provide(self) -> MetricsProvider:
53
+ """
54
+ DI factory method.
55
+ """
56
+ metrics_class: Type[MetricsProvider] = locate(
57
+ os.getenv(
58
+ "NEXUS__METRIC_PROVIDER_CLASS",
59
+ "adapta.metrics.providers.datadog_provider.DatadogMetricsProvider",
60
+ )
61
+ )
62
+ metrics_settings = json.loads(os.getenv("NEXUS__METRIC_PROVIDER_CONFIGURATION"))
63
+ return metrics_class(**metrics_settings)
64
+
65
+
66
+ class LoggerFactoryModule(Module):
67
+ """
68
+ Logger factory module.
69
+ """
70
+
71
+ @singleton
72
+ @provider
73
+ def provide(self) -> LoggerFactory:
74
+ """
75
+ DI factory method.
76
+ """
77
+ return LoggerFactory()
78
+
79
+
80
+ class CrystalReceiverClientModule(Module):
81
+ """
82
+ Crystal receiver module.
83
+ """
84
+
85
+ @singleton
86
+ @provider
87
+ def provide(self) -> CrystalConnector:
88
+ """
89
+ DI factory method.
90
+ """
91
+ return CrystalConnector.create_anonymous(
92
+ receiver_base_url=os.getenv("NEXUS__ALGORITHM_METRIC_NAMESPACE"),
93
+ )
94
+
95
+
96
+ class QueryEnabledStoreModule(Module):
97
+ """
98
+ QES module.
99
+ """
100
+
101
+ @singleton
102
+ @provider
103
+ def provide(self) -> QueryEnabledStore:
104
+ """
105
+ DI factory method.
106
+ """
107
+ return QueryEnabledStore.from_string(os.getenv("NEXUS__QES_CONNECTION_STRING"))
108
+
109
+
110
+ class StorageClientModule(Module):
111
+ """
112
+ Storage client module.
113
+ """
114
+
115
+ @singleton
116
+ @provider
117
+ def provide(self) -> StorageClient:
118
+ """
119
+ DI factory method.
120
+ """
121
+ storage_client_class: Type[StorageClient] = locate(
122
+ os.getenv(
123
+ "NEXUS__STORAGE_CLIENT_CLASS",
124
+ )
125
+ )
126
+ if not storage_client_class:
127
+ raise FatalStartupConfigurationError("NEXUS__STORAGE_CLIENT_CLASS")
128
+ if "NEXUS__ALGORITHM_OUTPUT_PATH" not in os.environ:
129
+ raise FatalStartupConfigurationError("NEXUS__ALGORITHM_OUTPUT_PATH")
130
+
131
+ return storage_client_class.for_storage_path(
132
+ path=os.getenv("NEXUS__ALGORITHM_OUTPUT_PATH")
133
+ )
134
+
135
+
136
+ @final
137
+ class ExternalSocketsModule(Module):
138
+ """
139
+ Storage client module.
140
+ """
141
+
142
+ @singleton
143
+ @provider
144
+ def provide(self) -> ExternalSocketProvider:
145
+ """
146
+ Dependency provider.
147
+ """
148
+ if "NEXUS__ALGORITHM_INPUT_EXTERNAL_DATA_SOCKETS" not in os.environ:
149
+ raise FatalStartupConfigurationError(
150
+ "NEXUS__ALGORITHM_INPUT_EXTERNAL_DATA_SOCKETS"
151
+ )
152
+
153
+ return ExternalSocketProvider.from_serialized(
154
+ os.getenv("NEXUS__ALGORITHM_INPUT_EXTERNAL_DATA_SOCKETS")
155
+ )
156
+
157
+
158
+ @final
159
+ class ServiceConfigurator:
160
+ """
161
+ Runtime DI support.
162
+ """
163
+
164
+ def __init__(self):
165
+ self._injection_binds = [
166
+ MetricsModule(),
167
+ CrystalReceiverClientModule(),
168
+ QueryEnabledStoreModule(),
169
+ StorageClientModule(),
170
+ ExternalSocketsModule(),
171
+ ]
172
+
173
+ @property
174
+ def injection_binds(self) -> list:
175
+ """
176
+ Currently configured injection bindings
177
+ """
178
+ return self._injection_binds
179
+
180
+ def with_input_reader(self, reader: Type[InputReader]) -> "ServiceConfigurator":
181
+ """
182
+ Adds the input reader implementation to the DI.
183
+ """
184
+ self._injection_binds.append(type(f"{reader.__name__}Module", (Module,), {})())
185
+ return self
186
+
187
+ def with_input_processor(
188
+ self, input_processor: Type[InputProcessor]
189
+ ) -> "ServiceConfigurator":
190
+ """
191
+ Adds the input processor implementation
192
+ """
193
+ self._injection_binds.append(
194
+ type(f"{input_processor.__name__}Module", (Module,), {})()
195
+ )
196
+ return self
197
+
198
+ def with_payload(self, payload: AlgorithmPayload) -> "ServiceConfigurator":
199
+ """
200
+ Adds the specified payload instance to the DI container.
201
+ """
202
+ self._injection_binds.append(
203
+ lambda binder: binder.bind(payload.__class__, to=payload, scope=singleton)
204
+ )
205
+ return self
@@ -0,0 +1,20 @@
1
+ """
2
+ Import index.
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
+ from esd_services_api_client.nexus.exceptions._nexus_error import *
@@ -0,0 +1,30 @@
1
+ """
2
+ Base classes for custom exceptions.
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
+
21
+ class FatalNexusError(BaseException):
22
+ """
23
+ Base exception class for all non-retryable application errors.
24
+ """
25
+
26
+
27
+ class TransientNexusError(BaseException):
28
+ """
29
+ Base exception class for all retryable application errors.
30
+ """
@@ -0,0 +1,51 @@
1
+ """
2
+ Input reader exceptions.
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
+ from esd_services_api_client.nexus.exceptions._nexus_error import (
21
+ FatalNexusError,
22
+ TransientNexusError,
23
+ )
24
+
25
+
26
+ class FatalInputReaderError(FatalNexusError):
27
+ """
28
+ Input reader exception that shuts down the Nexus.
29
+ """
30
+
31
+ def __init__(self, failed_reader: str, underlying: BaseException):
32
+ super().__init__()
33
+ self.with_traceback(underlying.__traceback__)
34
+ self._failed_reader = failed_reader
35
+
36
+ def __str__(self) -> str:
37
+ return f"Reader for alias '{self._failed_reader}' failed to fetch the data and this operation cannot be retried. Review traceback for more information"
38
+
39
+
40
+ class TransientInputReaderError(TransientNexusError):
41
+ """
42
+ Input reader exception that will initiate a retry in Crystal.
43
+ """
44
+
45
+ def __init__(self, failed_reader: str, underlying: BaseException):
46
+ super().__init__()
47
+ self.with_traceback(underlying.__traceback__)
48
+ self._failed_reader = failed_reader
49
+
50
+ def __str__(self) -> str:
51
+ return f"Reader for alias '{self._failed_reader}' failed to fetch the data. This error can be resolved by retrying the operation"