esd-services-api-client 2.6.2a158.dev6__py3-none-any.whl → 2.6.3a155.dev7__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.2a158.dev6'
1
+ __version__ = 'v2.6.3a155.dev7'
@@ -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()
@@ -0,0 +1,106 @@
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 os
21
+ from dataclasses import dataclass
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
+ from dataclasses_json import DataClassJsonMixin
28
+
29
+ from esd_services_api_client.nexus.exceptions.startup_error import (
30
+ FatalStartupConfigurationError,
31
+ )
32
+
33
+
34
+ @final
35
+ @dataclass
36
+ class MetricsProviderSettings(DataClassJsonMixin):
37
+ """
38
+ Settings model for the metrics provider
39
+ """
40
+
41
+ init_args: dict
42
+ fixed_tags: dict[str, str] | None = None
43
+ protocol: str | None = None
44
+
45
+ def __post_init__(self):
46
+ """
47
+ Force not-null values with default constructor, even if the source provides nulls.
48
+ """
49
+ if self.protocol is None:
50
+ self.protocol = "udp"
51
+
52
+ if self.fixed_tags is None:
53
+ self.fixed_tags = {}
54
+
55
+
56
+ @final
57
+ class MetricsProviderFactory:
58
+ """
59
+ Async logger provisioner.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ global_tags: dict[str, str] | None = None,
65
+ ):
66
+ self._global_tags = global_tags
67
+ self._metrics_class: type[MetricsProvider] = locate(
68
+ os.getenv(
69
+ "NEXUS__METRICS_PROVIDER_CLASS",
70
+ "adapta.metrics.providers.datadog_provider.DatadogMetricsProvider",
71
+ )
72
+ )
73
+
74
+ if "NEXUS__METRICS_PROVIDER_CONFIGURATION" not in os.environ:
75
+ raise FatalStartupConfigurationError(
76
+ "NEXUS__METRICS_PROVIDER_CONFIGURATION is not provided, cannot initialize a metrics provider instance"
77
+ )
78
+
79
+ self._metrics_settings: MetricsProviderSettings = (
80
+ MetricsProviderSettings.from_json(
81
+ os.getenv("NEXUS__METRICS_PROVIDER_CONFIGURATION")
82
+ )
83
+ )
84
+
85
+ def create_provider(
86
+ self,
87
+ ) -> MetricsProvider:
88
+ """
89
+ Creates a metrics provider enriched with additional tags for each metric emitted by this algorithm.
90
+ In case of DatadogMetricsProvider, takes care of UDP/UDS specific initialization.
91
+ """
92
+ self._metrics_settings.fixed_tags |= self._global_tags
93
+
94
+ if self._metrics_class == DatadogMetricsProvider:
95
+ if self._metrics_settings.protocol == "udp":
96
+ return self._metrics_class.udp(**self._metrics_settings.init_args)
97
+
98
+ if self._metrics_settings.protocol == "uds":
99
+ return self._metrics_class.uds(**self._metrics_settings.init_args)
100
+
101
+ return self._metrics_class(
102
+ **(
103
+ self._metrics_settings.init_args
104
+ | {"fixed_tags": self._metrics_settings.fixed_tags}
105
+ )
106
+ )
@@ -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
@@ -225,6 +236,23 @@ class Nexus:
225
236
  self._log_enrichment_delimiter = delimiter
226
237
  return self
227
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
+
228
256
  def with_module(self, module: Type[Module]) -> "Nexus":
229
257
  """
230
258
  Adds a (custom) DI module into the DI container.
@@ -317,28 +345,59 @@ class Nexus:
317
345
 
318
346
  bootstrap_logger.start()
319
347
 
320
- for payload_type in self._payload_types:
321
- try:
348
+ try:
349
+ logger_fixed_template = {}
350
+ logger_tags = {}
351
+ metric_tags = {}
352
+
353
+ for payload_type in self._payload_types:
322
354
  payload = await self._get_payload(payload_type=payload_type)
323
355
  self._injector.binder.bind(
324
356
  payload.__class__, to=payload, scope=singleton
325
357
  )
326
- logger_factory = LoggerFactory(
327
- fixed_template=None
328
- if not self._log_enricher
329
- else self._log_enricher(payload, self._run_args),
330
- fixed_template_delimiter=self._log_enrichment_delimiter,
331
- 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 {}
332
362
  )
333
- # bind app-level LoggerFactory now
334
- self._injector.binder.bind(
335
- logger_factory.__class__,
336
- to=logger_factory,
337
- scope=singleton,
363
+ logger_tags |= (
364
+ self._log_tagger(payload, self._run_args)
365
+ if self._log_tagger
366
+ else {}
338
367
  )
339
- except BaseException as ex: # pylint: disable=broad-except
340
- bootstrap_logger.error("Error reading algorithm payload", ex)
341
- 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)
342
401
 
343
402
  bootstrap_logger.stop()
344
403
 
@@ -400,7 +459,9 @@ class Nexus:
400
459
  for on_complete_task_class in self._on_complete_tasks
401
460
  ]
402
461
  if len(on_complete_tasks) > 0:
403
- done, pending = await asyncio.wait(on_complete_tasks)
462
+ done, pending = await asyncio.wait(
463
+ on_complete_tasks, return_when=asyncio.FIRST_EXCEPTION
464
+ )
404
465
  if len(pending) > 0:
405
466
  root_logger.warning(
406
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.2a158.dev6
3
+ Version: 2.6.3a155.dev7
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=BFT3GAEuBdlaagsxlEJTo148nYL5qvNWSCQNnPshMwg,32
2
+ esd_services_api_client/_version.py,sha256=9dKnoZygvWOmOrbdRwD8cP93iiTMUgYzawYWtQl40c0,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
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=0wPcSBolTIL6nGLv7BNpYfcyc9_qUF-puLsWgUkg9w0,3318
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=Lmkpq7Hy74EfMOi5vtjD82PpY7Lb84RVbXN5b82BxKs,15462
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=0IfXadbR3G0mowqRQlIOP2LFjNs-P-Tr42Ia2G-ehtg,2275
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=E4bj0N4QDo9kSxf1XMSplCfSG29iDKg-UgrRY-nq_XE,5225
54
- esd_services_api_client-2.6.2a158.dev6.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
55
- esd_services_api_client-2.6.2a158.dev6.dist-info/METADATA,sha256=tA9xEH1FCVQe401yXJDggxpmNdhgKL-RS0Wafc59NvU,1071
56
- esd_services_api_client-2.6.2a158.dev6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
57
- esd_services_api_client-2.6.2a158.dev6.dist-info/RECORD,,
55
+ esd_services_api_client-2.6.3a155.dev7.dist-info/LICENSE,sha256=0gS6zXsPp8qZhzi1xaGCIYPzb_0e8on7HCeFJe8fOpw,10693
56
+ esd_services_api_client-2.6.3a155.dev7.dist-info/METADATA,sha256=Kxke6Te8fjk-PlDapySEgg1PkoifkrbSH0oWFNrKlPc,1071
57
+ esd_services_api_client-2.6.3a155.dev7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
58
+ esd_services_api_client-2.6.3a155.dev7.dist-info/RECORD,,