Skip to content

Commit fb94553

Browse files
anuraagaxrmx
andauthored
fix(metrics): fix metric reader types and tests (open-telemetry#4938)
* chore(metrics): fix metric reader types and tests * Fix lint * Fix lint * Changelog --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent dc9268b commit fb94553

File tree

7 files changed

+94
-34
lines changed

7 files changed

+94
-34
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Unreleased
1414

15+
- `opentelemetry-sdk`: fix type annotations on `MetricReader` and related types
16+
([#4938](https://github.com/open-telemetry/opentelemetry-python/pull/4938/))
17+
1518
## Version 1.40.0/0.61b0 (2026-03-04)
1619

1720
- `opentelemetry-sdk`: deprecate `LoggingHandler` in favor of `opentelemetry-instrumentation-logging`, see `opentelemetry-instrumentation-logging` documentation

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/export/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ class ConsoleLogRecordExporter(LogRecordExporter):
134134
def __init__(
135135
self,
136136
out: IO = sys.stdout,
137-
formatter: Callable[
138-
[ReadableLogRecord], str
139-
] = lambda record: record.to_json() + linesep,
137+
formatter: Callable[[ReadableLogRecord], str] = lambda record: (
138+
record.to_json() + linesep
139+
),
140140
):
141141
self.out = out
142142
self.formatter = formatter

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ class ConsoleMetricExporter(MetricExporter):
144144
def __init__(
145145
self,
146146
out: IO = stdout,
147-
formatter: Callable[
148-
[MetricsData], str
149-
] = lambda metrics_data: metrics_data.to_json() + linesep,
147+
formatter: Callable[[MetricsData], str] = lambda metrics_data: (
148+
metrics_data.to_json() + linesep
149+
),
150150
preferred_temporality: dict[type, AggregationTemporality]
151151
| None = None,
152152
preferred_aggregation: dict[
@@ -353,7 +353,7 @@ def _set_collect_callback(
353353
"opentelemetry.sdk.metrics.export.MetricReader",
354354
AggregationTemporality,
355355
],
356-
Iterable["opentelemetry.sdk.metrics.export.Metric"],
356+
MetricsData,
357357
],
358358
) -> None:
359359
"""This function is internal to the SDK. It should not be called or overridden by users"""

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement_consumer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from abc import ABC, abstractmethod
1818
from threading import Lock
1919
from time import time_ns
20-
from typing import Iterable, List, Mapping, Optional
20+
from typing import List, Mapping, Optional
2121

2222
# This kind of import is needed to avoid Sphinx errors.
2323
import opentelemetry.sdk.metrics
@@ -29,7 +29,7 @@
2929
from opentelemetry.sdk.metrics._internal.metric_reader_storage import (
3030
MetricReaderStorage,
3131
)
32-
from opentelemetry.sdk.metrics._internal.point import Metric
32+
from opentelemetry.sdk.metrics._internal.point import MetricsData
3333

3434

3535
class MeasurementConsumer(ABC):
@@ -51,7 +51,7 @@ def collect(
5151
self,
5252
metric_reader: "opentelemetry.sdk.metrics.MetricReader",
5353
timeout_millis: float = 10_000,
54-
) -> Optional[Iterable[Metric]]:
54+
) -> Optional[MetricsData]:
5555
pass
5656

5757

@@ -104,7 +104,7 @@ def collect(
104104
self,
105105
metric_reader: "opentelemetry.sdk.metrics.MetricReader",
106106
timeout_millis: float = 10_000,
107-
) -> Optional[Iterable[Metric]]:
107+
) -> Optional[MetricsData]:
108108
with self._lock:
109109
metric_reader_storage = self._reader_storages[metric_reader]
110110
# for now, just use the defaults

opentelemetry-sdk/src/opentelemetry/sdk/trace/export/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,9 @@ def __init__(
300300
self,
301301
service_name: str | None = None,
302302
out: typing.IO = sys.stdout,
303-
formatter: typing.Callable[
304-
[ReadableSpan], str
305-
] = lambda span: span.to_json() + linesep,
303+
formatter: typing.Callable[[ReadableSpan], str] = lambda span: (
304+
span.to_json() + linesep
305+
),
306306
):
307307
self.out = out
308308
self.formatter = formatter

opentelemetry-sdk/tests/metrics/test_in_memory_metric_reader.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,32 @@
2020

2121
from opentelemetry.metrics import Observation
2222
from opentelemetry.sdk.metrics import Counter, MeterProvider
23+
from opentelemetry.sdk.metrics._internal.point import (
24+
MetricsData,
25+
ResourceMetrics,
26+
ScopeMetrics,
27+
)
2328
from opentelemetry.sdk.metrics.export import (
2429
AggregationTemporality,
2530
InMemoryMetricReader,
2631
Metric,
2732
NumberDataPoint,
2833
Sum,
2934
)
35+
from opentelemetry.sdk.resources import Resource
36+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
3037

3138

3239
class TestInMemoryMetricReader(TestCase):
3340
def test_no_metrics(self):
34-
mock_collect_callback = Mock(return_value=[])
41+
mock_collect_callback = Mock(
42+
return_value=MetricsData(resource_metrics=[])
43+
)
3544
reader = InMemoryMetricReader()
3645
reader._set_collect_callback(mock_collect_callback)
37-
self.assertEqual(reader.get_metrics_data(), [])
46+
self.assertEqual(
47+
reader.get_metrics_data(), MetricsData(resource_metrics=[])
48+
)
3849
mock_collect_callback.assert_called_once()
3950

4051
def test_converts_metrics_to_list(self):
@@ -55,15 +66,32 @@ def test_converts_metrics_to_list(self):
5566
is_monotonic=True,
5667
),
5768
)
58-
mock_collect_callback = Mock(return_value=(metric,))
69+
metric_data = MetricsData(
70+
resource_metrics=[
71+
ResourceMetrics(
72+
scope_metrics=[
73+
ScopeMetrics(
74+
metrics=[metric],
75+
scope=InstrumentationScope(name="test"),
76+
schema_url="",
77+
)
78+
],
79+
resource=Resource.create(),
80+
schema_url="",
81+
)
82+
]
83+
)
84+
mock_collect_callback = Mock(return_value=metric_data)
5985
reader = InMemoryMetricReader()
6086
reader._set_collect_callback(mock_collect_callback)
6187

6288
returned_metrics = reader.get_metrics_data()
6389
mock_collect_callback.assert_called_once()
64-
self.assertIsInstance(returned_metrics, tuple)
65-
self.assertEqual(len(returned_metrics), 1)
66-
self.assertIs(returned_metrics[0], metric)
90+
self.assertIsNotNone(returned_metrics)
91+
self.assertIs(
92+
returned_metrics.resource_metrics[0].scope_metrics[0].metrics[0],
93+
metric,
94+
)
6795

6896
def test_shutdown(self):
6997
# shutdown should always be successful

opentelemetry-sdk/tests/metrics/test_periodic_exporting_metric_reader.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@
1919
import weakref
2020
from logging import WARNING
2121
from time import sleep, time_ns
22-
from typing import Optional, Sequence
22+
from typing import Optional
2323
from unittest.mock import Mock
2424

2525
import pytest
2626

2727
from opentelemetry.sdk.metrics import Counter, MetricsTimeoutError
2828
from opentelemetry.sdk.metrics._internal import _Counter
29+
from opentelemetry.sdk.metrics._internal.point import (
30+
MetricsData,
31+
ResourceMetrics,
32+
ScopeMetrics,
33+
)
2934
from opentelemetry.sdk.metrics.export import (
3035
AggregationTemporality,
3136
Gauge,
@@ -40,6 +45,8 @@
4045
DefaultAggregation,
4146
LastValueAggregation,
4247
)
48+
from opentelemetry.sdk.resources import Resource
49+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
4350
from opentelemetry.test.concurrency_test import ConcurrencyTestBase
4451

4552

@@ -48,7 +55,7 @@ def __init__(
4855
self, wait=0, preferred_temporality=None, preferred_aggregation=None
4956
):
5057
self.wait = wait
51-
self.metrics = []
58+
self.metrics: list[MetricsData] = []
5259
self._shutdown = False
5360
super().__init__(
5461
preferred_temporality=preferred_temporality,
@@ -57,13 +64,13 @@ def __init__(
5764

5865
def export(
5966
self,
60-
metrics_data: Sequence[Metric],
67+
metrics_data: MetricsData,
6168
timeout_millis: float = 10_000,
6269
**kwargs,
6370
) -> MetricExportResult:
6471
sleep(self.wait)
65-
self.metrics.extend(metrics_data)
66-
return True
72+
self.metrics.append(metrics_data)
73+
return MetricExportResult.SUCCESS
6774

6875
def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
6976
self._shutdown = True
@@ -126,6 +133,21 @@ def collect(self, timeout_millis: float = 10_000) -> None:
126133
),
127134
),
128135
]
136+
metrics = MetricsData(
137+
resource_metrics=[
138+
ResourceMetrics(
139+
scope_metrics=[
140+
ScopeMetrics(
141+
metrics=metrics_list,
142+
scope=InstrumentationScope(name="test"),
143+
schema_url="",
144+
)
145+
],
146+
resource=Resource.create(),
147+
schema_url="",
148+
)
149+
]
150+
)
129151

130152

131153
class TestPeriodicExportingMetricReader(ConcurrencyTestBase):
@@ -137,7 +159,12 @@ def test_defaults(self):
137159
pmr.shutdown()
138160

139161
def _create_periodic_reader(
140-
self, metrics, exporter, collect_wait=0, interval=60000, timeout=30000
162+
self,
163+
metrics_data: MetricsData,
164+
exporter,
165+
collect_wait=0,
166+
interval=60000,
167+
timeout=30000,
141168
):
142169
pmr = PeriodicExportingMetricReader(
143170
exporter,
@@ -147,7 +174,7 @@ def _create_periodic_reader(
147174

148175
def _collect(reader, timeout_millis):
149176
sleep(collect_wait)
150-
pmr._receive_metrics(metrics, timeout_millis)
177+
return metrics_data
151178

152179
pmr._set_collect_callback(_collect)
153180
return pmr
@@ -198,24 +225,26 @@ def test_ticker_value_exception_on_negative(self):
198225
def test_ticker_collects_metrics(self):
199226
exporter = FakeMetricsExporter()
200227

201-
pmr = self._create_periodic_reader(
202-
metrics_list, exporter, interval=100
203-
)
228+
pmr = self._create_periodic_reader(metrics, exporter, interval=100)
204229
sleep(0.15)
205-
self.assertEqual(exporter.metrics, metrics_list)
230+
self.assertEqual(exporter.metrics[0], metrics)
206231
pmr.shutdown()
207232

208233
def test_shutdown(self):
209234
exporter = FakeMetricsExporter()
210235

211-
pmr = self._create_periodic_reader([], exporter)
236+
pmr = self._create_periodic_reader(
237+
MetricsData(resource_metrics=[]), exporter
238+
)
212239
pmr.shutdown()
213-
self.assertEqual(exporter.metrics, [])
240+
self.assertEqual(exporter.metrics[0], MetricsData(resource_metrics=[]))
214241
self.assertTrue(pmr._shutdown)
215242
self.assertTrue(exporter._shutdown)
216243

217244
def test_shutdown_multiple_times(self):
218-
pmr = self._create_periodic_reader([], FakeMetricsExporter())
245+
pmr = self._create_periodic_reader(
246+
MetricsData(resource_metrics=[]), FakeMetricsExporter()
247+
)
219248
with self.assertLogs(level="WARNING") as w:
220249
self.run_with_many_threads(pmr.shutdown)
221250
self.assertTrue("Can't shutdown multiple times" in w.output[0])

0 commit comments

Comments
 (0)