diff --git a/opentelemetry-api/src/opentelemetry/attributes/__init__.py b/opentelemetry-api/src/opentelemetry/attributes/__init__.py index 72ee38abd7..13d9fd4946 100644 --- a/opentelemetry-api/src/opentelemetry/attributes/__init__.py +++ b/opentelemetry-api/src/opentelemetry/attributes/__init__.py @@ -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 @@ -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 @@ -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 " @@ -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 @@ -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 diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py index 1fed32c0be..1697141420 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 List, Optional 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] @@ -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 ): @@ -173,7 +182,7 @@ def create( if not attributes: attributes = {} - resource_detectors = [] + resource_detectors: List[ResourceDetector] = [] resource = _DEFAULT_RESOURCE @@ -182,8 +191,10 @@ 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( @@ -191,11 +202,10 @@ def create( 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)) @@ -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) ) @@ -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 @@ -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 == "": @@ -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: @@ -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, @@ -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 @@ -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] @@ -358,15 +369,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() + process: pustil_module.Process = psutil.Process() + username = process.username() + resource_info[PROCESS_OWNER] = username return Resource(resource_info) @@ -374,7 +386,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..59ee5ada83 100644 --- a/tox.ini +++ b/tox.ini @@ -125,7 +125,7 @@ setenv = ; i.e: CONTRIB_REPO_SHA=dde62cebffe519c35875af6d06fae053b3be65ec tox -e 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 = @@ -331,7 +331,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