dt-extensions-sdk 1.7.1__py3-none-any.whl → 1.7.3__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.4
2
2
  Name: dt-extensions-sdk
3
- Version: 1.7.1
3
+ Version: 1.7.3
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,4 +1,4 @@
1
- dynatrace_extension/__about__.py,sha256=EKqfngohTaiZ-afjIydbqpFds5q6Y6m0tK_D6Wyw2Dc,110
1
+ dynatrace_extension/__about__.py,sha256=-BvmT9sstUN3n3QmUgym0fwfILxaCrccJsuxxXpn5H0,110
2
2
  dynatrace_extension/__init__.py,sha256=MJNJYCFWLEwPmBLoETWFZddyUCMDgZfKkRycmmGM_w4,806
3
3
  dynatrace_extension/cli/__init__.py,sha256=HCboY_eJPoqjFmoPDsBL8Jk6aNvank8K7JpkVrgwzUM,123
4
4
  dynatrace_extension/cli/main.py,sha256=OTjJ4XHJvvYXj10a7WFFHVNnkyECPg1ClW6Os8piN8k,20168
@@ -17,22 +17,22 @@ dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.tem
17
17
  dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=cS79GVxJB-V-gocu4ZOjmZ54HXJNg89eXdLf89zDHJQ,1249
18
18
  dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
19
19
  dynatrace_extension/sdk/activation.py,sha256=KIoPWMZs3tKiMG8XhCfeNgRlz2vxDKcAASgSACcEfIQ,1456
20
- dynatrace_extension/sdk/callback.py,sha256=woumpcWID09QHGc_rSrukNslVG8Qo0UQEgv8VTB6m7c,6681
21
- dynatrace_extension/sdk/communication.py,sha256=-ccvNz0NYLSDXbqcpnWEqrGKZ4AgTy9XMyu8xnd_qEU,18418
20
+ dynatrace_extension/sdk/callback.py,sha256=NW63fQBh-BeN-zfAeDY94wpSBoBHRsR3VLJCvxJPiaw,6703
21
+ dynatrace_extension/sdk/communication.py,sha256=uTSURmgSHit2N1hHUc3-yKmEBVMHi6hDBrdb1EaCAsE,18419
22
22
  dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
23
- dynatrace_extension/sdk/extension.py,sha256=BAdvQbXl03RGSPw_hYm1y5_9aZZia8fFzIZ0dpoZedo,49681
23
+ dynatrace_extension/sdk/extension.py,sha256=KZYVn5VDW19pfnqe6UnpzRVPYUasW8n8AYSvUYvvOUw,49723
24
24
  dynatrace_extension/sdk/helper.py,sha256=m4gGHtIKYkfANC2MOGdxKUZlmH5tnZO6WTNqll27lyY,6476
25
25
  dynatrace_extension/sdk/metric.py,sha256=-kq7JWpk7UGvcjqafTt-o6k4urwhsGVXmnuQg7Sf9PQ,3622
26
26
  dynatrace_extension/sdk/runtime.py,sha256=7bC4gUJsVSHuL_E7r2EWrne95nm1BjZiMGkyNqA7ZCU,2796
27
27
  dynatrace_extension/sdk/snapshot.py,sha256=LnWVCtCK4NIEV3_kX-ly_LGHpNBSeErtsxCI1PH3L28,7521
28
- dynatrace_extension/sdk/status.py,sha256=TG00-Aenp6D5aFbNxJeQYEyasthAW_vi9-OsutpNC58,8425
28
+ dynatrace_extension/sdk/status.py,sha256=fE0qCGaanV7Ss1144p-dLX9uJMKIb0Rs6A0pLZLPlrI,8599
29
29
  dynatrace_extension/sdk/throttled_logger.py,sha256=JXDiHh8syl8R0gJ-wfxmmBqvGCBMQX4pxPkxscaCsXo,3292
30
30
  dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
32
32
  dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=znF4mvzk5L03CLNozRz8UpK-fMijmSkObDFwlbhwLUg,14656
34
- dt_extensions_sdk-1.7.1.dist-info/METADATA,sha256=5Fq2vrh0GcZ4VTUgAnNnRxgU7-xuhaKb4gY4evc48bs,2721
35
- dt_extensions_sdk-1.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- dt_extensions_sdk-1.7.1.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
37
- dt_extensions_sdk-1.7.1.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
38
- dt_extensions_sdk-1.7.1.dist-info/RECORD,,
34
+ dt_extensions_sdk-1.7.3.dist-info/METADATA,sha256=irv3Te4njH0dWl4Yl-KyNN--ErS4PP7-xrghjwbtoqo,2721
35
+ dt_extensions_sdk-1.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ dt_extensions_sdk-1.7.3.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
37
+ dt_extensions_sdk-1.7.3.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
38
+ dt_extensions_sdk-1.7.3.dist-info/RECORD,,
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
 
6
- __version__ = "1.7.1"
6
+ __version__ = "1.7.3"
@@ -4,6 +4,7 @@
4
4
 
5
5
  import logging
6
6
  import random
7
+ import time
7
8
  from collections.abc import Callable
8
9
  from datetime import datetime, timedelta
9
10
  from timeit import default_timer as timer
@@ -39,6 +40,7 @@ class WrappedCallback:
39
40
  self.duration = 0 # global counter
40
41
  self.duration_interval_total = 0 # counter per interval = 1 min by default
41
42
  self.cluster_time_diff = 0
43
+ self.start_timestamp_monotonic = time.monotonic()
42
44
  self.start_timestamp = self.get_current_time_with_cluster_diff()
43
45
  self.running_in_sim = running_in_sim
44
46
  self.activation_type = activation_type
@@ -147,8 +149,6 @@ class WrappedCallback:
147
149
  """
148
150
  Get the timestamp for the next execution of the callback
149
151
  This is done using execution total, the interval and the start timestamp
150
- :return: datetime
152
+ :return: float
151
153
  """
152
- return (
153
- self.start_timestamp + timedelta(seconds=self.interval.total_seconds() * (self.iterations or 1))
154
- ).timestamp()
154
+ return self.start_timestamp_monotonic + self.interval.total_seconds() * (self.iterations or 1)
@@ -343,7 +343,7 @@ class DebugClient(CommunicationClient):
343
343
  def get_feature_sets(self) -> dict[str, list[str]]:
344
344
  # This is only called from dt-sdk run, where PyYaml is installed because of dt-cli
345
345
  # Do NOT move this to the top of the file
346
- import yaml # type: ignore
346
+ import yaml # noqa: PLC0415
347
347
 
348
348
  # Grab the feature sets from the extension.yaml file
349
349
  extension_yaml = yaml.safe_load(self.extension_config)
@@ -28,10 +28,11 @@ from .snapshot import Snapshot
28
28
  from .status import EndpointStatuses, EndpointStatusesMap, IgnoreStatus, Status, StatusValue
29
29
  from .throttled_logger import StrictThrottledHandler, ThrottledHandler
30
30
 
31
- HEARTBEAT_INTERVAL = timedelta(seconds=50)
32
- METRIC_SENDING_INTERVAL = timedelta(seconds=30)
33
- SFM_METRIC_SENDING_INTERVAL = timedelta(seconds=60)
34
- TIME_DIFF_INTERVAL = timedelta(seconds=60)
31
+ # Intervals defined in seconds for internal callbacks
32
+ HEARTBEAT_INTERVAL = 50
33
+ METRIC_SENDING_INTERVAL = 30
34
+ SFM_METRIC_SENDING_INTERVAL = 60
35
+ TIME_DIFF_INTERVAL = 60
35
36
 
36
37
  CALLBACKS_THREAD_POOL_SIZE = 100
37
38
  INTERNAL_THREAD_POOL_SIZE = 20
@@ -180,11 +181,11 @@ class Extension:
180
181
  """
181
182
 
182
183
  _instance: ClassVar = None
183
- schedule_decorators: ClassVar = []
184
+ schedule_decorators: ClassVar[list[tuple[Callable, timedelta | int, tuple | None, ActivationType | None]]] = []
184
185
 
185
186
  def __new__(cls, *args, **kwargs): # noqa: ARG004
186
187
  if Extension._instance is None:
187
- Extension._instance = super(__class__, cls).__new__(cls)
188
+ Extension._instance = super().__new__(cls)
188
189
  return Extension._instance
189
190
 
190
191
  def __init__(self, name: str = "") -> None:
@@ -240,15 +241,15 @@ class Extension:
240
241
  self._running_callbacks: dict[int, WrappedCallback] = {}
241
242
  self._running_callbacks_lock: Lock = Lock()
242
243
 
243
- self._scheduler = sched.scheduler(time.time, time.sleep)
244
+ self._scheduler = sched.scheduler(time.monotonic, time.sleep)
244
245
 
245
246
  # Timestamps for scheduling of internal callbacks
246
- self._next_internal_callbacks_timestamps: dict[str, datetime] = {
247
- "timediff": datetime.now() + TIME_DIFF_INTERVAL,
248
- "heartbeat": datetime.now() + HEARTBEAT_INTERVAL,
249
- "metrics": datetime.now() + METRIC_SENDING_INTERVAL,
250
- "events": datetime.now() + METRIC_SENDING_INTERVAL,
251
- "sfm_metrics": datetime.now() + SFM_METRIC_SENDING_INTERVAL,
247
+ self._next_internal_callbacks_timestamps: dict[str, float] = {
248
+ "timediff": time.monotonic() + TIME_DIFF_INTERVAL,
249
+ "heartbeat": time.monotonic() + HEARTBEAT_INTERVAL,
250
+ "metrics": time.monotonic() + METRIC_SENDING_INTERVAL,
251
+ "events": time.monotonic() + METRIC_SENDING_INTERVAL,
252
+ "sfm_metrics": time.monotonic() + SFM_METRIC_SENDING_INTERVAL,
252
253
  }
253
254
 
254
255
  # Executors for the callbacks and internal methods
@@ -412,7 +413,7 @@ class Extension:
412
413
  else:
413
414
  self._schedule_callback(callback)
414
415
 
415
- def query(self):
416
+ def query(self) -> Any:
416
417
  """Callback to be executed every minute by default.
417
418
 
418
419
  Optional method that can be implemented by subclasses.
@@ -683,7 +684,7 @@ class Extension:
683
684
  msg = f"Event type must be a DtEventType enum value, got: {value}"
684
685
  raise ValueError(msg)
685
686
  if key == "properties":
686
- for prop_key, prop_val in event[key].items():
687
+ for prop_key, prop_val in value.items():
687
688
  if not isinstance(prop_key, str) or not isinstance(prop_val, str):
688
689
  msg = f'invalid "properties" member: {prop_key}: {prop_val}, required: "str": str'
689
690
  raise ValueError(msg)
@@ -777,7 +778,7 @@ class Extension:
777
778
  parser.add_argument("--secrets", required=False, default="secrets.json")
778
779
  parser.add_argument("--no-print-metrics", required=False, action="store_true")
779
780
 
780
- args, unknown = parser.parse_known_args()
781
+ args, _ = parser.parse_known_args()
781
782
  self._is_fastcheck = args.fastcheck
782
783
  if args.dsid is None:
783
784
  # DEV mode
@@ -1169,10 +1170,10 @@ class Extension:
1169
1170
  def _send_dt_event(self, event: dict[str, str | int | dict[str, str]]):
1170
1171
  self._client.send_dt_event(event)
1171
1172
 
1172
- def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval: timedelta):
1173
+ def _get_and_set_next_internal_callback_timestamp(self, callback_name: str, interval: float) -> float:
1173
1174
  next_timestamp = self._next_internal_callbacks_timestamps[callback_name]
1174
1175
  self._next_internal_callbacks_timestamps[callback_name] += interval
1175
- return next_timestamp.timestamp()
1176
+ return next_timestamp
1176
1177
 
1177
1178
  def get_version(self) -> str:
1178
1179
  """Return the extension version."""
@@ -114,6 +114,9 @@ class EndpointStatus:
114
114
  def __eq__(self, other):
115
115
  return isinstance(other, EndpointStatus) and self.__dict__ == other.__dict__
116
116
 
117
+ def __hash__(self):
118
+ return hash(tuple(sorted(self.__dict__.items())))
119
+
117
120
 
118
121
  class EndpointStatuses:
119
122
  def __init__(self, total_endpoints_number=None) -> None:
@@ -227,33 +230,34 @@ class EndpointStatusesMap:
227
230
  with self._lock:
228
231
  # Summarize all statuses
229
232
  ok_count = 0
230
- nok_count = 0
231
- error_messages = []
232
- has_warning_status = False
233
+ warning_count = 0
234
+ error_count = 0
235
+ messages_to_report = []
233
236
 
234
237
  for ep_record in self._ep_records.values():
235
238
  ep_status = ep_record.ep_status
236
- if ep_status.status.is_warning():
237
- has_warning_status = True
238
239
 
239
- if ep_status.status.is_error():
240
- nok_count += 1
241
- error_messages.append(f"{ep_status.endpoint} - {ep_status.status.value} {ep_status.message}")
240
+ if ep_status.status.is_warning():
241
+ warning_count += 1
242
+ messages_to_report.append(f"{ep_status.endpoint} - {ep_status.status.value} {ep_status.message}")
243
+ elif ep_status.status.is_error():
244
+ error_count += 1
245
+ messages_to_report.append(f"{ep_status.endpoint} - {ep_status.status.value} {ep_status.message}")
242
246
  else:
243
247
  ok_count += 1
244
248
 
249
+ status_msg = f"Endpoints OK: {ok_count} WARNING: {warning_count} ERROR: {error_count}"
250
+
245
251
  # Early return if all OK
246
- if nok_count == 0:
247
- return Status(StatusValue.OK, f"Endpoints OK: {ok_count} NOK: 0")
252
+ if error_count == 0 and warning_count == 0:
253
+ return Status(StatusValue.OK, status_msg)
248
254
 
249
255
  # Build final status if some errors present
250
- common_msg = ", ".join(error_messages)
251
- all_endpoints_faulty = ok_count == 0
256
+ status_msg += f" Unhealthy endpoints: {', '.join(messages_to_report)}"
252
257
 
253
- if all_endpoints_faulty and not has_warning_status:
258
+ if ok_count == 0 and warning_count == 0:
254
259
  status_value = StatusValue.GENERIC_ERROR
255
260
  else:
256
261
  status_value = StatusValue.WARNING
257
262
 
258
- message = f"Endpoints OK: {ok_count} NOK: {nok_count} NOK_reported_errors: {common_msg}"
259
- return Status(status=status_value, message=message)
263
+ return Status(status=status_value, message=status_msg)