From 8d67371887bcf0273596fe09b2070faf9e3b0809 Mon Sep 17 00:00:00 2001 From: Brayden Neale Date: Mon, 10 Mar 2025 15:29:22 +1100 Subject: [PATCH 1/3] Include extra useful entry properties when parsing snapshot --- dynatrace_extension/sdk/snapshot.py | 33 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/dynatrace_extension/sdk/snapshot.py b/dynatrace_extension/sdk/snapshot.py index c206ce1..a646278 100644 --- a/dynatrace_extension/sdk/snapshot.py +++ b/dynatrace_extension/sdk/snapshot.py @@ -14,12 +14,14 @@ class EntryProperties: technologies: list[str] pg_technologies: list[str] + extra_properties: dict[str,str] @staticmethod - def from_json(json_data: dict) -> EntryProperties: + def from_json(json_data: dict[str,str]) -> EntryProperties: technologies = json_data.get("Technologies", "").split(",") pg_technologies = json_data.get("pgTechnologies", "").split(",") - return EntryProperties(technologies, pg_technologies) + extra_properties = {k: v for k, v in json_data.items() if k not in ["Technologies", "pgTechnologies"]} + return EntryProperties(technologies, pg_technologies, extra_properties) @dataclass @@ -111,20 +113,29 @@ def from_json(json_data: dict) -> Entry: processes = [Process.from_json(p) for p in json_data.get("processes", [])] # The structure here was never thought out, so we have to check for both keys and merge them into one object - properties_list = json_data.get("properties", []) - technologies = [p for p in properties_list if "Technologies" in p] - if technologies: - technologies = technologies[0]["Technologies"].split(",") - - pg_technologies = [p for p in properties_list if "pgTechnologies" in p] - if pg_technologies: - pg_technologies = pg_technologies[0]["pgTechnologies"].split(",") - properties = EntryProperties(technologies or [], pg_technologies or []) + properties_list: list[dict[str,str]] = json_data.get("properties", []) + technologies = [] + pg_technologies = [] + # There may be other useful properties included such as: mssql_instance_name. + extra_properties = {} + + for prop in properties_list: + for key, value in prop.items(): + match key: + case "Technologies": + technologies.extend(value.split(",")) + case "pgTechnologies": + pg_technologies.extend(value.split(",")) + case _: + extra_properties[key] = value + + properties = EntryProperties(technologies, pg_technologies, extra_properties) return Entry(group_id, node_id, group_instance_id, process_type, group_name, processes, properties) @dataclass + class Snapshot: host_id: str entries: list[Entry] From 070c475ccf821f00372b48a80fba360975c0599b Mon Sep 17 00:00:00 2001 From: dlopes7 Date: Mon, 10 Mar 2025 18:55:23 -0500 Subject: [PATCH 2/3] Set target version to python 3.10 Run lint and format --- dynatrace_extension/cli/create/create.py | 4 +- dynatrace_extension/cli/main.py | 25 ++++---- dynatrace_extension/sdk/activation.py | 3 +- dynatrace_extension/sdk/callback.py | 8 +-- dynatrace_extension/sdk/communication.py | 7 +-- dynatrace_extension/sdk/extension.py | 75 ++++++++++++------------ dynatrace_extension/sdk/helper.py | 44 +++++++------- dynatrace_extension/sdk/metric.py | 19 +++--- dynatrace_extension/sdk/runtime.py | 4 +- dynatrace_extension/sdk/snapshot.py | 11 ++-- pyproject.toml | 4 +- tests/sdk/test_communication.py | 3 - tests/sdk/test_extension.py | 1 - tests/sdk/test_runtime_properties.py | 4 +- tests/sdk/test_status.py | 1 - 15 files changed, 101 insertions(+), 112 deletions(-) diff --git a/dynatrace_extension/cli/create/create.py b/dynatrace_extension/cli/create/create.py index 6d7c6ea..0f36ffa 100644 --- a/dynatrace_extension/cli/create/create.py +++ b/dynatrace_extension/cli/create/create.py @@ -2,7 +2,7 @@ import re import shutil from pathlib import Path -from typing import Final, List, NamedTuple, Tuple +from typing import Final, NamedTuple output_mode_folder: Final = 0o755 output_mode_file: Final = 0o644 @@ -14,7 +14,7 @@ class ReplaceString(NamedTuple): replace: str -def replace_placeholders(file: Path, replaces: List[Tuple[str, str]]): +def replace_placeholders(file: Path, replaces: list[tuple[str, str]]): with open(file) as f: contents = f.read() for replace in replaces: diff --git a/dynatrace_extension/cli/main.py b/dynatrace_extension/cli/main.py index 8d586bf..91a2c5c 100644 --- a/dynatrace_extension/cli/main.py +++ b/dynatrace_extension/cli/main.py @@ -4,7 +4,6 @@ import subprocess import sys from pathlib import Path -from typing import List, Optional import typer from dtcli.server_api import upload as dt_cli_upload # type: ignore @@ -91,17 +90,17 @@ def build( "-k", help="Path to the dev fused key-certificate", ), - target_directory: Optional[Path] = typer.Option(None, "--target-directory", "-t"), - extra_platforms: Optional[list[str]] = typer.Option( + target_directory: Path | None = typer.Option(None, "--target-directory", "-t"), + extra_platforms: list[str] | None = typer.Option( None, "--extra-platform", "-e", help="Download wheels for an extra platform" ), - extra_index_url: Optional[str] = typer.Option( + extra_index_url: str | None = typer.Option( None, "--extra-index-url", "-i", help="Extra index url to use when downloading dependencies" ), - find_links: Optional[str] = typer.Option( + find_links: str | None = typer.Option( None, "--find-links", "-f", help="Extra index url to use when downloading dependencies" ), - only_extra_platforms: Optional[bool] = typer.Option( + only_extra_platforms: bool | None = typer.Option( False, "--only-extra-platforms", "-o", @@ -163,7 +162,7 @@ def assemble( # Checks that the module name is valid and exists in the filesystem module_folder = Path(extension_dir) / extension_yaml.python.runtime.module - src_module_folder = Path('src') / module_folder + src_module_folder = Path("src") / module_folder if not module_folder.exists() and not src_module_folder.exists(): msg = f"Extension module folder {module_folder} not found" raise FileNotFoundError(msg) @@ -188,16 +187,16 @@ def assemble( @app.command(help="Downloads the dependencies of the extension to the lib folder") def wheel( extension_dir: Path = typer.Argument(".", help="Path to the python extension"), - extra_platforms: Optional[list[str]] = typer.Option( + extra_platforms: list[str] | None = typer.Option( None, "--extra-platform", "-e", help="Download wheels for an extra platform" ), - extra_index_url: Optional[str] = typer.Option( + extra_index_url: str | None = typer.Option( None, "--extra-index-url", "-i", help="Extra index url to use when downloading dependencies" ), - find_links: Optional[str] = typer.Option( + find_links: str | None = typer.Option( None, "--find-links", "-f", help="Extra index url to use when downloading dependencies" ), - only_extra_platforms: Optional[bool] = typer.Option( + only_extra_platforms: bool | None = typer.Option( False, "--only-extra-platforms", "-o", @@ -476,9 +475,7 @@ def ruff_init(extension_dir: Path = typer.Argument(".", help="Path to the python console.print(f"Added ruff.toml to {extension_dir.resolve()}", style="bold green") -def run_process( - command: List[str], cwd: Optional[Path] = None, env: Optional[dict] = None, print_message: Optional[str] = None -): +def run_process(command: list[str], cwd: Path | None = None, env: dict | None = None, print_message: str | None = None): friendly_command = " ".join(command) if print_message is not None: console.print(print_message, style="cyan") diff --git a/dynatrace_extension/sdk/activation.py b/dynatrace_extension/sdk/activation.py index c238801..1fa352a 100644 --- a/dynatrace_extension/sdk/activation.py +++ b/dynatrace_extension/sdk/activation.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: MIT from enum import Enum -from typing import List class ActivationType(Enum): @@ -17,7 +16,7 @@ def __init__(self, activation_context_json: dict): self.version: str = self._activation_context_json.get("version", "") self.enabled: bool = self._activation_context_json.get("enabled", True) self.description: str = self._activation_context_json.get("description", "") - self.feature_sets: List[str] = self._activation_context_json.get("featureSets", []) + self.feature_sets: list[str] = self._activation_context_json.get("featureSets", []) self.type: ActivationType = ActivationType.REMOTE if self.remote else ActivationType.LOCAL super().__init__() diff --git a/dynatrace_extension/sdk/callback.py b/dynatrace_extension/sdk/callback.py index d2ef487..1e026c4 100644 --- a/dynatrace_extension/sdk/callback.py +++ b/dynatrace_extension/sdk/callback.py @@ -4,9 +4,9 @@ import logging import random +from collections.abc import Callable from datetime import datetime, timedelta from timeit import default_timer as timer -from typing import Callable, Dict, Optional, Tuple from .activation import ActivationType from .communication import MultiStatus, Status, StatusValue @@ -18,10 +18,10 @@ def __init__( interval: timedelta, callback: Callable, logger: logging.Logger, - args: Optional[Tuple] = None, - kwargs: Optional[Dict] = None, + args: tuple | None = None, + kwargs: dict | None = None, running_in_sim=False, - activation_type: Optional[ActivationType] = None, + activation_type: ActivationType | None = None, ): self.callback: Callable = callback if args is None: diff --git a/dynatrace_extension/sdk/communication.py b/dynatrace_extension/sdk/communication.py index 17571dd..ed8e347 100644 --- a/dynatrace_extension/sdk/communication.py +++ b/dynatrace_extension/sdk/communication.py @@ -8,16 +8,17 @@ import logging import sys from abc import ABC, abstractmethod +from collections.abc import Generator, Sequence from dataclasses import dataclass from enum import Enum from pathlib import Path -from typing import Any, Generator, List, Sequence, TypeVar +from typing import Any, TypeVar from .vendor.mureq.mureq import HTTPException, Response, request CONTENT_TYPE_JSON = "application/json;charset=utf-8" CONTENT_TYPE_PLAIN = "text/plain;charset=utf-8" -COUNT_METRIC_ITEMS_DICT = TypeVar("COUNT_METRIC_ITEMS_DICT", str, List[str]) +COUNT_METRIC_ITEMS_DICT = TypeVar("COUNT_METRIC_ITEMS_DICT", str, list[str]) # TODO - I believe these can be adjusted via RuntimeConfig, they can't be constants MAX_MINT_LINES_PER_REQUEST = 1000 @@ -60,7 +61,6 @@ def is_error(self) -> bool: class MultiStatus: - def __init__(self): self.statuses = [] @@ -352,7 +352,6 @@ def __init__( local_ingest_port: int = 14499, print_metrics: bool = True, ): - self.secrets = {} if secrets_path and Path(secrets_path).exists(): with open(secrets_path) as f: diff --git a/dynatrace_extension/sdk/extension.py b/dynatrace_extension/sdk/extension.py index 37607a1..b75d405 100644 --- a/dynatrace_extension/sdk/extension.py +++ b/dynatrace_extension/sdk/extension.py @@ -9,13 +9,14 @@ import threading import time from argparse import ArgumentParser +from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta, timezone from enum import Enum from itertools import chain from pathlib import Path from threading import Lock, RLock, active_count -from typing import Any, Callable, ClassVar, Dict, List, NamedTuple, Optional, Union +from typing import Any, ClassVar, NamedTuple from .activation import ActivationConfig, ActivationType from .callback import WrappedCallback @@ -99,7 +100,7 @@ class CountMetricRegistrationEntry(NamedTuple): dimensions_list: list[str] @staticmethod - def make_list(metric_key: str, dimensions_list: List[str]): + def make_list(metric_key: str, dimensions_list: list[str]): """Build an entry that uses defined list of dimensions for aggregation. Args: @@ -135,7 +136,7 @@ def registration_items_dict(self): return result -def _add_sfm_metric(metric: Metric, sfm_metrics: Optional[List[Metric]] = None): +def _add_sfm_metric(metric: Metric, sfm_metrics: list[Metric] | None = None): if sfm_metrics is None: sfm_metrics = [] metric.validate() @@ -194,24 +195,24 @@ def __init__(self, name: str = "") -> None: self._cluster_time_diff: int = 0 # Optional callback to be invoked during the fastcheck - self._fast_check_callback: Optional[Callable[[ActivationConfig, str], Status]] = None + self._fast_check_callback: Callable[[ActivationConfig, str], Status] | None = None # List of all scheduled callbacks we must run - self._scheduled_callbacks: List[WrappedCallback] = [] - self._scheduled_callbacks_before_run: List[WrappedCallback] = [] + self._scheduled_callbacks: list[WrappedCallback] = [] + self._scheduled_callbacks_before_run: list[WrappedCallback] = [] # Internal callbacks results, used to report statuses - self._internal_callbacks_results: Dict[str, Status] = {} + self._internal_callbacks_results: dict[str, Status] = {} self._internal_callbacks_results_lock: Lock = Lock() # Running callbacks, used to get the callback info when reporting metrics - self._running_callbacks: Dict[int, WrappedCallback] = {} + self._running_callbacks: dict[int, WrappedCallback] = {} self._running_callbacks_lock: Lock = Lock() self._scheduler = sched.scheduler(time.time, time.sleep) # Timestamps for scheduling of internal callbacks - self._next_internal_callbacks_timestamps: Dict[str, datetime] = { + self._next_internal_callbacks_timestamps: dict[str, datetime] = { "timediff": datetime.now() + TIME_DIFF_INTERVAL, "heartbeat": datetime.now() + HEARTBEAT_INTERVAL, "metrics": datetime.now() + METRIC_SENDING_INTERVAL, @@ -226,15 +227,15 @@ def __init__(self, name: str = "") -> None: # Extension metrics self._metrics_lock = RLock() - self._metrics: List[str] = [] + self._metrics: list[str] = [] # Extension logs self._logs_lock = RLock() - self._logs: List[dict] = [] + self._logs: list[dict] = [] # Self monitoring metrics self._sfm_metrics_lock = Lock() - self._callbackSfmReport: Dict[str, WrappedCallback] = {} + self._callbackSfmReport: dict[str, WrappedCallback] = {} # Count metric delta signals self._delta_signal_buffer: set[str] = set() @@ -342,9 +343,9 @@ def _schedule_callback(self, callback: WrappedCallback): def schedule( self, callback: Callable, - interval: Union[timedelta, int], - args: Optional[tuple] = None, - activation_type: Optional[ActivationType] = None, + interval: timedelta | int, + args: tuple | None = None, + activation_type: ActivationType | None = None, ) -> None: """Schedule a method to be executed periodically. @@ -455,11 +456,11 @@ def _send_count_delta_signal(self, metric_keys: set[str], force: bool = True) -> def report_metric( self, key: str, - value: Union[float, str, int, SummaryStat], - dimensions: Optional[Dict[str, str]] = None, - device_address: Optional[str] = None, - techrule: Optional[str] = None, - timestamp: Optional[datetime] = None, + value: float | str | int | SummaryStat, + dimensions: dict[str, str] | None = None, + device_address: str | None = None, + techrule: str | None = None, + timestamp: datetime | None = None, metric_type: MetricType = MetricType.GAUGE, ) -> None: """Report a metric. @@ -498,7 +499,7 @@ def report_metric( metric = Metric(key=key, value=value, dimensions=dimensions, metric_type=metric_type, timestamp=timestamp) self._add_metric(metric) - def report_mint_lines(self, lines: List[str]) -> None: + def report_mint_lines(self, lines: list[str]) -> None: """Report mint lines using the MINT protocol Examples: @@ -515,9 +516,9 @@ def report_event( self, title: str, description: str, - properties: Optional[dict] = None, - timestamp: Optional[datetime] = None, - severity: Union[Severity, str] = Severity.INFO, + properties: dict | None = None, + timestamp: datetime | None = None, + severity: Severity | str = Severity.INFO, send_immediately: bool = False, ) -> None: """Report an event using log ingest. @@ -551,11 +552,11 @@ def report_dt_event( self, event_type: DtEventType, title: str, - start_time: Optional[int] = None, - end_time: Optional[int] = None, - timeout: Optional[int] = None, - entity_selector: Optional[str] = None, - properties: Optional[dict[str, str]] = None, + start_time: int | None = None, + end_time: int | None = None, + timeout: int | None = None, + entity_selector: str | None = None, + properties: dict[str, str] | None = None, ) -> None: """ Reports an event using the v2 event ingest API. @@ -575,7 +576,7 @@ def report_dt_event( entity_selector: The entity selector, if not set, the event is associated with environment entity (optional) properties: A map of event properties (optional) """ - event: Dict[str, Any] = {"eventType": event_type, "title": title} + event: dict[str, Any] = {"eventType": event_type, "title": title} if start_time: event["startTime"] = start_time if end_time: @@ -662,7 +663,7 @@ def report_log_event(self, log_event: dict, send_immediately: bool = False): """ self._send_events(log_event, send_immediately=send_immediately) - def report_log_events(self, log_events: List[dict], send_immediately: bool = False): + def report_log_events(self, log_events: list[dict], send_immediately: bool = False): """Report a list of custom log events using log ingest. Args: @@ -671,7 +672,7 @@ def report_log_events(self, log_events: List[dict], send_immediately: bool = Fal """ self._send_events(log_events, send_immediately=send_immediately) - def report_log_lines(self, log_lines: List[Union[str, bytes]], send_immediately: bool = False): + def report_log_lines(self, log_lines: list[str | bytes], send_immediately: bool = False): """Report a list of log lines using log ingest Args: @@ -885,13 +886,13 @@ def _send_metrics(self): api_logger.info(f"Sent {number_of_metrics} metric lines to EEC: {responses}") self._metrics = [] - def _prepare_sfm_metrics(self) -> List[str]: + def _prepare_sfm_metrics(self) -> list[str]: """Prepare self monitoring metrics. Builds the list of mint metric lines to send as self monitoring metrics. """ - sfm_metrics: List[Metric] = [] + sfm_metrics: list[Metric] = [] sfm_dimensions = {"dt.extension.config.id": self.monitoring_config_id} _add_sfm_metric( SfmMetric("threads", active_count(), sfm_dimensions, client_facing=True, metric_type=MetricType.DELTA), @@ -1047,11 +1048,11 @@ def _add_metric(self, metric: Metric): with self._metrics_lock: self._metrics.append(metric.to_mint_line()) - def _add_mint_lines(self, lines: List[str]): + def _add_mint_lines(self, lines: list[str]): with self._metrics_lock: self._metrics.extend(lines) - def _send_events_internal(self, events: Union[dict, List[dict]]): + def _send_events_internal(self, events: dict | list[dict]): try: responses = self._client.send_events(events, self.log_event_enrichment) @@ -1068,7 +1069,7 @@ def _send_events_internal(self, events: Union[dict, List[dict]]): with self._internal_callbacks_results_lock: self._internal_callbacks_results[self._send_events.__name__] = Status(StatusValue.GENERIC_ERROR, str(e)) - def _send_events(self, events: Union[dict, List[dict]], send_immediately: bool = False): + def _send_events(self, events: dict | list[dict], send_immediately: bool = False): if send_immediately: self._internal_executor.submit(self._send_events_internal, events) return diff --git a/dynatrace_extension/sdk/helper.py b/dynatrace_extension/sdk/helper.py index 808b667..48e8fb6 100644 --- a/dynatrace_extension/sdk/helper.py +++ b/dynatrace_extension/sdk/helper.py @@ -2,8 +2,8 @@ # # SPDX-License-Identifier: MIT +from collections.abc import Callable from datetime import datetime, timedelta -from typing import Callable, Dict, List, Optional, Union from .activation import ActivationConfig, ActivationType from .communication import Status @@ -20,10 +20,10 @@ def is_helper(self) -> bool: def report_metric( key: str, - value: Union[float, str, int, SummaryStat], - dimensions: Optional[Dict[str, str]] = None, - techrule: Optional[str] = None, - timestamp: Optional[datetime] = None, + value: float | str | int | SummaryStat, + dimensions: dict[str, str] | None = None, + techrule: str | None = None, + timestamp: datetime | None = None, metric_type: MetricType = MetricType.GAUGE, ) -> None: """Reports a metric using the MINT protocol @@ -37,10 +37,10 @@ def report_metric( :param timestamp: The timestamp of the metric, defaults to the current time :param metric_type: The type of the metric, defaults to MetricType.GAUGE """ - _HelperExtension().report_metric(key, value, dimensions, techrule, timestamp, metric_type) + _HelperExtension().report_metric(key, value, dimensions, techrule, timestamp, metric_type) # type: ignore -def report_mint_lines(lines: List[str]) -> None: +def report_mint_lines(lines: list[str]) -> None: """Reports mint lines using the MINT protocol. These lines are not validated before being sent. @@ -52,11 +52,11 @@ def report_mint_lines(lines: List[str]) -> None: def report_dt_event( event_type: DtEventType, title: str, - start_time: Optional[int] = None, - end_time: Optional[int] = None, - timeout: Optional[int] = None, - entity_selector: Optional[str] = None, - properties: Optional[dict[str, str]] = None, + start_time: int | None = None, + end_time: int | None = None, + timeout: int | None = None, + entity_selector: str | None = None, + properties: dict[str, str] | None = None, ) -> None: """ Reports a custom event v2 using event ingest @@ -85,9 +85,9 @@ def report_dt_event_dict(event: dict): def schedule( callback: Callable, - interval: Union[timedelta, int], - args: Optional[tuple] = None, - activation_type: Optional[ActivationType] = None, + interval: timedelta | int, + args: tuple | None = None, + activation_type: ActivationType | None = None, ) -> None: """Schedules a callback to be called periodically @@ -101,7 +101,7 @@ def schedule( def schedule_function( - interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None + interval: timedelta | int, args: tuple | None = None, activation_type: ActivationType | None = None ): def decorator(function): schedule(function, interval, args=args, activation_type=activation_type) @@ -110,7 +110,7 @@ def decorator(function): def schedule_method( - interval: Union[timedelta, int], args: Optional[tuple] = None, activation_type: Optional[ActivationType] = None + interval: timedelta | int, args: tuple | None = None, activation_type: ActivationType | None = None ): def decorator(function): Extension.schedule_decorators.append((function, interval, args, activation_type)) @@ -121,9 +121,9 @@ def decorator(function): def report_event( title: str, description: str, - properties: Optional[dict] = None, - timestamp: Optional[datetime] = None, - severity: Union[Severity, str] = Severity.INFO, + properties: dict | None = None, + timestamp: datetime | None = None, + severity: Severity | str = Severity.INFO, ) -> None: """Reports an event using the MINT protocol @@ -144,7 +144,7 @@ def report_log_event(log_event: dict): _HelperExtension().report_log_event(log_event) -def report_log_events(log_events: List[dict]): +def report_log_events(log_events: list[dict]): """Reports a list of custom log events using log ingest :param log_events: The list of log events @@ -152,7 +152,7 @@ def report_log_events(log_events: List[dict]): _HelperExtension().report_log_events(log_events) -def report_log_lines(log_lines: List[Union[str, bytes]]): +def report_log_lines(log_lines: list[str | bytes]): """Reports a list of log lines using log ingest :param log_lines: The list of log lines diff --git a/dynatrace_extension/sdk/metric.py b/dynatrace_extension/sdk/metric.py index 0d384b2..ea33d68 100644 --- a/dynatrace_extension/sdk/metric.py +++ b/dynatrace_extension/sdk/metric.py @@ -4,7 +4,6 @@ from datetime import datetime from enum import Enum -from typing import Dict, Optional, Union # https://bitbucket.lab.dynatrace.org/projects/ONE/repos/schemaless-metrics-spec/browse/limits.md LIMIT_DIMENSIONS_COUNT = 50 @@ -41,18 +40,18 @@ class Metric: def __init__( self, key: str, - value: Union[float, int, str, SummaryStat], - dimensions: Optional[Dict[str, str]] = None, + value: float | int | str | SummaryStat, + dimensions: dict[str, str] | None = None, metric_type: MetricType = MetricType.GAUGE, - timestamp: Optional[datetime] = None, + timestamp: datetime | None = None, ): self.key: str = key - self.value: Union[float, int, str, SummaryStat] = value + self.value: float | int | str | SummaryStat = value if dimensions is None: dimensions = {} - self.dimensions: Dict[str, str] = dimensions + self.dimensions: dict[str, str] = dimensions self.metric_type: MetricType = metric_type - self.timestamp: Optional[datetime] = timestamp + self.timestamp: datetime | None = timestamp def __hash__(self): return hash(self._key_and_dimensions()) @@ -103,10 +102,10 @@ class SfmMetric(Metric): def __init__( self, key: str, - value: Union[float, int, str, SummaryStat], - dimensions: Optional[Dict[str, str]] = None, + value: float | int | str | SummaryStat, + dimensions: dict[str, str] | None = None, metric_type: MetricType = MetricType.GAUGE, - timestamp: Optional[datetime] = None, + timestamp: datetime | None = None, client_facing: bool = False, ): key = create_sfm_metric_key(key, client_facing) diff --git a/dynatrace_extension/sdk/runtime.py b/dynatrace_extension/sdk/runtime.py index c386da9..eab4cc3 100644 --- a/dynatrace_extension/sdk/runtime.py +++ b/dynatrace_extension/sdk/runtime.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT import logging -from typing import ClassVar, List, NamedTuple +from typing import ClassVar, NamedTuple class DefaultLogLevel(NamedTuple): @@ -25,7 +25,7 @@ def __init__(self, json_response: dict): self.userconfig: str = json_response.get("userconfig", "") self.debugmode: bool = json_response.get("debugmode", "0") == "1" self.runtime: dict = json_response.get("runtime", {}) - self.tasks: List[str] = json_response.get("tasks", []) + self.tasks: list[str] = json_response.get("tasks", []) @classmethod def set_default_log_level(cls, value: str): diff --git a/dynatrace_extension/sdk/snapshot.py b/dynatrace_extension/sdk/snapshot.py index a646278..643bcb1 100644 --- a/dynatrace_extension/sdk/snapshot.py +++ b/dynatrace_extension/sdk/snapshot.py @@ -14,10 +14,10 @@ class EntryProperties: technologies: list[str] pg_technologies: list[str] - extra_properties: dict[str,str] + extra_properties: dict[str, str] @staticmethod - def from_json(json_data: dict[str,str]) -> EntryProperties: + def from_json(json_data: dict[str, str]) -> EntryProperties: technologies = json_data.get("Technologies", "").split(",") pg_technologies = json_data.get("pgTechnologies", "").split(",") extra_properties = {k: v for k, v in json_data.items() if k not in ["Technologies", "pgTechnologies"]} @@ -113,7 +113,7 @@ def from_json(json_data: dict) -> Entry: processes = [Process.from_json(p) for p in json_data.get("processes", [])] # The structure here was never thought out, so we have to check for both keys and merge them into one object - properties_list: list[dict[str,str]] = json_data.get("properties", []) + properties_list: list[dict[str, str]] = json_data.get("properties", []) technologies = [] pg_technologies = [] # There may be other useful properties included such as: mssql_instance_name. @@ -123,9 +123,9 @@ def from_json(json_data: dict) -> Entry: for key, value in prop.items(): match key: case "Technologies": - technologies.extend(value.split(",")) + technologies.extend(value.split(",")) case "pgTechnologies": - pg_technologies.extend(value.split(",")) + pg_technologies.extend(value.split(",")) case _: extra_properties[key] = value @@ -135,7 +135,6 @@ def from_json(json_data: dict) -> Entry: @dataclass - class Snapshot: host_id: str entries: list[Entry] diff --git a/pyproject.toml b/pyproject.toml index 4027eba..3605c66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,12 +116,12 @@ rebuild = [ ] [tool.black] -target-version = ["py37"] +target-version = ["py310"] line-length = 120 skip-string-normalization = true [tool.ruff] -target-version = "py37" +target-version = "py310" line-length = 160 select = [ "A", diff --git a/tests/sdk/test_communication.py b/tests/sdk/test_communication.py index a957759..bde614d 100644 --- a/tests/sdk/test_communication.py +++ b/tests/sdk/test_communication.py @@ -28,7 +28,6 @@ def test_http_client_metric_report(self, mock_make_request): self.assertEqual(len(responses), 0) def test_large_log_chunk(self): - # This is 14_660_000 bytes events = [] for _ in range(5000): @@ -58,7 +57,6 @@ def test_small_log_chunk(self): self.assertEqual(len(chunks[0]), 1720) def test_large_metric_chunk(self): - metrics = ['my.metric,dim="dim" 10'] * 500 * 100 # it needs to be divided into 2 lists, each with 650_000 bytes @@ -81,7 +79,6 @@ def test_no_metrics(self): self.assertEqual(len(chunks), 0) def test_large_log_chunk_valid_json(self): - events = [] for _ in range(5000): attributes = {} diff --git a/tests/sdk/test_extension.py b/tests/sdk/test_extension.py index 05c245a..0648479 100644 --- a/tests/sdk/test_extension.py +++ b/tests/sdk/test_extension.py @@ -53,7 +53,6 @@ def test_add_metric_with_device_address(self): extension.logger = MagicMock() extension._running_in_sim = True extension.report_metric("my_metric", 1, device_address="10.10.10.10") - print(extension._metrics[0]) self.assertEqual(len(extension._metrics), 1) self.assertTrue(extension._metrics[0].startswith('my_metric,device.address="10.10.10.10" gauge,1')) diff --git a/tests/sdk/test_runtime_properties.py b/tests/sdk/test_runtime_properties.py index f2d4030..a7a78b3 100644 --- a/tests/sdk/test_runtime_properties.py +++ b/tests/sdk/test_runtime_properties.py @@ -1,6 +1,6 @@ import logging import unittest -from typing import Any, Dict +from typing import Any from dynatrace_extension import Extension from dynatrace_extension.sdk.runtime import RuntimeProperties @@ -11,7 +11,7 @@ def tearDown(self) -> None: Extension._instance = None def test_api_log_level(self) -> None: - response_json: Dict[str, Any] = {"runtime": {}} + response_json: dict[str, Any] = {"runtime": {}} response_json["runtime"]["debuglevel.extension1.api"] = "debug" # converted to debug response_json["runtime"]["debuglevel.extension2.api"] = "WARNING" # wrong value - default to info diff --git a/tests/sdk/test_status.py b/tests/sdk/test_status.py index 9f785b7..6e658f9 100644 --- a/tests/sdk/test_status.py +++ b/tests/sdk/test_status.py @@ -113,7 +113,6 @@ def callback(): self.assertIn("foo1", status.message) def test_direct_statuses_return(self): - def callback(): return Status(StatusValue.OK, "foo1") From 9ff1ef331b386ab25dd2cf8c07e49c77347bde9e Mon Sep 17 00:00:00 2001 From: dlopes7 Date: Mon, 10 Mar 2025 18:56:19 -0500 Subject: [PATCH 3/3] Bump version --- dynatrace_extension/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynatrace_extension/__about__.py b/dynatrace_extension/__about__.py index af5946d..5efc598 100644 --- a/dynatrace_extension/__about__.py +++ b/dynatrace_extension/__about__.py @@ -3,4 +3,4 @@ # SPDX-License-Identifier: MIT -__version__ = "1.5.0" +__version__ = "1.5.1"