Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running mypy on SDK resources #3995

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@
import os
import sys
import typing
from typing import Dict, Any, Union, Optional, Union, cast, List
from json import dumps
from os import environ
from urllib import parse

from types import ModuleType
from opentelemetry.attributes import BoundedAttributes


from opentelemetry.sdk.environment_variables import (
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS,
OTEL_RESOURCE_ATTRIBUTES,
Expand All @@ -75,10 +78,13 @@
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]
Expand Down Expand Up @@ -146,6 +152,8 @@

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
Expand Down Expand Up @@ -173,7 +181,7 @@ def create(
if not attributes:
attributes = {}

resource_detectors = []
resource_detectors: List[ResourceDetector]

resource = _DEFAULT_RESOURCE

Expand All @@ -182,20 +190,20 @@ 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:
resource_detectors.append(
resource_detectors.append(
next(
iter(
entry_points(
group="opentelemetry_resource_detector",
name=resource_detector.strip(),
)
)
).load()()
iter(
entry_points(
group="opentelemetry_resource_detector",
name=resource_detector.strip(),
)
)

)
)
resource = get_aggregated_resources(
resource_detectors, _DEFAULT_RESOURCE
).merge(Resource(attributes, schema_url))
Expand All @@ -206,7 +214,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)
)
Expand All @@ -218,13 +226,14 @@ def get_empty() -> "Resource":

@property
def attributes(self) -> Attributes:
return self._attributes
if self._attributes is None:
raise ValueError("Attributes are not set.")
return self._attributes

@property
def schema_url(self) -> str:
return self._schema_url

def merge(self, other: "Resource") -> "Resource":
def merge(self, other: 'Resource') -> 'Resource':
"""Merges this resource and an updating resource into a new `Resource`.

If a key exists on both the old and updating resource, the value of the
Expand All @@ -241,9 +250,9 @@ 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 == "":
schema_url = other.schema_url
elif other.schema_url == "":
Expand All @@ -257,8 +266,8 @@ def merge(self, other: "Resource") -> "Resource":
other.schema_url,
)
return self

return Resource(merged_attributes, schema_url)


def __eq__(self, other: object) -> bool:
if not isinstance(other, Resource):
Expand All @@ -268,21 +277,17 @@ 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:
return dumps(
{
"attributes": dict(self._attributes),
def to_json(self, indent: int = 4) -> str:
attributes = dict(self._attributes)
return dumps({
"attributes": attributes,
"schema_url": self._schema_url,
},
indent=indent,
)


}, indent=indent)

_EMPTY_RESOURCE = Resource({})
_DEFAULT_RESOURCE = Resource(
{
Expand All @@ -294,7 +299,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
Expand Down Expand Up @@ -343,7 +348,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]
Expand All @@ -358,23 +363,24 @@ 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)


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

Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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
Expand Down
Loading