Skip to content

Commit 51b7e4c

Browse files
authored
Running mypy on SDK resources (following up #3995) (#4053)
1 parent b9cadc0 commit 51b7e4c

File tree

4 files changed

+66
-45
lines changed

4 files changed

+66
-45
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10-
- Added py.typed file to top-level module ([#4084](https://github.com/open-telemetry/opentelemetry-python/pull/4084))
10+
- Running mypy on SDK resources
11+
([#4053](https://github.com/open-telemetry/opentelemetry-python/pull/4053))
12+
- Added py.typed file to top-level module
13+
([#4084](https://github.com/open-telemetry/opentelemetry-python/pull/4084))
1114
- Drop Final annotation from Enum in semantic conventions
1215
([#4085](https://github.com/open-telemetry/opentelemetry-python/pull/4085))
1316
- Update log export example to not use root logger ([#4090](https://github.com/open-telemetry/opentelemetry-python/pull/4090))

opentelemetry-api/src/opentelemetry/attributes/__init__.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
# type: ignore
1514

1615
import logging
1716
import threading
1817
from collections import OrderedDict
1918
from collections.abc import MutableMapping
20-
from typing import Optional, Sequence, Union
19+
from typing import Optional, Sequence, Tuple, Union
2120

2221
from opentelemetry.util import types
2322

@@ -31,7 +30,7 @@
3130

3231
def _clean_attribute(
3332
key: str, value: types.AttributeValue, max_len: Optional[int]
34-
) -> Optional[types.AttributeValue]:
33+
) -> Optional[Union[types.AttributeValue, Tuple[Union[str, int, float], ...]]]:
3534
"""Checks if attribute value is valid and cleans it if required.
3635
3736
The function returns the cleaned value or None if the value is not valid.
@@ -59,7 +58,7 @@ def _clean_attribute(
5958
cleaned_seq = []
6059

6160
for element in value:
62-
element = _clean_attribute_value(element, max_len)
61+
element = _clean_attribute_value(element, max_len) # type: ignore
6362
if element is None:
6463
cleaned_seq.append(element)
6564
continue
@@ -110,7 +109,7 @@ def _clean_attribute(
110109

111110
def _clean_attribute_value(
112111
value: types.AttributeValue, limit: Optional[int]
113-
) -> Union[types.AttributeValue, None]:
112+
) -> Optional[types.AttributeValue]:
114113
if value is None:
115114
return None
116115

@@ -126,7 +125,7 @@ def _clean_attribute_value(
126125
return value
127126

128127

129-
class BoundedAttributes(MutableMapping):
128+
class BoundedAttributes(MutableMapping): # type: ignore
130129
"""An ordered dict with a fixed max capacity.
131130
132131
Oldest elements are dropped when the dict is full and a new element is
@@ -149,28 +148,32 @@ def __init__(
149148
self.dropped = 0
150149
self.max_value_len = max_value_len
151150
# OrderedDict is not used until the maxlen is reached for efficiency.
152-
self._dict = {} # type: dict | OrderedDict
153-
self._lock = threading.RLock() # type: threading.RLock
151+
152+
self._dict: Union[
153+
MutableMapping[str, types.AttributeValue],
154+
OrderedDict[str, types.AttributeValue],
155+
] = {}
156+
self._lock = threading.RLock()
154157
if attributes:
155158
for key, value in attributes.items():
156159
self[key] = value
157160
self._immutable = immutable
158161

159-
def __repr__(self):
162+
def __repr__(self) -> str:
160163
return f"{dict(self._dict)}"
161164

162-
def __getitem__(self, key):
165+
def __getitem__(self, key: str) -> types.AttributeValue:
163166
return self._dict[key]
164167

165-
def __setitem__(self, key, value):
166-
if getattr(self, "_immutable", False):
168+
def __setitem__(self, key: str, value: types.AttributeValue) -> None:
169+
if getattr(self, "_immutable", False): # type: ignore
167170
raise TypeError
168171
with self._lock:
169172
if self.maxlen is not None and self.maxlen == 0:
170173
self.dropped += 1
171174
return
172175

173-
value = _clean_attribute(key, value, self.max_value_len)
176+
value = _clean_attribute(key, value, self.max_value_len) # type: ignore
174177
if value is not None:
175178
if key in self._dict:
176179
del self._dict[key]
@@ -179,23 +182,23 @@ def __setitem__(self, key, value):
179182
):
180183
if not isinstance(self._dict, OrderedDict):
181184
self._dict = OrderedDict(self._dict)
182-
self._dict.popitem(last=False)
185+
self._dict.popitem(last=False) # type: ignore
183186
self.dropped += 1
184187

185-
self._dict[key] = value
188+
self._dict[key] = value # type: ignore
186189

187-
def __delitem__(self, key):
188-
if getattr(self, "_immutable", False):
190+
def __delitem__(self, key: str) -> None:
191+
if getattr(self, "_immutable", False): # type: ignore
189192
raise TypeError
190193
with self._lock:
191194
del self._dict[key]
192195

193-
def __iter__(self):
196+
def __iter__(self): # type: ignore
194197
with self._lock:
195-
return iter(self._dict.copy())
198+
return iter(self._dict.copy()) # type: ignore
196199

197-
def __len__(self):
200+
def __len__(self) -> int:
198201
return len(self._dict)
199202

200-
def copy(self):
201-
return self._dict.copy()
203+
def copy(self): # type: ignore
204+
return self._dict.copy() # type: ignore

opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import typing
6464
from json import dumps
6565
from os import environ
66+
from types import ModuleType
67+
from typing import List, MutableMapping, Optional, cast
6668
from urllib import parse
6769

6870
from opentelemetry.attributes import BoundedAttributes
@@ -75,10 +77,14 @@
7577
from opentelemetry.util._importlib_metadata import entry_points, version
7678
from opentelemetry.util.types import AttributeValue
7779

80+
psutil: Optional[ModuleType] = None
81+
7882
try:
79-
import psutil
83+
import psutil as psutil_module
84+
85+
pustil = psutil_module
8086
except ImportError:
81-
psutil = None
87+
pass
8288

8389
LabelValue = AttributeValue
8490
Attributes = typing.Mapping[str, LabelValue]
@@ -141,12 +147,15 @@
141147
TELEMETRY_AUTO_VERSION = ResourceAttributes.TELEMETRY_AUTO_VERSION
142148
TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE
143149

144-
_OPENTELEMETRY_SDK_VERSION = version("opentelemetry-sdk")
150+
_OPENTELEMETRY_SDK_VERSION: str = version("opentelemetry-sdk")
145151

146152

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

156+
_attributes: BoundedAttributes
157+
_schema_url: str
158+
150159
def __init__(
151160
self, attributes: Attributes, schema_url: typing.Optional[str] = None
152161
):
@@ -173,7 +182,7 @@ def create(
173182
if not attributes:
174183
attributes = {}
175184

176-
resource_detectors = []
185+
resource_detectors: List[ResourceDetector] = []
177186

178187
resource = _DEFAULT_RESOURCE
179188

@@ -184,26 +193,27 @@ def create(
184193
if "otel" not in otel_experimental_resource_detectors:
185194
otel_experimental_resource_detectors.append("otel")
186195

196+
resource_detector: str
187197
for resource_detector in otel_experimental_resource_detectors:
188198
resource_detectors.append(
189199
next(
190200
iter(
191201
entry_points(
192202
group="opentelemetry_resource_detector",
193203
name=resource_detector.strip(),
194-
)
204+
) # type: ignore
195205
)
196206
).load()()
197207
)
198-
199208
resource = get_aggregated_resources(
200209
resource_detectors, _DEFAULT_RESOURCE
201210
).merge(Resource(attributes, schema_url))
202211

203212
if not resource.attributes.get(SERVICE_NAME, None):
204213
default_service_name = "unknown_service"
205-
process_executable_name = resource.attributes.get(
206-
PROCESS_EXECUTABLE_NAME, None
214+
process_executable_name = cast(
215+
Optional[str],
216+
resource.attributes.get(PROCESS_EXECUTABLE_NAME, None),
207217
)
208218
if process_executable_name:
209219
default_service_name += ":" + process_executable_name
@@ -241,8 +251,8 @@ def merge(self, other: "Resource") -> "Resource":
241251
Returns:
242252
The newly-created Resource.
243253
"""
244-
merged_attributes = self.attributes.copy()
245-
merged_attributes.update(other.attributes)
254+
merged_attributes = self.attributes.copy() # type: ignore
255+
merged_attributes.update(other.attributes) # type: ignore
246256

247257
if self.schema_url == "":
248258
schema_url = other.schema_url
@@ -257,8 +267,7 @@ def merge(self, other: "Resource") -> "Resource":
257267
other.schema_url,
258268
)
259269
return self
260-
261-
return Resource(merged_attributes, schema_url)
270+
return Resource(merged_attributes, schema_url) # type: ignore
262271

263272
def __eq__(self, other: object) -> bool:
264273
if not isinstance(other, Resource):
@@ -268,15 +277,18 @@ def __eq__(self, other: object) -> bool:
268277
and self._schema_url == other._schema_url
269278
)
270279

271-
def __hash__(self):
280+
def __hash__(self) -> int:
272281
return hash(
273-
f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}"
282+
f"{dumps(self._attributes.copy(), sort_keys=True)}|{self._schema_url}" # type: ignore
274283
)
275284

276-
def to_json(self, indent=4) -> str:
285+
def to_json(self, indent: int = 4) -> str:
286+
attributes: MutableMapping[str, AttributeValue] = dict(
287+
self._attributes
288+
)
277289
return dumps(
278290
{
279-
"attributes": dict(self._attributes),
291+
"attributes": attributes, # type: ignore
280292
"schema_url": self._schema_url,
281293
},
282294
indent=indent,
@@ -294,7 +306,7 @@ def to_json(self, indent=4) -> str:
294306

295307

296308
class ResourceDetector(abc.ABC):
297-
def __init__(self, raise_on_error=False):
309+
def __init__(self, raise_on_error: bool = False) -> None:
298310
self.raise_on_error = raise_on_error
299311

300312
@abc.abstractmethod
@@ -365,16 +377,17 @@ def detect(self) -> "Resource":
365377
resource_info[PROCESS_PARENT_PID] = os.getppid()
366378

367379
if psutil is not None:
368-
process = psutil.Process()
369-
resource_info[PROCESS_OWNER] = process.username()
380+
process: psutil_module.Process = psutil.Process()
381+
username = process.username()
382+
resource_info[PROCESS_OWNER] = username
370383

371-
return Resource(resource_info)
384+
return Resource(resource_info) # type: ignore
372385

373386

374387
def get_aggregated_resources(
375388
detectors: typing.List["ResourceDetector"],
376389
initial_resource: typing.Optional[Resource] = None,
377-
timeout=5,
390+
timeout: int = 5,
378391
) -> "Resource":
379392
"""Retrieves resources from detectors in the order that they were passed
380393

tox.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ setenv =
126126
; i.e: CONTRIB_REPO_SHA=dde62cebffe519c35875af6d06fae053b3be65ec tox -e <env to test>
127127
CONTRIB_REPO_SHA={env:CONTRIB_REPO_SHA:main}
128128
CONTRIB_REPO=git+https://github.com/open-telemetry/opentelemetry-python-contrib.git@{env:CONTRIB_REPO_SHA}
129-
mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/:{toxinidir}/tests/opentelemetry-test-utils/src/
129+
mypy: MYPYPATH={toxinidir}/opentelemetry-api/src/:{toxinidir}/opentelemetry-semantic-conventions/src/:{toxinidir}/opentelemetry-sdk/src/:{toxinidir}/tests/opentelemetry-test-utils/src/
130130

131131
commands_pre =
132132

@@ -314,7 +314,9 @@ commands =
314314

315315
coverage: {toxinidir}/scripts/coverage.sh
316316

317+
mypy: mypy --version
317318
mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-api/src/opentelemetry/
319+
mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-sdk/src/opentelemetry/sdk/resources
318320
mypy: mypy --install-types --non-interactive --namespace-packages --explicit-package-bases opentelemetry-semantic-conventions/src/opentelemetry/semconv/
319321

320322
; For test code, we don't want to enforce the full mypy strictness

0 commit comments

Comments
 (0)