dt-extensions-sdk 1.1.14__py3-none-any.whl → 1.1.16__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.1
2
2
  Name: dt-extensions-sdk
3
- Version: 1.1.14
3
+ Version: 1.1.16
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=g3hhG7B9t83hKfX5DAKY1soVWx-Ut524cAYTdATNX3w,115
1
+ dynatrace_extension/__about__.py,sha256=5SE6zz8YNj3cZnRJ6gzkBsJ3yLzLCTp4BjtDtYA4pTI,115
2
2
  dynatrace_extension/__init__.py,sha256=XYHyWducrLWengm6jcCZMYAHzaQwQfoJKzKT4QvhTxE,779
3
3
  dynatrace_extension/cli/__init__.py,sha256=eg2YQkeboIfJ_hcUGp1WFEvT-moa2qGGN-L9RjTbxCM,128
4
4
  dynatrace_extension/cli/main.py,sha256=3KLvB2HZZe-tZKyB3n236N75ja5hGEMwxV-UaGyXxvE,17393
@@ -8,17 +8,17 @@ dynatrace_extension/cli/create/create.py,sha256=Wkkp4AuRID18k9EUJdVZ3GP2US35OF5h
8
8
  dynatrace_extension/cli/create/extension_template/.gitignore.template,sha256=k8e-DOj0fYnXYjlLOCHoN5GBgtkNXHAYEfEWr3_YZV0,3238
9
9
  dynatrace_extension/cli/create/extension_template/README.md.template,sha256=VUkh1AGyHJ_7hmxaGGhwiHys82VR16ii92_WvuhKHTY,670
10
10
  dynatrace_extension/cli/create/extension_template/activation.json.template,sha256=8sO0WcqhoQouRHfy_V5KhJXdNYbBxc73VOcxLQKylzs,272
11
- dynatrace_extension/cli/create/extension_template/setup.py.template,sha256=yTWMPaJZzO2FterwJv1YKO0v75VKmCQRiwzpMwa9S1A,405
11
+ dynatrace_extension/cli/create/extension_template/setup.py.template,sha256=i38dH4uIN0XJ8v5Ag7vJOrb6WDeXJpbylEIeVavln_I,874
12
12
  dynatrace_extension/cli/create/extension_template/extension/activationSchema.json.template,sha256=ufVIxeAkrrrhEnk-TPvYgdEKpAbiWXNvHS5t9iZoED0,3023
13
- dynatrace_extension/cli/create/extension_template/extension/extension.yaml.template,sha256=db7N-VGVqMFn1OXW1em-lRqf_-FKYMMfEyQCg9cghUs,313
13
+ dynatrace_extension/cli/create/extension_template/extension/extension.yaml.template,sha256=hdj4kShGE2u7A7bcVKypXTR3GExGm690EHwsNIBRrC4,316
14
14
  dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.template,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=UTwxpOkHFZTShEztuZwbAz8TjmXmRGAGmr_fwE4u0ro,1301
16
16
  dynatrace_extension/sdk/__init__.py,sha256=sh7MNjmyR0vt-ugkqEHXVqwTNLExfexS0CTK-QnULcw,89
17
17
  dynatrace_extension/sdk/activation.py,sha256=s1ZToshWNptfmgu5NsZCI_WMsNM3-O8CSzzoauo62Uk,1523
18
18
  dynatrace_extension/sdk/callback.py,sha256=61o2ui2gXO__sDuY-zGaNTGmkk1tdn-5fQs6Ik7EodE,5962
19
- dynatrace_extension/sdk/communication.py,sha256=qHkt-gCS7C2twOqqsYQFbz-7dprzy5bF9gRGgz87iu4,17724
19
+ dynatrace_extension/sdk/communication.py,sha256=zKiUP0oKPniLzcyJIHAoWl6jj_0DjZr4gzZGB9O794U,19239
20
20
  dynatrace_extension/sdk/event.py,sha256=oyvcm8uTle-A1jKgXIsFwAvl-Ta2RKbFHVZv3sxTPmo,407
21
- dynatrace_extension/sdk/extension.py,sha256=JooYJnL_G7CAjW9SLIHdN5QKdMqRzwOmrpuvrIB4V7w,41205
21
+ dynatrace_extension/sdk/extension.py,sha256=6FSayHmh24SRrFZ5TOEe9pVxWDWN3LhU3eSu3INhDoU,41349
22
22
  dynatrace_extension/sdk/helper.py,sha256=TyL6aSIN6lhoH2yHZw8NbhtixdvlyV5q6fDh5NPODdc,6753
23
23
  dynatrace_extension/sdk/metric.py,sha256=QwrfrmFdj-IddZxAZlZrch62TWKDtzrDU88FwBIv30E,3808
24
24
  dynatrace_extension/sdk/runtime.py,sha256=JgP_qVomatVw85ZuAjbR0S-rzrK5U9JwSZXwfQDKNA4,2869
@@ -26,8 +26,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
26
26
  dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=_rKBhB1pJYXjxjBCdOSIkv_slxWbhEXVBXXZs1MdpQQ,684
27
27
  dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=gKzGiPmZ2g74Nb8K_6bu0f2coWZHZiZ2aXGzG2Pimlg,15102
29
- dt_extensions_sdk-1.1.14.dist-info/METADATA,sha256=FG3KBEQ_gvYG4PsR9bWXGz3LMbf-jfpz594aU8jdwV4,2794
30
- dt_extensions_sdk-1.1.14.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
31
- dt_extensions_sdk-1.1.14.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
32
- dt_extensions_sdk-1.1.14.dist-info/licenses/LICENSE.txt,sha256=k7kok_OTpJ5sfb5ANni8wu-Q1lXw8OQjNZXdrTGhFKc,1087
33
- dt_extensions_sdk-1.1.14.dist-info/RECORD,,
29
+ dt_extensions_sdk-1.1.16.dist-info/METADATA,sha256=O_O8uBSihvwD_GTv5PMWe2exjNtiM7uE-Kt4iOZsdEM,2794
30
+ dt_extensions_sdk-1.1.16.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
31
+ dt_extensions_sdk-1.1.16.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
32
+ dt_extensions_sdk-1.1.16.dist-info/licenses/LICENSE.txt,sha256=k7kok_OTpJ5sfb5ANni8wu-Q1lXw8OQjNZXdrTGhFKc,1087
33
+ dt_extensions_sdk-1.1.16.dist-info/RECORD,,
@@ -2,4 +2,4 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
- __version__ = "1.1.14"
5
+ __version__ = "1.1.16"
@@ -1,6 +1,6 @@
1
1
  name: %extension-prefix%%extension-name%
2
2
  version: 0.0.1
3
- minDynatraceVersion: "1.253"
3
+ minDynatraceVersion: "1.285"
4
4
  author:
5
5
  name: "Dynatrace"
6
6
 
@@ -8,10 +8,10 @@ python:
8
8
  runtime:
9
9
  module: %extension_name%
10
10
  version:
11
- min: "3.9"
11
+ min: "3.10"
12
12
 
13
13
  activation:
14
14
  remote:
15
15
  path: activationSchema.json
16
16
  local:
17
- path: activationSchema.json
17
+ path: activationSchema.json
@@ -1,7 +1,23 @@
1
+ from pathlib import Path
1
2
  from setuptools import setup, find_packages
2
3
 
4
+
5
+ def find_version() -> str:
6
+ version = "0.0.1"
7
+ extension_yaml_path = Path(__file__).parent / "extension" / "extension.yaml"
8
+ try:
9
+ with open(extension_yaml_path, encoding="utf-8") as f:
10
+ for line in f:
11
+ if line.startswith("version"):
12
+ version = line.split(" ")[-1].strip("\"")
13
+ break
14
+ except Exception:
15
+ pass
16
+ return version
17
+
18
+
3
19
  setup(name="%extension_name%",
4
- version="0.0.1",
20
+ version=find_version(),
5
21
  description="%Extension_Name% python EF2 extension",
6
22
  author="Dynatrace",
7
23
  packages=find_packages(),
@@ -10,11 +10,12 @@ import random
10
10
  import sys
11
11
  import time
12
12
  from abc import ABC, abstractmethod
13
+ from collections import deque
13
14
  from dataclasses import dataclass
14
15
  from enum import Enum
15
16
  from itertools import islice
16
17
  from pathlib import Path
17
- from typing import Any, Iterable, List, TypeVar
18
+ from typing import Any, Dict, Iterable, List, TypeVar, Union
18
19
 
19
20
  from .vendor.mureq.mureq import HTTPException, Response, request
20
21
 
@@ -22,6 +23,8 @@ CONTENT_TYPE_JSON = "application/json;charset=utf-8"
22
23
  CONTENT_TYPE_PLAIN = "text/plain;charset=utf-8"
23
24
  COUNT_METRIC_ITEMS_DICT = TypeVar("COUNT_METRIC_ITEMS_DICT", str, List[str])
24
25
  MAX_MINT_LINES_PER_REQUEST = 1000
26
+ MAX_LOG_EVENTS_PER_REQUEST = 50_000
27
+ MAX_LOG_REQUEST_SIZE = 5_000_000
25
28
  HTTP_BAD_REQUEST = 400
26
29
 
27
30
 
@@ -94,7 +97,7 @@ class CommunicationClient(ABC):
94
97
  pass
95
98
 
96
99
  @abstractmethod
97
- def send_events(self, event: dict | list[dict], eec_enrichment: bool) -> dict | None:
100
+ def send_events(self, event: dict | list[dict], eec_enrichment: bool) -> list[Union[dict | None]]:
98
101
  pass
99
102
 
100
103
  @abstractmethod
@@ -283,19 +286,26 @@ class HttpClient(CommunicationClient):
283
286
  responses.append(mint_response)
284
287
  return responses
285
288
 
286
- def send_events(self, events: dict | list[dict], eec_enrichment: bool = True) -> dict | None:
289
+ def send_events(self, events: dict | list[dict], eec_enrichment: bool = True) -> list[dict | None]:
287
290
  self.logger.debug(f"Sending log events: {events}")
288
- event_data = json.dumps(events).encode("utf-8")
289
- try:
290
- # EEC returns empty body on success
291
- return self._make_request(
292
- self._events_url,
293
- "POST",
294
- event_data,
295
- extra_headers={"Content-Type": CONTENT_TYPE_JSON, "eec-enrichment": str(eec_enrichment).lower()},
296
- ).json()
297
- except json.JSONDecodeError:
298
- return None
291
+
292
+ responses = []
293
+ batches = divide_logs_into_batches([events] if isinstance(events, dict) else events)
294
+
295
+ for batch in batches:
296
+ try:
297
+ encoded_batch = json.dumps(batch).encode("utf-8")
298
+ eec_response = self._make_request(
299
+ self._events_url,
300
+ "POST",
301
+ encoded_batch,
302
+ extra_headers={"Content-Type": CONTENT_TYPE_JSON, "eec-enrichment": str(eec_enrichment).lower()},
303
+ ).json()
304
+ responses.append(eec_response)
305
+ except json.JSONDecodeError:
306
+ responses.append(None)
307
+
308
+ return responses
299
309
 
300
310
  def send_sfm_metrics(self, mint_lines: list[str]) -> MintResponse:
301
311
  mint_data = "\n".join(mint_lines).encode("utf-8")
@@ -448,6 +458,46 @@ def divide_into_chunks(iterable: Iterable, chunk_size: int) -> Iterable:
448
458
  return
449
459
  yield subset
450
460
 
461
+ def divide_logs_into_batches(logs: list[dict]):
462
+ """
463
+ Yield successive batches from a list of log events, according to sizing limitations
464
+ imposed by the EEC: 5 MB payload, 50,000 events
465
+
466
+ :param logs: The list of log events
467
+ """
468
+ events_left = len(logs)
469
+ events = deque(logs)
470
+
471
+ batch = []
472
+ batch_size = 0
473
+ batch_items = 0
474
+
475
+ while events_left > 0:
476
+ if batch_items == MAX_LOG_EVENTS_PER_REQUEST:
477
+ yield batch
478
+ batch = []
479
+ batch_size = 0
480
+ batch_items = 0
481
+ continue
482
+
483
+ event = events.popleft()
484
+ events_left -= 1
485
+
486
+ if event is not None:
487
+ event = json.dumps(event).encode("utf-8")
488
+ event_size = len(event)
489
+
490
+ if batch_size + event_size >= MAX_LOG_REQUEST_SIZE:
491
+ yield batch
492
+ batch = [event]
493
+ batch_size = event_size
494
+ batch_items = 1
495
+ else:
496
+ batch.append(event)
497
+ batch_size += event_size
498
+ batch_items += 1
499
+ else:
500
+ yield batch
451
501
 
452
502
  @dataclass
453
503
  class MintResponse:
@@ -9,6 +9,7 @@ import sys
9
9
  import threading
10
10
  import time
11
11
  from argparse import ArgumentParser
12
+ from collections import deque
12
13
  from concurrent.futures import ThreadPoolExecutor
13
14
  from datetime import datetime, timedelta, timezone
14
15
  from enum import Enum
@@ -79,14 +80,15 @@ class DtEventType(str, Enum):
79
80
  https://docs.dynatrace.com/docs/dynatrace-api/environment-api/events-v2/post-event
80
81
  """
81
82
 
83
+ AVAILABILITY_EVENT = "AVAILABILITY_EVENT"
82
84
  CUSTOM_INFO = "CUSTOM_INFO"
83
85
  CUSTOM_ALERT = "CUSTOM_ALERT"
84
86
  CUSTOM_ANNOTATION = "CUSTOM_ANNOTATION"
85
87
  CUSTOM_CONFIGURATION = "CUSTOM_CONFIGURATION"
86
88
  CUSTOM_DEPLOYMENT = "CUSTOM_DEPLOYMENT"
87
- MARKED_FOR_TERMINATION = "MARKED_FOR_TERMINATION"
88
89
  ERROR_EVENT = "ERROR_EVENT"
89
- AVAILABILITY_EVENT = "AVAILABILITY_EVENT"
90
+ MARKED_FOR_TERMINATION = "MARKED_FOR_TERMINATION"
91
+ PERFORMANCE_EVENT = "PERFORMANCE_EVENT"
90
92
  RESOURCE_CONTENTION_EVENT = "RESOURCE_CONTENTION_EVENT"
91
93
 
92
94
 
@@ -995,14 +997,16 @@ class Extension:
995
997
  self._metrics.extend(lines)
996
998
 
997
999
  def _send_events_internal(self, events: Union[dict, List[dict]]):
998
- response = self._client.send_events(events, self.log_event_enrichment)
999
- with self._internal_callbacks_results_lock:
1000
- self._internal_callbacks_results[self._send_events.__name__] = Status(StatusValue.OK)
1001
- if not response or "error" not in response or "message" not in response["error"]:
1002
- return
1003
- self._internal_callbacks_results[self._send_events.__name__] = Status(
1004
- StatusValue.GENERIC_ERROR, response["error"]["message"]
1005
- )
1000
+ responses = self._client.send_events(events, self.log_event_enrichment)
1001
+
1002
+ for response in responses:
1003
+ with self._internal_callbacks_results_lock:
1004
+ self._internal_callbacks_results[self._send_events.__name__] = Status(StatusValue.OK)
1005
+ if not response or "error" not in response or "message" not in response["error"]:
1006
+ return
1007
+ self._internal_callbacks_results[self._send_events.__name__] = Status(
1008
+ StatusValue.GENERIC_ERROR, response["error"]["message"]
1009
+ )
1006
1010
 
1007
1011
  def _send_events(self, events: Union[dict, List[dict]]):
1008
1012
  self._internal_executor.submit(self._send_events_internal, events)
@@ -1011,9 +1015,8 @@ class Extension:
1011
1015
  self._client.send_dt_event(event)
1012
1016
 
1013
1017
  def get_version(self) -> str:
1014
- """Return the version of extensions sdk library."""
1015
-
1016
- return __version__
1018
+ """Return the extension version."""
1019
+ return self.activation_config.version
1017
1020
 
1018
1021
  @property
1019
1022
  def techrule(self) -> str: