esd-services-api-client 2.6.1a149.dev18__py3-none-any.whl → 2.6.2a155.dev2__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.
@@ -1 +1 @@
1
- __version__ = 'v2.6.1a149.dev18'
1
+ __version__ = 'v2.6.2a155.dev2'
@@ -302,6 +302,10 @@ async def main():
302
302
  "(value of y:{y})": {"y": payload.y},
303
303
  "(request_id:{request_id})": {"request_id": run_args.request_id}
304
304
  }
305
+ def tag_metrics(payload: MyAlgorithmPayload2, run_args: CrystalEntrypointArguments) -> dict[str, str]:
306
+ return {
307
+ "country": payload.y,
308
+ }
305
309
  with ThreadingHTTPServer(("localhost", 9876), MockRequestHandler) as server:
306
310
  server_thread = threading.Thread(target=server.serve_forever)
307
311
  server_thread.daemon = True
@@ -317,6 +321,7 @@ async def main():
317
321
  .inject_configuration(MyAlgorithmConfiguration)
318
322
  .inject_payload(MyAlgorithmPayload, MyAlgorithmPayload2)
319
323
  .with_log_enricher(tags_from_payload, enrich_from_payload)
324
+ .with_metric_tagger(tag_metrics)
320
325
  )
321
326
 
322
327
  await nexus.activate()
@@ -36,8 +36,6 @@ class BootstrapLogger(LoggerInterface, ABC):
36
36
  Dummy class to separate bootstrap logging from core app loggers
37
37
  """
38
38
 
39
- pass
40
-
41
39
 
42
40
  @final
43
41
  class BootstrapLoggerFactory:
@@ -0,0 +1,70 @@
1
+ """
2
+ Metrics provider factory.
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 json
21
+ import os
22
+ from pydoc import locate
23
+ from typing import final
24
+
25
+ from adapta.metrics import MetricsProvider
26
+ from adapta.metrics.providers.datadog_provider import DatadogMetricsProvider
27
+
28
+
29
+ @final
30
+ class MetricsProviderFactory:
31
+ """
32
+ Async logger provisioner.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ global_tags: dict[str, str] | None = None,
38
+ ):
39
+ self._global_tags = global_tags
40
+ self._metrics_class: type[MetricsProvider] = locate(
41
+ os.getenv(
42
+ "NEXUS__METRICS_PROVIDER_CLASS",
43
+ "adapta.metrics.providers.datadog_provider.DatadogMetricsProvider",
44
+ )
45
+ )
46
+ self._metrics_settings: dict = json.loads(
47
+ os.getenv("NEXUS__METRICS_PROVIDER_CONFIGURATION")
48
+ )
49
+
50
+ def create_provider(
51
+ self,
52
+ ) -> MetricsProvider:
53
+ """
54
+ Creates a metrics provider enriched with additional tags for each metric emitted by this algorithm.
55
+ In case of DatadogMetricsProvider, takes care of UDP/UDS specific initialization.
56
+ """
57
+ self._metrics_settings["fixed_tags"] = (
58
+ self._metrics_settings.get("fixed_tags", {}) | self._global_tags
59
+ )
60
+
61
+ if type(self._metrics_class) is DatadogMetricsProvider:
62
+ assert isinstance(self._metrics_class, DatadogMetricsProvider)
63
+
64
+ if self._metrics_settings["protocol"] == "udp":
65
+ return self._metrics_class.udp(**self._metrics_settings)
66
+
67
+ if self._metrics_settings["protocol"] == "uds":
68
+ return self._metrics_class.uds(**self._metrics_settings)
69
+
70
+ return self._metrics_class(**self._metrics_settings)
@@ -45,6 +45,9 @@ from esd_services_api_client.nexus.abstractions.logger_factory import (
45
45
  LoggerFactory,
46
46
  BootstrapLoggerFactory,
47
47
  )
48
+ from esd_services_api_client.nexus.abstractions.metrics_provider_factory import (
49
+ MetricsProviderFactory,
50
+ )
48
51
  from esd_services_api_client.nexus.abstractions.nexus_object import AlgorithmResult
49
52
  from esd_services_api_client.nexus.algorithms import (
50
53
  BaselineAlgorithm,
@@ -138,6 +141,14 @@ class Nexus:
138
141
  ] | None = None
139
142
  self._log_enrichment_delimiter: str = ", "
140
143
 
144
+ self._metric_tagger: Callable[
145
+ [
146
+ AlgorithmPayload,
147
+ CrystalEntrypointArguments,
148
+ ],
149
+ dict[str, str],
150
+ ] | None = None
151
+
141
152
  attach_signal_handlers()
142
153
 
143
154
  @property
@@ -215,11 +226,33 @@ class Nexus:
215
226
  | None = None,
216
227
  delimiter: str = ", ",
217
228
  ) -> "Nexus":
229
+ """
230
+ Adds a log `tagger` and a log `enricher` to be used with injected logger.
231
+ A log `tagger` will add key-value tags to each emitted log message, and those tags can be inferred from the payload and entrypoint arguments.
232
+ A log `enricher` will add additional static templated content to log messages, and render those templates using payload properties entrypoint argyments.
233
+ """
218
234
  self._log_tagger = tagger
219
235
  self._log_enricher = enricher
220
236
  self._log_enrichment_delimiter = delimiter
221
237
  return self
222
238
 
239
+ def with_metric_tagger(
240
+ self,
241
+ tagger: Callable[
242
+ [
243
+ AlgorithmPayload,
244
+ CrystalEntrypointArguments,
245
+ ],
246
+ dict[str, str],
247
+ ]
248
+ | None = None,
249
+ ) -> "Nexus":
250
+ """
251
+ Adds a metric `enricher` to be used with injected metrics provider to assign additional tags to emitted metrics.
252
+ """
253
+ self._metric_tagger = tagger
254
+ return self
255
+
223
256
  def with_module(self, module: Type[Module]) -> "Nexus":
224
257
  """
225
258
  Adds a (custom) DI module into the DI container.
@@ -312,28 +345,59 @@ class Nexus:
312
345
 
313
346
  bootstrap_logger.start()
314
347
 
315
- for payload_type in self._payload_types:
316
- try:
348
+ try:
349
+ logger_fixed_template = {}
350
+ logger_tags = {}
351
+ metric_tags = {}
352
+
353
+ for payload_type in self._payload_types:
317
354
  payload = await self._get_payload(payload_type=payload_type)
318
355
  self._injector.binder.bind(
319
356
  payload.__class__, to=payload, scope=singleton
320
357
  )
321
- logger_factory = LoggerFactory(
322
- fixed_template=None
323
- if not self._log_enricher
324
- else self._log_enricher(payload, self._run_args),
325
- fixed_template_delimiter=self._log_enrichment_delimiter,
326
- global_tags=self._log_tagger(payload, self._run_args),
358
+ logger_fixed_template |= (
359
+ self._log_enricher(payload, self._run_args)
360
+ if self._log_enricher
361
+ else {}
327
362
  )
328
- # bind app-level LoggerFactory now
329
- self._injector.binder.bind(
330
- logger_factory.__class__,
331
- to=logger_factory,
332
- scope=singleton,
363
+ logger_tags |= (
364
+ self._log_tagger(payload, self._run_args)
365
+ if self._log_tagger
366
+ else {}
333
367
  )
334
- except BaseException as ex: # pylint: disable=broad-except
335
- bootstrap_logger.error("Error reading algorithm payload", ex)
336
- sys.exit(1)
368
+ metric_tags |= (
369
+ self._metric_tagger(payload, self._run_args)
370
+ if self._metric_tagger
371
+ else {}
372
+ )
373
+
374
+ logger_factory = LoggerFactory(
375
+ fixed_template=logger_fixed_template,
376
+ fixed_template_delimiter=self._log_enrichment_delimiter,
377
+ global_tags=logger_tags,
378
+ )
379
+ # bind app-level LoggerFactory now
380
+ self._injector.binder.bind(
381
+ logger_factory.__class__,
382
+ to=logger_factory,
383
+ scope=singleton,
384
+ )
385
+
386
+ # bind app-level MetricsProvider now
387
+ metrics_provider = MetricsProviderFactory(
388
+ global_tags=metric_tags,
389
+ ).create_provider()
390
+ self._injector.binder.bind(
391
+ metrics_provider.__class__,
392
+ to=metrics_provider,
393
+ scope=singleton,
394
+ )
395
+ except BaseException as ex: # pylint: disable=broad-except
396
+ bootstrap_logger.error("Error reading algorithm payload", ex)
397
+
398
+ # ensure we flush bootstrap logger before we exit
399
+ bootstrap_logger.stop()
400
+ sys.exit(1)
337
401
 
338
402
  bootstrap_logger.stop()
339
403
 
@@ -395,7 +459,9 @@ class Nexus:
395
459
  for on_complete_task_class in self._on_complete_tasks
396
460
  ]
397
461
  if len(on_complete_tasks) > 0:
398
- done, pending = await asyncio.wait(on_complete_tasks)
462
+ done, pending = await asyncio.wait(
463
+ on_complete_tasks, return_when=asyncio.FIRST_EXCEPTION
464
+ )
399
465
  if len(pending) > 0:
400
466
  root_logger.warning(
401
467
  "Some post-processing operations did not complete or failed. Please review application logs for more information"
@@ -17,13 +17,11 @@
17
17
  # limitations under the License.
18
18
  #
19
19
 
20
- import json
21
20
  import os
22
21
  import re
23
22
  from pydoc import locate
24
23
  from typing import final, Type, Any
25
24
 
26
- from adapta.metrics import MetricsProvider
27
25
  from adapta.storage.blob.base import StorageClient
28
26
  from adapta.storage.query_enabled_store import QueryEnabledStore
29
27
  from injector import Module, singleton, provider
@@ -51,28 +49,6 @@ from esd_services_api_client.nexus.core.serializers import (
51
49
  )
52
50
 
53
51
 
54
- @final
55
- class MetricsModule(Module):
56
- """
57
- Metrics provider module.
58
- """
59
-
60
- @singleton
61
- @provider
62
- def provide(self) -> MetricsProvider:
63
- """
64
- DI factory method.
65
- """
66
- metrics_class: Type[MetricsProvider] = locate(
67
- os.getenv(
68
- "NEXUS__METRIC_PROVIDER_CLASS",
69
- "adapta.metrics.providers.datadog_provider.DatadogMetricsProvider",
70
- )
71
- )
72
- metrics_settings = json.loads(os.getenv("NEXUS__METRIC_PROVIDER_CONFIGURATION"))
73
- return metrics_class(**metrics_settings)
74
-
75
-
76
52
  @final
77
53
  class BootstrapLoggerFactoryModule(Module):
78
54
  """
@@ -237,7 +213,6 @@ class ServiceConfigurator:
237
213
  def __init__(self):
238
214
  self._injection_binds = [
239
215
  BootstrapLoggerFactoryModule(),
240
- MetricsModule(),
241
216
  CrystalReceiverClientModule(),
242
217
  QueryEnabledStoreModule(),
243
218
  StorageClientModule(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: esd-services-api-client
3
- Version: 2.6.1a149.dev18
3
+ Version: 2.6.2a155.dev2
4
4
  Summary: Python clients for ESD services
5
5
  License: Apache 2.0
6
6
  Author: ECCO Sneaks & Data
@@ -1,5 +1,5 @@
1
1
  esd_services_api_client/__init__.py,sha256=4LskDwFuAFMOjHtN3_-71G_VZ4MNfjMJ7wX2cHYxV-0,648
2
- esd_services_api_client/_version.py,sha256=5jW7m_EcnzUj8Xmyd4-SVBtnXcu70wrdGCsFEBMOrg8,33
2
+ esd_services_api_client/_version.py,sha256=x9TNme45HBCR1griEXTEmapIcfQ52hZrgNjpB8cDyM8,32
3
3
  esd_services_api_client/beast/__init__.py,sha256=zNhXcHSP5w4P9quM1XP4oXVJEccvC_VScG41TZ0GzZ8,723
4
4
  esd_services_api_client/beast/v3/__init__.py,sha256=FtumtInoDyCCRE424Llqv8QZLRuwXzj-smyfu1od1nc,754
5
5
  esd_services_api_client/beast/v3/_connector.py,sha256=VqxiCzJWKERh42aZAIphzmOEG5cdOcKM0DQzG7eQ_-8,11479
@@ -15,12 +15,13 @@ esd_services_api_client/crystal/__init__.py,sha256=oeyJjdQ9EpTnIq6XnjPq5v0DWPdHq
15
15
  esd_services_api_client/crystal/_api_versions.py,sha256=GHbmV_5lP9fP72TZE0j_ZeQSeJjMRcRaBRxNJbz-MWQ,837
16
16
  esd_services_api_client/crystal/_connector.py,sha256=0CqBpPahozAnentM6ZZrH84s_D7SoWCShr5_cRQGMX8,13110
17
17
  esd_services_api_client/crystal/_models.py,sha256=j8SEbwp_iAKjn06i-0f8npmhQhs2YJlmQ3eHwc2TwQE,4366
18
- esd_services_api_client/nexus/README.md,sha256=hNFV_fzAGrG6_1yXE3o_GuLTKIWEia2FfedMh-Sg0ew,10619
18
+ esd_services_api_client/nexus/README.md,sha256=7kR_rmhb_EK0ta_kL72n3RJ4CEnp0C6fofEVvSz1On8,10832
19
19
  esd_services_api_client/nexus/__init__.py,sha256=sOgKKq3_LZGbLmQMtMS7lDw2hv027qownTmNIRV0BB8,627
20
20
  esd_services_api_client/nexus/abstractions/__init__.py,sha256=sOgKKq3_LZGbLmQMtMS7lDw2hv027qownTmNIRV0BB8,627
21
21
  esd_services_api_client/nexus/abstractions/algrorithm_cache.py,sha256=6GevJJ7mf1c_PImhKQ_4_6n652VyHlgK_12LNidirxs,3644
22
22
  esd_services_api_client/nexus/abstractions/input_object.py,sha256=RUKnhekuZwd_RVvnLGAxHa4wYDFJf6wEwWQI9f-o0lM,1761
23
- esd_services_api_client/nexus/abstractions/logger_factory.py,sha256=agkmveLTMwsDx5XdG9TNTEHaFi0leQF3MVSwOUn66os,3957
23
+ esd_services_api_client/nexus/abstractions/logger_factory.py,sha256=oJStrOPir8J18E3ALhUTCQrj3rbjo2yuYrnjpDwLQg8,3947
24
+ esd_services_api_client/nexus/abstractions/metrics_provider_factory.py,sha256=ifcdtzHOrIAM5MNjTEueeU7ezhRmQoJ3_i-iKPE9iIQ,2308
24
25
  esd_services_api_client/nexus/abstractions/nexus_object.py,sha256=rLE42imCVGE6Px4Yu6X6C4b69gA1grK-7Md_SuCLN2Q,3115
25
26
  esd_services_api_client/nexus/abstractions/socket_provider.py,sha256=Rwa_aPErI4Es5AdyCd3EoGze7mg2D70u8kuc2UGEBaI,1729
26
27
  esd_services_api_client/nexus/algorithms/__init__.py,sha256=v4rPDf36r6AaHi_3K8isBKYU_fG8ct3w14KpUg2XRgg,976
@@ -33,8 +34,8 @@ esd_services_api_client/nexus/algorithms/recursive.py,sha256=uaCCl4q-st_KqbcmkdO
33
34
  esd_services_api_client/nexus/configurations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
35
  esd_services_api_client/nexus/configurations/algorithm_configuration.py,sha256=eE7diX2PATCGkmqhvFOcZwXrr6vns4fqnJGmgNvhhZM,1091
35
36
  esd_services_api_client/nexus/core/__init__.py,sha256=sOgKKq3_LZGbLmQMtMS7lDw2hv027qownTmNIRV0BB8,627
36
- esd_services_api_client/nexus/core/app_core.py,sha256=dfC-o_IbExeZfUjVZiByIf7oNsmaiKVK-QIgxhuvpoo,15045
37
- esd_services_api_client/nexus/core/app_dependencies.py,sha256=1m7Oc9JCXyrU0qVZekYhpbXLQo20aQPQ-Bf8zEdjtqI,8704
37
+ esd_services_api_client/nexus/core/app_core.py,sha256=5eLTytj2RS6gtqvSqWBrnLOfo_BZud0P9OILTmHz_do,17236
38
+ esd_services_api_client/nexus/core/app_dependencies.py,sha256=-s_jtEdw25Jef6dMCtIqfIQnAPmzP7fvrB1oq4GSjwA,8042
38
39
  esd_services_api_client/nexus/core/serializers.py,sha256=Vk9FaEeDHXx3S7rPlYoWzsOcN6gzLzemsrjq6ytfaI0,2217
39
40
  esd_services_api_client/nexus/exceptions/__init__.py,sha256=feN33VdqB5-2bD9aJesJl_OlsKrNNo3hZCnQgKuaU9k,696
40
41
  esd_services_api_client/nexus/exceptions/_nexus_error.py,sha256=QvtY38mNoIA6t26dUN6UIsaPfljhtVNsbQVS7ksMb-Q,895
@@ -51,7 +52,7 @@ esd_services_api_client/nexus/modules/mlflow_module.py,sha256=d4y8XetGF37md4dEpE
51
52
  esd_services_api_client/nexus/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
53
  esd_services_api_client/nexus/telemetry/recorder.py,sha256=-shxk2vYDTafJ0U3CzkHxIHBQtxVJ1ZNI4ST0p1YV2g,4801
53
54
  esd_services_api_client/nexus/telemetry/user_telemetry_recorder.py,sha256=NgnjSY64nEx1kslkdomENC6vMqkEOpfXggJXwm-NyFs,5163
54
- esd_services_api_client-2.6.1a149.dev18.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
55
- esd_services_api_client-2.6.1a149.dev18.dist-info/METADATA,sha256=1OO8iLgNdoRClcuBqRIiJ1mCyRNuRB0OEW8cmWTbb5I,1068
56
- esd_services_api_client-2.6.1a149.dev18.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
57
- esd_services_api_client-2.6.1a149.dev18.dist-info/RECORD,,
55
+ esd_services_api_client-2.6.2a155.dev2.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
56
+ esd_services_api_client-2.6.2a155.dev2.dist-info/METADATA,sha256=Dg-Er67YUqZPAqWEkE3iXSwlESmj5Uay_7b9fn3BTq8,1067
57
+ esd_services_api_client-2.6.2a155.dev2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
58
+ esd_services_api_client-2.6.2a155.dev2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any