dt-extensions-sdk 1.2.14__py3-none-any.whl → 1.3.1__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
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: dt-extensions-sdk
3
- Version: 1.2.14
3
+ Version: 1.3.1
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
@@ -1,13 +1,14 @@
1
- dynatrace_extension/__about__.py,sha256=OAg8p7JkpTB4TTezDDuRFRD-dX4xumJyns-GLgyZaOw,111
1
+ dynatrace_extension/__about__.py,sha256=-jEpNU1eRpe8GBW_KoSO-6Flc820P-KhyssXl0K9kO4,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=qv6BRgoyvhThhRh64OzL6OYHSLoxiJgWlZIxUDxKX4E,17995
4
+ dynatrace_extension/cli/main.py,sha256=Z8gFcp0vIMBkzV6jUd5mwvP1U0JcLqAHoMLJw_6puz4,18260
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
8
- dynatrace_extension/cli/create/extension_template/.gitignore.template,sha256=XixnHdyeMB2vwkGtGnwHqoWpH-9WChdyG0GXe57duhc,3078
8
+ dynatrace_extension/cli/create/extension_template/.gitignore.template,sha256=FPye23W8dqmked4HQBCDCAKFf1UbBGkwhKjQpgmXhdg,3101
9
9
  dynatrace_extension/cli/create/extension_template/README.md.template,sha256=QcV0fYqJ1PHaouKdGQgzveJY5zAFBSICp7xSfgnoQj0,637
10
- dynatrace_extension/cli/create/extension_template/activation.json.template,sha256=EtjCI3EZY5nHu4ONcWLzaOR-Et5SdU0BMkCp0ZSPSrA,257
10
+ dynatrace_extension/cli/create/extension_template/activation.json.template,sha256=qX-Fgq_JhUWNYMe1-RMcjwQPzF4scJYGfGlBr043UiY,266
11
+ dynatrace_extension/cli/create/extension_template/secrets.json.template,sha256=fr-ya8lR0TiUmr6lf-PFko6RZw6sr54FxtMbqIWBVkA,36
11
12
  dynatrace_extension/cli/create/extension_template/setup.py.template,sha256=M4HPg3UFkvmWFHAz6vHbDG5Ocb-ZiR0TQuLt9eedW1M,846
12
13
  dynatrace_extension/cli/create/extension_template/extension/activationSchema.json.template,sha256=me3DL_Q449q4VaOStTqaBL-gUkKnlafC8L2093O0LOY,2905
13
14
  dynatrace_extension/cli/create/extension_template/extension/extension.yaml.template,sha256=qDyAURGXKsZBIPjW2s1BPhD-xOC8_FoBgA33HbsOkAU,299
@@ -15,10 +16,10 @@ dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.tem
15
16
  dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=NYuZ6BWZ6HOs4TCuboh6S0nuYp3F8MXW2lmjSpdREEA,1205
16
17
  dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
17
18
  dynatrace_extension/sdk/activation.py,sha256=goTbT1tD2kn8xfyXFdTy_cTZNcFPJpgbvQM8HOzKECA,1480
18
- dynatrace_extension/sdk/callback.py,sha256=PNRvOVzNpKvpsa0wCf7HRDkfo3wMsF2otVWI9a1AAkU,6307
19
- dynatrace_extension/sdk/communication.py,sha256=lPUKxAq3mO8cO1ye79p9nuxYL-_cG6G4cJRzBZxD64w,17792
19
+ dynatrace_extension/sdk/callback.py,sha256=eMpC0F3fCI82mWHIFgmy9QmKl7Kq_9dSaanHdV6o7hA,6511
20
+ dynatrace_extension/sdk/communication.py,sha256=QkJgEBblOen-jmvsb3ZfYZYglYUc7jHbkEgPtRj9l6w,19123
20
21
  dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
21
- dynatrace_extension/sdk/extension.py,sha256=2H86BhYwuLGs35c522TgMHBiBJcc_qXcQO-AxGHp2x4,42816
22
+ dynatrace_extension/sdk/extension.py,sha256=RnF7Czlmg6QrQLLlP90dM0tAIxdX3LrqaUi8M1p1Guo,43251
22
23
  dynatrace_extension/sdk/helper.py,sha256=ZNrO9ao2hE3KQ934vAYD74k0fCr6QTG-_bAvbk9-hi8,6562
23
24
  dynatrace_extension/sdk/metric.py,sha256=7VClzJCFJNDCxA-d69uTu1pdPtDZBTwq7fbafs_L6nQ,3690
24
25
  dynatrace_extension/sdk/runtime.py,sha256=jyYsM1x-gMnW68eWq8IoZZZBarHgIcr_nVeGDDgpRDk,2802
@@ -27,8 +28,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
27
28
  dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
28
29
  dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
30
  dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=znF4mvzk5L03CLNozRz8UpK-fMijmSkObDFwlbhwLUg,14656
30
- dt_extensions_sdk-1.2.14.dist-info/METADATA,sha256=RCVLQM2azU7CLaL0G507ojhkDVPlqpFJwShuCAKv1BY,2686
31
- dt_extensions_sdk-1.2.14.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
32
- dt_extensions_sdk-1.2.14.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
33
- dt_extensions_sdk-1.2.14.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
34
- dt_extensions_sdk-1.2.14.dist-info/RECORD,,
31
+ dt_extensions_sdk-1.3.1.dist-info/METADATA,sha256=LwKOdwizaSteiT3s-yPkAXYRyqkiqQC11TaPXiGlJ-Q,2685
32
+ dt_extensions_sdk-1.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ dt_extensions_sdk-1.3.1.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
34
+ dt_extensions_sdk-1.3.1.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
35
+ dt_extensions_sdk-1.3.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
 
6
- __version__ = "1.2.14"
6
+ __version__ = "1.3.1"
@@ -158,3 +158,6 @@ cython_debug/
158
158
  # and can be added to the global gitignore or merged into this file. For a more nuclear
159
159
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
160
  #.idea/
161
+
162
+ # Secrets
163
+ secrets.json
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "url": "http://127.0.0.1:15672",
10
10
  "user": "guest",
11
- "password": "guest"
11
+ "password": "{{myPassword}}"
12
12
  }
13
13
  ]
14
14
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "myPassword": "secretPassword"
3
+ }
@@ -40,6 +40,7 @@ def version():
40
40
  def run(
41
41
  extension_dir: Path = typer.Argument("."),
42
42
  activation_config: str = "activation.json",
43
+ secrets: str = "secrets.json",
43
44
  fast_check: bool = typer.Option(False, "--fastcheck"),
44
45
  local_ingest: bool = typer.Option(False, "--local-ingest"),
45
46
  local_ingest_port: int = typer.Option(14499, "--local-ingest-port"),
@@ -50,6 +51,7 @@ def run(
50
51
 
51
52
  :param extension_dir: The directory of the extension, by default this is the current directory
52
53
  :param activation_config: The activation config file, by default this is activation.json
54
+ :param secrets: The secrets file to be used to enrich the activation config, by default this is secrets.json
53
55
  :param fast_check: If true, run a fastcheck and exits
54
56
  :param local_ingest: If true, send metrics to localhost:14499 on top of printing them
55
57
  :param local_ingest_port: The port to send metrics to, by default this is 14499
@@ -59,7 +61,15 @@ def run(
59
61
  # This parses the yaml, which validates it before running
60
62
  extension_yaml = ExtensionYaml(extension_dir / "extension/extension.yaml")
61
63
  try:
62
- command = [sys.executable, "-m", extension_yaml.python.runtime.module, "--activationconfig", activation_config]
64
+ command = [
65
+ sys.executable,
66
+ "-m",
67
+ extension_yaml.python.runtime.module,
68
+ "--activationconfig",
69
+ activation_config,
70
+ "--secrets",
71
+ secrets,
72
+ ]
63
73
  if fast_check:
64
74
  command.append("--fastcheck")
65
75
  if local_ingest:
@@ -9,7 +9,7 @@ from timeit import default_timer as timer
9
9
  from typing import Callable, Dict, Optional, Tuple
10
10
 
11
11
  from .activation import ActivationType
12
- from .communication import Status, StatusValue
12
+ from .communication import MultiStatus, Status, StatusValue
13
13
 
14
14
 
15
15
  class WrappedCallback:
@@ -60,8 +60,13 @@ class WrappedCallback:
60
60
  start_time = timer()
61
61
  failed = False
62
62
  try:
63
- self.callback(*self.callback_args, **self.callback_kwargs)
64
- self.status = Status(StatusValue.OK)
63
+ ret = self.callback(*self.callback_args, **self.callback_kwargs)
64
+ if isinstance(ret, Status):
65
+ self.status = ret
66
+ elif isinstance(ret, MultiStatus):
67
+ self.status = ret.build()
68
+ else:
69
+ self.status = Status(StatusValue.OK)
65
70
  except Exception as e:
66
71
  failed = True
67
72
  self.logger.exception(f"Error running callback {self}: {e!r}")
@@ -59,6 +59,29 @@ class Status:
59
59
  return self.status not in (StatusValue.OK, StatusValue.EMPTY)
60
60
 
61
61
 
62
+ class MultiStatus:
63
+
64
+ def __init__(self):
65
+ self.statuses = []
66
+
67
+ def add_status(self, status: StatusValue, message):
68
+ self.statuses.append(Status(status, message))
69
+
70
+ def build(self) -> Status:
71
+ ret = Status(StatusValue.OK)
72
+ if len(self.statuses) == 0:
73
+ return ret
74
+
75
+ messages = []
76
+ for stored_status in self.statuses:
77
+ print(stored_status) # noqa: T201
78
+ if stored_status.is_error():
79
+ ret.status = stored_status.status
80
+ messages.append(stored_status.message)
81
+ ret.message = "\n".join(messages)
82
+ return ret
83
+
84
+
62
85
  class CommunicationClient(ABC):
63
86
  """
64
87
  Abstract class for extension communication
@@ -324,14 +347,24 @@ class DebugClient(CommunicationClient):
324
347
  activation_config_path: str,
325
348
  extension_config_path: str,
326
349
  logger: logging.Logger,
350
+ secrets_path: str = "secrets.json",
327
351
  local_ingest: bool = False,
328
352
  local_ingest_port: int = 14499,
329
353
  print_metrics: bool = True,
330
354
  ):
355
+
356
+ self.secrets = {}
357
+ if secrets_path and Path(secrets_path).exists():
358
+ with open(secrets_path) as f:
359
+ self.secrets = json.load(f)
360
+
331
361
  self.activation_config = {}
332
362
  if activation_config_path and Path(activation_config_path).exists():
333
363
  with open(activation_config_path) as f:
334
- self.activation_config = json.load(f)
364
+ raw_activation_config = f.read()
365
+ self.activation_config = json.loads(
366
+ self.replace_secrets_in_activation_config(self.secrets, raw_activation_config)
367
+ )
335
368
 
336
369
  self.extension_config = ""
337
370
  if not extension_config_path:
@@ -430,6 +463,12 @@ class DebugClient(CommunicationClient):
430
463
  def get_cluster_time_diff(self) -> int:
431
464
  return 0
432
465
 
466
+ def replace_secrets_in_activation_config(self, secrets: dict, activation_config_string: str) -> str:
467
+ for secret_name, secret_value in secrets.items():
468
+ activation_config_string = activation_config_string.replace(f"{{{{{secret_name}}}}}", str(secret_value))
469
+
470
+ return activation_config_string
471
+
433
472
 
434
473
  def divide_into_batches(
435
474
  items: Sequence[dict | str], max_size_bytes: int, join_with: str | None = None
@@ -32,6 +32,7 @@ TIME_DIFF_INTERVAL = timedelta(seconds=60)
32
32
 
33
33
  CALLBACKS_THREAD_POOL_SIZE = 100
34
34
  INTERNAL_THREAD_POOL_SIZE = 20
35
+ HEARTBEAT_THREAD_POOL_SIZE = 10
35
36
 
36
37
  RFC_3339_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
37
38
  DATASOURCE_TYPE = "python"
@@ -220,6 +221,7 @@ class Extension:
220
221
  # Executors for the callbacks and internal methods
221
222
  self._callbacks_executor = ThreadPoolExecutor(max_workers=CALLBACKS_THREAD_POOL_SIZE)
222
223
  self._internal_executor = ThreadPoolExecutor(max_workers=INTERNAL_THREAD_POOL_SIZE)
224
+ self._heartbeat_executor = ThreadPoolExecutor(max_workers=HEARTBEAT_THREAD_POOL_SIZE)
223
225
 
224
226
  # Extension metrics
225
227
  self._metrics_lock = RLock()
@@ -715,6 +717,7 @@ class Extension:
715
717
  # Debug parameters, these are used when running the extension locally
716
718
  parser.add_argument("--extensionconfig", required=False, default=None)
717
719
  parser.add_argument("--activationconfig", required=False, default="activation.json")
720
+ parser.add_argument("--secrets", required=False, default="secrets.json")
718
721
  parser.add_argument("--no-print-metrics", required=False, action="store_true")
719
722
 
720
723
  args, unknown = parser.parse_known_args()
@@ -727,6 +730,7 @@ class Extension:
727
730
  activation_config_path=args.activationconfig,
728
731
  extension_config_path=args.extensionconfig,
729
732
  logger=api_logger,
733
+ secrets_path=args.secrets,
730
734
  local_ingest=args.local_ingest,
731
735
  local_ingest_port=args.local_ingest_port,
732
736
  print_metrics=print_metrics,
@@ -825,7 +829,7 @@ class Extension:
825
829
  self._scheduler.enterabs(next_timestamp, 1, self._timediff_iteration)
826
830
 
827
831
  def _heartbeat_iteration(self):
828
- self._internal_executor.submit(self._heartbeat)
832
+ self._heartbeat_executor.submit(self._heartbeat)
829
833
  next_timestamp = self._get_and_set_next_internal_callback_timestamp("heartbeat", HEARTBEAT_INTERVAL)
830
834
  self._scheduler.enterabs(next_timestamp, 2, self._heartbeat_iteration)
831
835
 
@@ -974,7 +978,9 @@ class Extension:
974
978
  if callback.status.is_error():
975
979
  overall_status.status = callback.status.status
976
980
  messages.append(f"{callback}: {callback.status.message}")
977
-
981
+ continue
982
+ if callback.status.message is not None and callback.status.message != "":
983
+ messages.append(f"{callback}: {callback.status.message}")
978
984
  overall_status.message = "\n".join(messages)
979
985
  return overall_status
980
986