dt-extensions-sdk 1.1.11__py3-none-any.whl → 1.1.12__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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dt-extensions-sdk
3
- Version: 1.1.11
3
+ Version: 1.1.12
4
4
  Project-URL: Documentation, https://github.com/dynatrace-extensions/dt-extensions-python-sdk#readme
5
5
  Project-URL: Issues, https://github.com/dynatrace-extensions/dt-extensions-python-sdk/issues
6
6
  Project-URL: Source, https://github.com/dynatrace-extensions/dt-extensions-python-sdk
@@ -18,7 +18,7 @@ Requires-Python: >=3.10
18
18
  Provides-Extra: cli
19
19
  Requires-Dist: dt-cli>=1.6.13; extra == 'cli'
20
20
  Requires-Dist: pyyaml; extra == 'cli'
21
- Requires-Dist: typer[all]; extra == 'cli'
21
+ Requires-Dist: typer; extra == 'cli'
22
22
  Description-Content-Type: text/markdown
23
23
 
24
24
  # Dynatrace Extensions Python SDK
@@ -1,7 +1,7 @@
1
- dynatrace_extension/__about__.py,sha256=CG0yZc6B3sWvqcZg92zLklMVRsTrmWxs6iHrC4jKdko,109
1
+ dynatrace_extension/__about__.py,sha256=XQBn893nbChfqgenpRBcrSsmllyosJ-0hLOaYr6NUkM,110
2
2
  dynatrace_extension/__init__.py,sha256=BvQuknmA7ti3WJi3zEXZfY7aAxJrie37VNitWICsUvI,752
3
3
  dynatrace_extension/cli/__init__.py,sha256=HCboY_eJPoqjFmoPDsBL8Jk6aNvank8K7JpkVrgwzUM,123
4
- dynatrace_extension/cli/main.py,sha256=FyH3SLed5X4vKGXUdzAJNWp1H8pZ1E6xnOTyQ9BypzQ,16166
4
+ dynatrace_extension/cli/main.py,sha256=wi3xxji3WbVXViebzLCB5UGNSADUcdYUzGI02EYbsaM,16965
5
5
  dynatrace_extension/cli/schema.py,sha256=d8wKUodRiaU3hfSZDWVNpD15lBfhmif2oQ-k07IxcaA,3230
6
6
  dynatrace_extension/cli/create/__init__.py,sha256=NfyOJCVlxs8dYtfDAMHS1Q5SJTuZcFzOg5rtaI-ZPRE,72
7
7
  dynatrace_extension/cli/create/create.py,sha256=apXden2M93MDDDm7aa-Os-AEtUtyKbk_PsS56j32NK4,2708
@@ -16,9 +16,9 @@ dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.tem
16
16
  dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
17
17
  dynatrace_extension/sdk/activation.py,sha256=goTbT1tD2kn8xfyXFdTy_cTZNcFPJpgbvQM8HOzKECA,1480
18
18
  dynatrace_extension/sdk/callback.py,sha256=x1ImFqAhomfqWtoOkQLksWqNlIi_3E3v3TaWhNndauU,6165
19
- dynatrace_extension/sdk/communication.py,sha256=c92sQ08NWEtC3VhXcw0MZcDpaM9nPcNTtCmBaLCE_zw,16649
19
+ dynatrace_extension/sdk/communication.py,sha256=7N516-6g2IKzmWi_4PFPU_mkz49SG8KTPi7MrRxgTF8,17255
20
20
  dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
21
- dynatrace_extension/sdk/extension.py,sha256=MOmqZ7Rb0rcD9v-zhk4-JO-vCw5h4FMVvuRnSYMckck,40121
21
+ dynatrace_extension/sdk/extension.py,sha256=B79fvNB4KoQ8x5MnEvb9EO4a5xeNBzk3EF1I_8RZ2Nc,40213
22
22
  dynatrace_extension/sdk/helper.py,sha256=ZNrO9ao2hE3KQ934vAYD74k0fCr6QTG-_bAvbk9-hi8,6562
23
23
  dynatrace_extension/sdk/metric.py,sha256=7VClzJCFJNDCxA-d69uTu1pdPtDZBTwq7fbafs_L6nQ,3690
24
24
  dynatrace_extension/sdk/runtime.py,sha256=jyYsM1x-gMnW68eWq8IoZZZBarHgIcr_nVeGDDgpRDk,2802
@@ -26,8 +26,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
26
26
  dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
27
27
  dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=TQ3xcpfwLEYIU1TU65A9OqWqwIKqyO8SSUFeuCvE60Y,14655
29
- dt_extensions_sdk-1.1.11.dist-info/METADATA,sha256=RJTp4_IuLeAypFDrlhUPm-I1x1Y4vVhBp0lHz-uNfaE,2794
30
- dt_extensions_sdk-1.1.11.dist-info/WHEEL,sha256=K0BPUNF1N3kQ9olb8aVEtkObePEjdr2JOLT1N83EVws,87
31
- dt_extensions_sdk-1.1.11.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
32
- dt_extensions_sdk-1.1.11.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
33
- dt_extensions_sdk-1.1.11.dist-info/RECORD,,
29
+ dt_extensions_sdk-1.1.12.dist-info/METADATA,sha256=DK_2-CG9dk8x2V-x2lUUQkOjh2OOg-eH1EW16bNTY8w,2789
30
+ dt_extensions_sdk-1.1.12.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
31
+ dt_extensions_sdk-1.1.12.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
32
+ dt_extensions_sdk-1.1.12.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
33
+ dt_extensions_sdk-1.1.12.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.24.0
2
+ Generator: hatchling 1.24.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,4 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2023-present Dynatrace LLC
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "1.1.11"
4
+
5
+ __version__ = "1.1.12"
@@ -38,6 +38,7 @@ def run(
38
38
  fast_check: bool = typer.Option(False, "--fastcheck"),
39
39
  local_ingest: bool = typer.Option(False, "--local-ingest"),
40
40
  local_ingest_port: int = typer.Option(14499, "--local-ingest-port"),
41
+ print_metrics: bool = typer.Option(True),
41
42
  ):
42
43
  """
43
44
  Runs an extension, this is used during development to locally run and test an extension
@@ -47,6 +48,7 @@ def run(
47
48
  :param fast_check: If true, run a fastcheck and exits
48
49
  :param local_ingest: If true, send metrics to localhost:14499 on top of printing them
49
50
  :param local_ingest_port: The port to send metrics to, by default this is 14499
51
+ :param print_metrics: If true, print metrics to the console
50
52
  """
51
53
 
52
54
  # This parses the yaml, which validates it before running
@@ -58,6 +60,8 @@ def run(
58
60
  if local_ingest:
59
61
  command.append("--local-ingest")
60
62
  command.append(f"--local-ingest-port={local_ingest_port}")
63
+ if not print_metrics:
64
+ command.append("--no-print-metrics")
61
65
  run_process(command, cwd=extension_dir)
62
66
  except KeyboardInterrupt:
63
67
  console.print("\nRun interrupted with a KeyboardInterrupt, stopping", style="bold yellow")
@@ -79,6 +83,7 @@ def build(
79
83
  extra_index_url: Optional[str] = typer.Option(
80
84
  None, "--extra-index-url", "-i", help="Extra index url to use when downloading dependencies"
81
85
  ),
86
+ find_links: Optional[str] = typer.Option( None, "--find-links", "-f", help="Extra index url to use when downloading dependencies" ),
82
87
  ):
83
88
  """
84
89
  Builds and signs an extension using the developer fused key-certificate
@@ -90,6 +95,7 @@ def build(
90
95
  folder
91
96
  :param extra_platforms: Attempt to also download wheels for an extra platform (e.g. manylinux1_x86_64 or win_amd64)
92
97
  :param extra_index_url: Extra index url to use when downloading dependencies
98
+ :param find_links: Extra index url to use when downloading dependencies
93
99
  """
94
100
  console.print(f"Building and signing extension from {extension_dir} to {target_directory}", style="cyan")
95
101
  if target_directory is None:
@@ -98,7 +104,7 @@ def build(
98
104
  target_directory.mkdir()
99
105
 
100
106
  console.print("Stage 1 - Download and build dependencies", style="bold blue")
101
- wheel(extension_dir, extra_platforms, extra_index_url)
107
+ wheel(extension_dir, extra_platforms, extra_index_url, find_links)
102
108
 
103
109
  console.print("Stage 2 - Create the extension zip file", style="bold blue")
104
110
  built_zip = assemble(extension_dir, target_directory)
@@ -163,6 +169,7 @@ def wheel(
163
169
  extra_index_url: Optional[str] = typer.Option(
164
170
  None, "--extra-index-url", "-i", help="Extra index url to use when downloading dependencies"
165
171
  ),
172
+ find_links: Optional[str] = typer.Option( None, "--find-links", "-f", help="Extra index url to use when downloading dependencies" ),
166
173
  ):
167
174
  """
168
175
  Builds the extension and it's dependencies into wheel files
@@ -171,6 +178,7 @@ def wheel(
171
178
  :param extension_dir: The directory of the extension, by default this is the current directory
172
179
  :param extra_platforms: Attempt to also download wheels for an extra platform (e.g. manylinux1_x86_64 or win_amd64)
173
180
  :param extra_index_url: Extra index url to use when downloading dependencies
181
+ :param find_links: Extra index url to use when downloading dependencies
174
182
  """
175
183
  relative_lib_folder_dir = "extension/lib"
176
184
  lib_folder: Path = extension_dir / relative_lib_folder_dir
@@ -182,6 +190,8 @@ def wheel(
182
190
  command = [sys.executable, "-m", "pip", "wheel", "-w", relative_lib_folder_dir]
183
191
  if extra_index_url is not None:
184
192
  command.extend(["--extra-index-url", extra_index_url])
193
+ if find_links is not None:
194
+ command.extend(["--find-links", find_links])
185
195
  command.append(".")
186
196
  run_process(command, cwd=extension_dir)
187
197
 
@@ -201,6 +211,8 @@ def wheel(
201
211
  ]
202
212
  if extra_index_url:
203
213
  command.extend(["--extra-index-url", extra_index_url])
214
+ if find_links:
215
+ command.extend(["--find-links", find_links])
204
216
  command.append(".")
205
217
 
206
218
  run_process(command, cwd=extension_dir)
@@ -6,7 +6,9 @@ from __future__ import annotations
6
6
 
7
7
  import json
8
8
  import logging
9
+ import random
9
10
  import sys
11
+ import time
10
12
  from abc import ABC, abstractmethod
11
13
  from dataclasses import dataclass
12
14
  from enum import Enum
@@ -114,8 +116,6 @@ class HttpClient(CommunicationClient):
114
116
  """
115
117
 
116
118
  def __init__(self, base_url: str, datasource_id: str, id_token_file_path: str, logger: logging.Logger):
117
- # TODO - Do we need to replace 127.0.0.1 with localhost?
118
-
119
119
  self._activation_config_url = f"{base_url}/userconfig/{datasource_id}"
120
120
  self._extension_config_url = f"{base_url}/extconfig/{datasource_id}"
121
121
  self._metric_url = f"{base_url}/mint/{datasource_id}"
@@ -324,6 +324,7 @@ class DebugClient(CommunicationClient):
324
324
  logger: logging.Logger,
325
325
  local_ingest: bool = False,
326
326
  local_ingest_port: int = 14499,
327
+ print_metrics: bool = True
327
328
  ):
328
329
  self.activation_config = {}
329
330
  if activation_config_path and Path(activation_config_path).exists():
@@ -339,6 +340,7 @@ class DebugClient(CommunicationClient):
339
340
  self.logger = logger
340
341
  self.local_ingest = local_ingest
341
342
  self.local_ingest_port = local_ingest_port
343
+ self.print_metrics = print_metrics
342
344
 
343
345
  def get_activation_config(self) -> dict:
344
346
  return self.activation_config
@@ -386,23 +388,36 @@ class DebugClient(CommunicationClient):
386
388
  return self.send_status(Status())
387
389
 
388
390
  def send_metrics(self, mint_lines: list[str]) -> list[MintResponse]:
391
+ total_lines = len(mint_lines)
392
+ lines_sent = 0
393
+
394
+ self.logger.info(f"Start sending {total_lines} metrics to the EEC")
395
+
389
396
  responses = []
390
- for line in mint_lines:
391
- self.logger.info(f"send_metric: {line}")
392
397
 
393
- if self.local_ingest:
394
- mint_data = "\n".join(mint_lines).encode("utf-8")
395
- response = request(
396
- "POST",
397
- f"http://localhost:{self.local_ingest_port}/metrics/ingest",
398
- body=mint_data,
399
- headers={"Content-Type": CONTENT_TYPE_PLAIN},
400
- ).json()
401
- mint_response = MintResponse.from_json(response)
402
- responses.append(mint_response)
398
+ chunks = divide_into_chunks(mint_lines, MAX_MINT_LINES_PER_REQUEST)
399
+ for chunk in chunks:
400
+ lines_in_chunk = len(chunk)
401
+ lines_sent += lines_in_chunk
402
+ self.logger.debug(f"Sending chunk with {lines_in_chunk} metric lines. ({lines_sent}/{total_lines})")
403
+
404
+ if self.local_ingest:
405
+ mint_data = "\n".join(chunk).encode("utf-8")
406
+ response = request(
407
+ "POST",
408
+ f"http://localhost:{self.local_ingest_port}/metrics/ingest",
409
+ body=mint_data,
410
+ headers={"Content-Type": CONTENT_TYPE_PLAIN},
411
+ ).json()
412
+ mint_response = MintResponse.from_json(response)
413
+ responses.append(mint_response)
414
+ else:
415
+ if self.print_metrics:
416
+ for line in mint_lines:
417
+ self.logger.info(f"send_metric: {line}")
403
418
 
404
- if not responses:
405
- responses = [MintResponse(lines_invalid=0, lines_ok=len(mint_lines), error=None, warnings=None)]
419
+ response = MintResponse(lines_invalid=0, lines_ok=len(chunk), error=None, warnings=None)
420
+ responses.append(response)
406
421
  return responses
407
422
 
408
423
  def send_events(self, events: dict | list[dict], eec_enrichment: bool = True) -> dict | None:
@@ -159,8 +159,6 @@ class Extension:
159
159
  if hasattr(self, "logger"):
160
160
  return
161
161
 
162
- # TODO - Move the logging implementation to its own file
163
- # TODO - Add sfm logging
164
162
  self.logger = extension_logger
165
163
 
166
164
  self.extension_config: str = ""
@@ -313,7 +311,6 @@ class Extension:
313
311
  api_logger.debug(f"Scheduling callback {callback}")
314
312
 
315
313
  # These properties are updated after the extension starts
316
- # TODO - These should be part of an ext singleton object instead
317
314
  callback.cluster_time_diff = self._cluster_time_diff
318
315
  callback.running_in_sim = self._running_in_sim
319
316
  self._scheduled_callbacks.append(callback)
@@ -698,14 +695,21 @@ class Extension:
698
695
  # Debug parameters, these are used when running the extension locally
699
696
  parser.add_argument("--extensionconfig", required=False, default=None)
700
697
  parser.add_argument("--activationconfig", required=False, default="activation.json")
698
+ parser.add_argument("--no-print-metrics", required=False, action="store_true")
701
699
 
702
700
  args, unknown = parser.parse_known_args()
703
701
  self._is_fastcheck = args.fastcheck
704
702
  if args.dsid is None:
705
703
  # DEV mode
706
704
  self._running_in_sim = True
705
+ print_metrics = not args.no_print_metrics
707
706
  self._client = DebugClient(
708
- args.activationconfig, args.extensionconfig, api_logger, args.local_ingest, args.local_ingest_port
707
+ activation_config_path=args.activationconfig,
708
+ extension_config_path=args.extensionconfig,
709
+ logger=api_logger,
710
+ local_ingest=args.local_ingest,
711
+ local_ingest_port=args.local_ingest_port,
712
+ print_metrics=print_metrics
709
713
  )
710
714
  RuntimeProperties.set_default_log_level(args.loglevel)
711
715
  else:
@@ -810,23 +814,22 @@ class Extension:
810
814
  self._scheduler.enter(SFM_METRIC_SENDING_INTERVAL.total_seconds(), 1, self._sfm_metrics_iteration)
811
815
 
812
816
  def _send_metrics(self):
813
- # TODO - we might need to check size and number of lines before sending
814
- # Maybe break it down into multiple packets
815
- with self._metrics_lock and self._internal_callbacks_results_lock:
816
- if self._metrics:
817
- number_of_metrics = len(self._metrics)
818
- responses = self._client.send_metrics(self._metrics)
819
-
820
- self._internal_callbacks_results[self._send_metrics.__name__] = Status(StatusValue.OK)
821
- lines_invalid = sum(response.lines_invalid for response in responses)
822
- if lines_invalid > 0:
823
- message = f"{lines_invalid} invalid metric lines found"
824
- self._internal_callbacks_results[self._send_metrics.__name__] = Status(
825
- StatusValue.GENERIC_ERROR, message
826
- )
827
-
828
- api_logger.info(f"Sent {number_of_metrics} metric lines to EEC: {responses}")
829
- self._metrics = []
817
+ with self._metrics_lock:
818
+ with self._internal_callbacks_results_lock:
819
+ if self._metrics:
820
+ number_of_metrics = len(self._metrics)
821
+ responses = self._client.send_metrics(self._metrics)
822
+
823
+ self._internal_callbacks_results[self._send_metrics.__name__] = Status(StatusValue.OK)
824
+ lines_invalid = sum(response.lines_invalid for response in responses)
825
+ if lines_invalid > 0:
826
+ message = f"{lines_invalid} invalid metric lines found"
827
+ self._internal_callbacks_results[self._send_metrics.__name__] = Status(
828
+ StatusValue.GENERIC_ERROR, message
829
+ )
830
+
831
+ api_logger.info(f"Sent {number_of_metrics} metric lines to EEC: {responses}")
832
+ self._metrics = []
830
833
 
831
834
  def _prepare_sfm_metrics(self) -> List[str]:
832
835
  """Prepare self monitoring metrics.