dt-extensions-sdk 1.3.0__tar.gz → 1.4.0__tar.gz

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.
Files changed (84) hide show
  1. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/PKG-INFO +4 -3
  2. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/__about__.py +1 -1
  3. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/callback.py +8 -3
  4. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/communication.py +23 -0
  5. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/extension.py +46 -11
  6. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_extension.py +45 -0
  7. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_status.py +66 -3
  8. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/.github/workflows/gh-pages-docs.yml +0 -0
  9. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/.github/workflows/publish.yml +0 -0
  10. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/.gitignore +0 -0
  11. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/LICENSE.txt +0 -0
  12. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/README.md +0 -0
  13. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/dt-sdk-header.png +0 -0
  14. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/dt-sdk-logo.png +0 -0
  15. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/favicon.ico +0 -0
  16. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-01-new-extension.png +0 -0
  17. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-02-type.png +0 -0
  18. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-03-import.png +0 -0
  19. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-04-import-remote.png +0 -0
  20. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-05-activation.png +0 -0
  21. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/_static/img/migrate-06-activation-config.png +0 -0
  22. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/events/event_severity.rst +0 -0
  23. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/events/event_type.rst +0 -0
  24. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/events/index.rst +0 -0
  25. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/extension.rst +0 -0
  26. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/metrics/index.rst +0 -0
  27. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/metrics/metric.rst +0 -0
  28. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/api/metrics/metric_type.rst +0 -0
  29. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/assemble.rst +0 -0
  30. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/build.rst +0 -0
  31. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/create.rst +0 -0
  32. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/gencerts.rst +0 -0
  33. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/help.rst +0 -0
  34. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/run.rst +0 -0
  35. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/sign.rst +0 -0
  36. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/upload.rst +0 -0
  37. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/cli/wheel.rst +0 -0
  38. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/conf.py +0 -0
  39. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/guides/building.rst +0 -0
  40. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/guides/extension_structure.rst +0 -0
  41. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/guides/installation.rst +0 -0
  42. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/guides/migration.rst +0 -0
  43. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/index.rst +0 -0
  44. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/docs/requirements.txt +0 -0
  45. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/__init__.py +0 -0
  46. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/__init__.py +0 -0
  47. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/__init__.py +0 -0
  48. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/create.py +0 -0
  49. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/.gitignore.template +0 -0
  50. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/README.md.template +0 -0
  51. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/activation.json.template +0 -0
  52. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/extension/activationSchema.json.template +0 -0
  53. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/extension/extension.yaml.template +0 -0
  54. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.template +0 -0
  55. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template +0 -0
  56. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/secrets.json.template +0 -0
  57. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/create/extension_template/setup.py.template +0 -0
  58. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/main.py +0 -0
  59. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/cli/schema.py +0 -0
  60. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/__init__.py +0 -0
  61. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/activation.py +0 -0
  62. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/event.py +0 -0
  63. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/helper.py +0 -0
  64. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/metric.py +0 -0
  65. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/runtime.py +0 -0
  66. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/snapshot.py +0 -0
  67. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/vendor/__init__.py +0 -0
  68. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/vendor/mureq/LICENSE +0 -0
  69. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/vendor/mureq/__init__.py +0 -0
  70. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/dynatrace_extension/sdk/vendor/mureq/mureq.py +0 -0
  71. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/pyproject.toml +0 -0
  72. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/__init__.py +0 -0
  73. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/cli/__init__.py +0 -0
  74. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/cli/test_dt_sdk.py +0 -0
  75. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/cli/test_templates.py +0 -0
  76. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/cli/test_types.py +0 -0
  77. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/data/snapshot.json +0 -0
  78. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/__init__.py +0 -0
  79. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_activation.py +0 -0
  80. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_callback.py +0 -0
  81. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_communication.py +0 -0
  82. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_metric.py +0 -0
  83. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_runtime_properties.py +0 -0
  84. {dt_extensions_sdk-1.3.0 → dt_extensions_sdk-1.4.0}/tests/sdk/test_snapshot.py +0 -0
@@ -1,11 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: dt-extensions-sdk
3
- Version: 1.3.0
3
+ Version: 1.4.0
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
7
7
  Author-email: dlopes7 <davidribeirolopes@gmail.com>
8
- License: MIT
8
+ License-Expression: MIT
9
+ License-File: LICENSE.txt
9
10
  Classifier: Development Status :: 4 - Beta
10
11
  Classifier: Programming Language :: Python
11
12
  Classifier: Programming Language :: Python :: 3.10
@@ -3,4 +3,4 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
 
6
- __version__ = "1.3.0"
6
+ __version__ = "1.4.0"
@@ -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
@@ -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"
@@ -214,17 +215,23 @@ class Extension:
214
215
  "timediff": datetime.now() + TIME_DIFF_INTERVAL,
215
216
  "heartbeat": datetime.now() + HEARTBEAT_INTERVAL,
216
217
  "metrics": datetime.now() + METRIC_SENDING_INTERVAL,
218
+ "events": datetime.now() + METRIC_SENDING_INTERVAL,
217
219
  "sfm_metrics": datetime.now() + SFM_METRIC_SENDING_INTERVAL,
218
220
  }
219
221
 
220
222
  # Executors for the callbacks and internal methods
221
223
  self._callbacks_executor = ThreadPoolExecutor(max_workers=CALLBACKS_THREAD_POOL_SIZE)
222
224
  self._internal_executor = ThreadPoolExecutor(max_workers=INTERNAL_THREAD_POOL_SIZE)
225
+ self._heartbeat_executor = ThreadPoolExecutor(max_workers=HEARTBEAT_THREAD_POOL_SIZE)
223
226
 
224
227
  # Extension metrics
225
228
  self._metrics_lock = RLock()
226
229
  self._metrics: List[str] = []
227
230
 
231
+ # Extension logs
232
+ self._logs_lock = RLock()
233
+ self._logs: List[dict] = []
234
+
228
235
  # Self monitoring metrics
229
236
  self._sfm_metrics_lock = Lock()
230
237
  self._callbackSfmReport: Dict[str, WrappedCallback] = {}
@@ -503,6 +510,7 @@ class Extension:
503
510
  properties: Optional[dict] = None,
504
511
  timestamp: Optional[datetime] = None,
505
512
  severity: Union[Severity, str] = Severity.INFO,
513
+ send_immediately: bool = False,
506
514
  ) -> None:
507
515
  """Report an event using log ingest.
508
516
 
@@ -512,6 +520,7 @@ class Extension:
512
520
  properties: A dictionary of extra event properties
513
521
  timestamp: The timestamp of the event, defaults to the current time
514
522
  severity: The severity of the event, defaults to Severity.INFO
523
+ send_immediately: Option to directly schedule log to be sent without batching
515
524
  """
516
525
  if timestamp is None:
517
526
  timestamp = datetime.now(tz=timezone.utc)
@@ -528,7 +537,7 @@ class Extension:
528
537
  **self._metadata,
529
538
  **properties,
530
539
  }
531
- self._send_events(event)
540
+ self._send_events(event, send_immediately=send_immediately)
532
541
 
533
542
  def report_dt_event(
534
543
  self,
@@ -633,7 +642,7 @@ class Extension:
633
642
  raise ValueError(msg)
634
643
  self._send_dt_event(event)
635
644
 
636
- def report_log_event(self, log_event: dict):
645
+ def report_log_event(self, log_event: dict, send_immediately: bool = False):
637
646
  """Report a custom log event using log ingest.
638
647
 
639
648
  Note:
@@ -641,25 +650,28 @@ class Extension:
641
650
 
642
651
  Args:
643
652
  log_event: The log event dictionary.
653
+ send_immediately: Option to directly schedule log to be sent without batching
644
654
  """
645
- self._send_events(log_event)
655
+ self._send_events(log_event, send_immediately=send_immediately)
646
656
 
647
- def report_log_events(self, log_events: List[dict]):
657
+ def report_log_events(self, log_events: List[dict], send_immediately: bool = False):
648
658
  """Report a list of custom log events using log ingest.
649
659
 
650
660
  Args:
651
661
  log_events: The list of log events
662
+ send_immediately: Option to directly schedule log to be sent without batching
652
663
  """
653
- self._send_events(log_events)
664
+ self._send_events(log_events, send_immediately=send_immediately)
654
665
 
655
- def report_log_lines(self, log_lines: List[Union[str, bytes]]):
666
+ def report_log_lines(self, log_lines: List[Union[str, bytes]], send_immediately: bool = False):
656
667
  """Report a list of log lines using log ingest
657
668
 
658
669
  Args:
659
670
  log_lines: The list of log lines
671
+ send_immediately: Option to directly schedule log to be sent without batching
660
672
  """
661
673
  events = [{"content": line} for line in log_lines]
662
- self._send_events(events)
674
+ self._send_events(events, send_immediately=send_immediately)
663
675
 
664
676
  @property
665
677
  def enabled_feature_sets(self) -> dict[str, list[str]]:
@@ -817,6 +829,7 @@ class Extension:
817
829
  for callback in self._scheduled_callbacks_before_run:
818
830
  self._schedule_callback(callback)
819
831
  self._metrics_iteration()
832
+ self._events_iteration()
820
833
  self._sfm_metrics_iteration()
821
834
  self._timediff_iteration()
822
835
  self._scheduler.run()
@@ -827,7 +840,7 @@ class Extension:
827
840
  self._scheduler.enterabs(next_timestamp, 1, self._timediff_iteration)
828
841
 
829
842
  def _heartbeat_iteration(self):
830
- self._internal_executor.submit(self._heartbeat)
843
+ self._heartbeat_executor.submit(self._heartbeat)
831
844
  next_timestamp = self._get_and_set_next_internal_callback_timestamp("heartbeat", HEARTBEAT_INTERVAL)
832
845
  self._scheduler.enterabs(next_timestamp, 2, self._heartbeat_iteration)
833
846
 
@@ -836,6 +849,11 @@ class Extension:
836
849
  next_timestamp = self._get_and_set_next_internal_callback_timestamp("metrics", METRIC_SENDING_INTERVAL)
837
850
  self._scheduler.enterabs(next_timestamp, 1, self._metrics_iteration)
838
851
 
852
+ def _events_iteration(self):
853
+ self._internal_executor.submit(self._send_buffered_events)
854
+ next_timestamp = self._get_and_set_next_internal_callback_timestamp("events", METRIC_SENDING_INTERVAL)
855
+ self._scheduler.enterabs(next_timestamp, 1, self._events_iteration)
856
+
839
857
  def _sfm_metrics_iteration(self):
840
858
  self._internal_executor.submit(self._send_sfm_metrics)
841
859
  next_timestamp = self._get_and_set_next_internal_callback_timestamp("sfm_metrics", SFM_METRIC_SENDING_INTERVAL)
@@ -976,7 +994,9 @@ class Extension:
976
994
  if callback.status.is_error():
977
995
  overall_status.status = callback.status.status
978
996
  messages.append(f"{callback}: {callback.status.message}")
979
-
997
+ continue
998
+ if callback.status.message is not None and callback.status.message != "":
999
+ messages.append(f"{callback}: {callback.status.message}")
980
1000
  overall_status.message = "\n".join(messages)
981
1001
  return overall_status
982
1002
 
@@ -1040,8 +1060,23 @@ class Extension:
1040
1060
  with self._internal_callbacks_results_lock:
1041
1061
  self._internal_callbacks_results[self._send_events.__name__] = Status(StatusValue.GENERIC_ERROR, str(e))
1042
1062
 
1043
- def _send_events(self, events: Union[dict, List[dict]]):
1044
- self._internal_executor.submit(self._send_events_internal, events)
1063
+ def _send_events(self, events: Union[dict, List[dict]], send_immediately: bool = False):
1064
+ if send_immediately:
1065
+ self._internal_executor.submit(self._send_events_internal, events)
1066
+ return
1067
+ with self._logs_lock:
1068
+ if isinstance(events, dict):
1069
+ self._logs.append(events)
1070
+ elif isinstance(events, list):
1071
+ self._logs.extend(events)
1072
+ else:
1073
+ self.logger.error(f"Invalid log format: {events}")
1074
+
1075
+ def _send_buffered_events(self):
1076
+ with self._logs_lock:
1077
+ if len(self._logs) > 0:
1078
+ self._send_events_internal(self._logs)
1079
+ self._logs = []
1045
1080
 
1046
1081
  def _send_dt_event(self, event: dict[str, str | int | dict[str, str]]):
1047
1082
  self._client.send_dt_event(event)
@@ -60,6 +60,50 @@ class TestExtension(unittest.TestCase):
60
60
  with extension._metrics_lock:
61
61
  self.assertEqual(len(extension._metrics), 0)
62
62
 
63
+ def test_add_event(self):
64
+ extension = Extension()
65
+ extension.logger = MagicMock()
66
+ extension._running_in_sim = True
67
+ extension.report_event("my_event1", "my_description")
68
+ extension.report_event("my_event1", "my_description")
69
+ self.assertEqual(len(extension._logs), 2)
70
+ self.assertEqual(extension._logs[0].get("content", "not_good"), "my_event1\nmy_description")
71
+
72
+ def test_send_event_immediately(self):
73
+ extension = Extension()
74
+ extension.logger = MagicMock()
75
+ extension._running_in_sim = True
76
+ extension.report_event("my_event1", "my_description", send_immediately=True)
77
+ extension.report_event("my_event1", "my_description", send_immediately=True)
78
+ self.assertEqual(len(extension._logs), 0)
79
+
80
+ def test_add_log_events(self):
81
+ extension = Extension()
82
+ extension.logger = MagicMock()
83
+ extension._running_in_sim = True
84
+ extension.report_log_events([{"my_event": "hello there!"}, {"my_event": "hello there!"}])
85
+ self.assertEqual(len(extension._logs), 2)
86
+ self.assertEqual(extension._logs[0].get("my_event", "not_good"), "hello there!")
87
+
88
+ def test_send_log_events_immediately(self):
89
+ extension = Extension()
90
+ extension.logger = MagicMock()
91
+ extension._running_in_sim = True
92
+ extension.report_log_events([{"my_event": "hello there!"}, {"my_event": "hello there!"}], send_immediately=True)
93
+ self.assertEqual(len(extension._logs), 0)
94
+
95
+ def test_flush_events(self):
96
+ extension = Extension()
97
+ extension.logger = MagicMock()
98
+ extension._running_in_sim = True
99
+ extension.report_event("my_event1", "my_description")
100
+ extension.report_event("my_event1", "my_description")
101
+ self.assertEqual(len(extension._logs), 2)
102
+ extension._events_iteration()
103
+ time.sleep(0.01)
104
+ with extension._logs_lock:
105
+ self.assertEqual(len(extension._logs), 0)
106
+
63
107
  def test_callback(self):
64
108
  extension = Extension()
65
109
  extension.logger = MagicMock()
@@ -453,6 +497,7 @@ class TestExtension(unittest.TestCase):
453
497
  extension._running_in_sim = True
454
498
  extension._is_fastcheck = False
455
499
  extension._client = MagicMock()
500
+
456
501
  # extension._run_callback = MagicMock()
457
502
 
458
503
  def callback():
@@ -4,7 +4,7 @@ from datetime import timedelta
4
4
  from unittest.mock import MagicMock
5
5
 
6
6
  from dynatrace_extension import Extension
7
- from dynatrace_extension.sdk.communication import DebugClient
7
+ from dynatrace_extension.sdk.communication import DebugClient, MultiStatus
8
8
  from dynatrace_extension.sdk.extension import Status, StatusValue
9
9
 
10
10
 
@@ -52,7 +52,7 @@ class TestStatus(unittest.TestCase):
52
52
  self.assertEqual(status.status, StatusValue.GENERIC_ERROR)
53
53
  self.assertIn("something went wrong", status.message)
54
54
 
55
- def test_mutiple_bad_status(self):
55
+ def test_multiple_bad_status(self):
56
56
  ext = Extension()
57
57
  ext.logger = MagicMock()
58
58
  ext._running_in_sim = True
@@ -70,7 +70,7 @@ class TestStatus(unittest.TestCase):
70
70
  ext.schedule(bad_method_1, timedelta(seconds=1))
71
71
  ext.schedule(bad_method_2, timedelta(seconds=1))
72
72
  ext._scheduler.run(blocking=False)
73
- time.sleep(0.01)
73
+ time.sleep(1)
74
74
 
75
75
  status = ext._build_current_status()
76
76
  self.assertEqual(status.status, StatusValue.GENERIC_ERROR)
@@ -93,3 +93,66 @@ class TestStatus(unittest.TestCase):
93
93
 
94
94
  self.assertTrue(extension._scheduled_callbacks[1].status.is_error())
95
95
  self.assertIn("longer than the interval", extension._scheduled_callbacks[1].status.message)
96
+
97
+ def test_direct_status_return(self):
98
+ ext = Extension()
99
+ ext.logger = MagicMock()
100
+ ext._running_in_sim = True
101
+ ext._client = DebugClient("", "", MagicMock())
102
+ ext._is_fastcheck = False
103
+
104
+ def callback():
105
+ return Status(StatusValue.OK, "foo1")
106
+
107
+ ext.schedule(callback, timedelta(seconds=1))
108
+ ext._scheduler.run(blocking=False)
109
+ time.sleep(0.01)
110
+
111
+ status = ext._build_current_status()
112
+ self.assertEqual(status.status, StatusValue.OK)
113
+ self.assertIn("foo1", status.message)
114
+
115
+ def test_direct_statuses_return(self):
116
+
117
+ def callback():
118
+ return Status(StatusValue.OK, "foo1")
119
+
120
+ def custom_query():
121
+ return Status(StatusValue.EMPTY, "foo2")
122
+
123
+ ext = Extension()
124
+ ext.logger = MagicMock()
125
+ ext._running_in_sim = True
126
+ ext._client = DebugClient("", "", MagicMock())
127
+ ext._is_fastcheck = False
128
+
129
+ ext.schedule(callback, timedelta(seconds=1))
130
+ ext.schedule(custom_query, timedelta(seconds=1))
131
+ ext._scheduler.run(blocking=False)
132
+ time.sleep(0.01)
133
+
134
+ status = ext._build_current_status()
135
+ self.assertEqual(status.status, StatusValue.OK)
136
+ self.assertIn("foo1", status.message)
137
+ self.assertIn("foo2", status.message)
138
+
139
+ def test_multistatus(self):
140
+ ext = Extension()
141
+ ext.logger = MagicMock()
142
+ ext._running_in_sim = True
143
+ ext._client = DebugClient("", "", MagicMock())
144
+ ext._is_fastcheck = False
145
+
146
+ def callback():
147
+ ret = MultiStatus()
148
+ ret.add_status(StatusValue.OK, "foo1")
149
+ ret.add_status(StatusValue.UNKNOWN_ERROR, "foo2")
150
+ return ret
151
+
152
+ ext.schedule(callback, timedelta(seconds=1))
153
+ ext._scheduler.run(blocking=False)
154
+ time.sleep(1)
155
+
156
+ status = ext._build_current_status()
157
+ self.assertEqual(status.status, StatusValue.UNKNOWN_ERROR)
158
+ self.assertIn("foo1", status.message)