diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 1fed32c0be..c470cb6969 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py @@ -63,6 +63,8 @@ import typing from json import dumps from os import environ +from types import ModuleType +from typing import Dict, List, Optional, Union, cast from urllib import parse from opentelemetry.attributes import BoundedAttributes @@ -75,10 +77,14 @@ from opentelemetry.util._importlib_metadata import entry_points, version from opentelemetry.util.types import AttributeValue +psutil: Optional[ModuleType] = None + try: - import psutil + import psutil as pustil_module + + pustil = pustil_module except ImportError: - psutil = None + pass LabelValue = AttributeValue Attributes = typing.Mapping[str, LabelValue] @@ -147,6 +153,11 @@ class Resource: """A Resource is an immutable representation of the entity producing telemetry as Attributes.""" + _attributes: Dict[ + str, Union[str, int, float, bool] + ] # Example: Adjust according to actual expected types + _schema_url: str + def __init__( self, attributes: Attributes, schema_url: typing.Optional[str] = None ): @@ -173,7 +184,7 @@ def create( if not attributes: attributes = {} - resource_detectors = [] + resource_detectors: List[ResourceDetector] = [] resource = _DEFAULT_RESOURCE @@ -182,6 +193,7 @@ def create( ).split(",") if "otel" not in otel_experimental_resource_detectors: + otel_experimental_resource_detectors.append("otel") for resource_detector in otel_experimental_resource_detectors: @@ -193,9 +205,8 @@ def create( name=resource_detector.strip(), ) ) - ).load()() + ) ) - resource = get_aggregated_resources( resource_detectors, _DEFAULT_RESOURCE ).merge(Resource(attributes, schema_url)) @@ -206,7 +217,7 @@ def create( PROCESS_EXECUTABLE_NAME, None ) if process_executable_name: - default_service_name += ":" + process_executable_name + default_service_name += ":" + str(process_executable_name) resource = resource.merge( Resource({SERVICE_NAME: default_service_name}, schema_url) ) @@ -218,6 +229,8 @@ def get_empty() -> "Resource": @property def attributes(self) -> Attributes: + if self._attributes is None: + raise ValueError("Attributes are not set.") return self._attributes @property @@ -241,7 +254,7 @@ def merge(self, other: "Resource") -> "Resource": Returns: The newly-created Resource. """ - merged_attributes = self.attributes.copy() + merged_attributes = dict(self.attributes) merged_attributes.update(other.attributes) if self.schema_url == "": @@ -257,7 +270,6 @@ def merge(self, other: "Resource") -> "Resource": other.schema_url, ) return self - return Resource(merged_attributes, schema_url) def __eq__(self, other: object) -> bool: @@ -268,15 +280,15 @@ def __eq__(self, other: object) -> bool: and self._schema_url == other._schema_url ) - def __hash__(self): - return hash( - f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}" - ) + def __hash__(self) -> int: + attributes_json = dumps(self._attributes.copy(), sort_keys=True) + return hash(f"{attributes_json}|{self._schema_url}") - def to_json(self, indent=4) -> str: + def to_json(self, indent: int = 4) -> str: + attributes = dict(self._attributes) return dumps( { - "attributes": dict(self._attributes), + "attributes": attributes, "schema_url": self._schema_url, }, indent=indent, @@ -294,7 +306,7 @@ def to_json(self, indent=4) -> str: class ResourceDetector(abc.ABC): - def __init__(self, raise_on_error=False): + def __init__(self, raise_on_error: bool = False) -> None: self.raise_on_error = raise_on_error @abc.abstractmethod @@ -343,7 +355,7 @@ def detect(self) -> "Resource": ), ) ) - _process_pid = os.getpid() + _process_pid = str(os.getpid()) _process_executable_name = sys.executable _process_executable_path = os.path.dirname(_process_executable_name) _process_command = sys.argv[0] @@ -358,15 +370,16 @@ def detect(self) -> "Resource": PROCESS_EXECUTABLE_PATH: _process_executable_path, PROCESS_COMMAND: _process_command, PROCESS_COMMAND_LINE: _process_command_line, - PROCESS_COMMAND_ARGS: _process_command_args, + PROCESS_COMMAND_ARGS: "".join(_process_command_args), } if hasattr(os, "getppid"): # pypy3 does not have getppid() - resource_info[PROCESS_PARENT_PID] = os.getppid() + resource_info[PROCESS_PARENT_PID] = str(os.getppid()) if psutil is not None: process = psutil.Process() - resource_info[PROCESS_OWNER] = process.username() + username = cast(str, process.username()) + resource_info[PROCESS_OWNER] = username return Resource(resource_info) @@ -374,7 +387,7 @@ def detect(self) -> "Resource": def get_aggregated_resources( detectors: typing.List["ResourceDetector"], initial_resource: typing.Optional[Resource] = None, - timeout=5, + timeout: int = 5, ) -> "Resource": """Retrieves resources from detectors in the order that they were passed diff --git a/tox.ini b/tox.ini index dfa3edd028..8f6e1079f8 100644 --- a/tox.ini +++ b/tox.ini @@ -332,6 +332,7 @@ commands = coverage: {toxinidir}/scripts/coverage.sh mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-api/src/opentelemetry/ + mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-sdk/src/opentelemetry/sdk/resources mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-semantic-conventions/src/opentelemetry/semconv/ ; For test code, we don't want to enforce the full mypy strictness