Skip to content

Commit

Permalink
This PR is based on open-telemetry#3995.
Browse files Browse the repository at this point in the history
Part of open-telemetry#1608

Addressing running mypy on opentelemetry-sdk iteratively so we don't have to make one big change addressing all mypy issues at once.
  • Loading branch information
asasvari authored and xrmx committed Jul 26, 2024
1 parent ed8d950 commit e56e87e
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 53 deletions.
59 changes: 30 additions & 29 deletions opentelemetry-api/src/opentelemetry/attributes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# type: ignore

import logging
import threading
Expand Down Expand Up @@ -59,7 +58,7 @@ def _clean_attribute(
cleaned_seq = []

for element in value:
element = _clean_attribute_value(element, max_len)
element = _clean_attribute_value(element, max_len) # type: ignore
if element is None:
cleaned_seq.append(element)
continue
Expand Down Expand Up @@ -96,7 +95,7 @@ def _clean_attribute(
cleaned_seq.append(element)

# Freeze mutable sequences defensively
return tuple(cleaned_seq)
return tuple(cleaned_seq) # type: ignore

_logger.warning(
"Invalid type %s for attribute '%s' value. Expected one of %s or a "
Expand Down Expand Up @@ -126,7 +125,7 @@ def _clean_attribute_value(
return value


class BoundedAttributes(MutableMapping):
class BoundedAttributes(MutableMapping): # type: ignore
"""An ordered dict with a fixed max capacity.
Oldest elements are dropped when the dict is full and a new element is
Expand All @@ -149,53 +148,55 @@ def __init__(
self.dropped = 0
self.max_value_len = max_value_len
# OrderedDict is not used until the maxlen is reached for efficiency.
self._dict = {} # type: dict | OrderedDict
self._lock = threading.RLock() # type: threading.RLock
# self._dict type: dict | OrderedDict

self._dict = {} # type: ignore
self._lock = threading.RLock()
if attributes:
for key, value in attributes.items():
self[key] = value
self._immutable = immutable

def __repr__(self):
return f"{dict(self._dict)}"
def __repr__(self) -> str:
return f"{dict(self._dict)}" # type: ignore

def __getitem__(self, key):
return self._dict[key]
def __getitem__(self, key): # type: ignore
return self._dict[key] # type: ignore

def __setitem__(self, key, value):
if getattr(self, "_immutable", False):
def __setitem__(self, key, value): # type: ignore
if getattr(self, "_immutable", False): # type: ignore
raise TypeError
with self._lock:
if self.maxlen is not None and self.maxlen == 0:
self.dropped += 1
return

value = _clean_attribute(key, value, self.max_value_len)
if value is not None:
if key in self._dict:
del self._dict[key]
value = _clean_attribute(key, value, self.max_value_len) # type: ignore
if value is not None: # type: ignore
if key in self._dict: # type: ignore
del self._dict[key] # type: ignore
elif (
self.maxlen is not None and len(self._dict) == self.maxlen
self.maxlen is not None and len(self._dict) == self.maxlen # type: ignore
):
if not isinstance(self._dict, OrderedDict):
self._dict = OrderedDict(self._dict)
if not isinstance(self._dict, OrderedDict): # type: ignore
self._dict = OrderedDict(self._dict) # type: ignore
self._dict.popitem(last=False)
self.dropped += 1

self._dict[key] = value
self._dict[key] = value # type: ignore

def __delitem__(self, key):
if getattr(self, "_immutable", False):
def __delitem__(self, key): # type: ignore
if getattr(self, "_immutable", False): # type: ignore
raise TypeError
with self._lock:
del self._dict[key]
del self._dict[key] # type: ignore

def __iter__(self):
def __iter__(self): # type: ignore
with self._lock:
return iter(self._dict.copy())
return iter(self._dict.copy()) # type: ignore

def __len__(self):
return len(self._dict)
def __len__(self): # type: ignore
return len(self._dict) # type: ignore

def copy(self):
return self._dict.copy()
def copy(self): # type: ignore
return self._dict.copy() # type: ignore
58 changes: 35 additions & 23 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
import typing
from json import dumps
from os import environ
from types import ModuleType
from typing import List, Optional
from urllib import parse

from opentelemetry.attributes import BoundedAttributes
Expand All @@ -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]
Expand Down Expand Up @@ -141,12 +147,15 @@
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE

_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")
_OPENTELEMETRY_SDK_VERSION: str = version("opentelemetry-sdk")


class Resource:
"""A Resource is an immutable representation of the entity producing telemetry as Attributes."""

_attributes: BoundedAttributes
_schema_url: str

def __init__(
self, attributes: Attributes, schema_url: typing.Optional[str] = None
):
Expand All @@ -173,7 +182,7 @@ def create(
if not attributes:
attributes = {}

resource_detectors = []
resource_detectors: List[ResourceDetector] = []

resource = _DEFAULT_RESOURCE

Expand All @@ -182,20 +191,21 @@ def create(
).split(",")

if "otel" not in otel_experimental_resource_detectors:

otel_experimental_resource_detectors.append("otel")

resource_detector: str
for resource_detector in otel_experimental_resource_detectors:
resource_detectors.append(
next(
iter(
entry_points(
group="opentelemetry_resource_detector",
name=resource_detector.strip(),
)
) # type: ignore
)
).load()()
)
)

resource = get_aggregated_resources(
resource_detectors, _DEFAULT_RESOURCE
).merge(Resource(attributes, schema_url))
Expand All @@ -206,7 +216,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,6 +228,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
Expand All @@ -241,7 +253,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 == "":
Expand All @@ -257,7 +269,6 @@ def merge(self, other: "Resource") -> "Resource":
other.schema_url,
)
return self

return Resource(merged_attributes, schema_url)

def __eq__(self, other: object) -> bool:
Expand All @@ -268,15 +279,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) # type: ignore
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) # type: ignore
return dumps(
{
"attributes": dict(self._attributes),
"attributes": attributes, # type: ignore
"schema_url": self._schema_url,
},
indent=indent,
Expand All @@ -294,7 +305,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 +354,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 +369,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()
process: pustil_module.Process = psutil.Process()
username = 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
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ setenv =
; i.e: CONTRIB_REPO_SHA=dde62cebffe519c35875af6d06fae053b3be65ec tox -e <env to test>
CONTRIB_REPO_SHA={env:CONTRIB_REPO_SHA:main}
CONTRIB_REPO=git+https://github.com/open-telemetry/opentelemetry-python-contrib.git@{env:CONTRIB_REPO_SHA}
mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/:{toxinidir}/tests/opentelemetry-test-utils/src/
mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/:{toxinidir}/opentelemetry-semantic-conventions/src/:{toxinidir}/opentelemetry-sdk/src/:{toxinidir}/tests/opentelemetry-test-utils/src/

commands_pre =

Expand Down Expand Up @@ -314,7 +314,9 @@ commands =

coverage: {toxinidir}/scripts/coverage.sh

mypy: mypy --version
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

0 comments on commit e56e87e

Please sign in to comment.