From a03546228084ef0e5eb32e3a881df788e262f392 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 13 Apr 2026 12:56:37 +0200 Subject: [PATCH 1/2] opentelemetry-sdk: fix typing issues for metrics instruments --- .../_internal/_view_instrument_match.py | 4 +-- .../sdk/metrics/_internal/aggregation.py | 16 +++++----- .../sdk/metrics/_internal/instrument.py | 29 ++++++++++++++----- .../sdk/metrics/_internal/measurement.py | 10 +++++-- .../_internal/metric_reader_storage.py | 10 +++---- .../sdk/metrics/_internal/view.py | 3 +- 6 files changed, 46 insertions(+), 26 deletions(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py index 96a77fa6b15..3b4405ac4c0 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/_view_instrument_match.py @@ -18,7 +18,6 @@ from time import time_ns from typing import Dict, List, Optional, Sequence -from opentelemetry.metrics import Instrument from opentelemetry.sdk.metrics._internal.aggregation import ( Aggregation, AggregationTemporality, @@ -26,6 +25,7 @@ _Aggregation, _SumAggregation, ) +from opentelemetry.sdk.metrics._internal.instrument import _Instrument from opentelemetry.sdk.metrics._internal.measurement import Measurement from opentelemetry.sdk.metrics._internal.point import DataPointT from opentelemetry.sdk.metrics._internal.view import View @@ -37,7 +37,7 @@ class _ViewInstrumentMatch: def __init__( self, view: View, - instrument: Instrument, + instrument: _Instrument, instrument_class_aggregation: Dict[type, Aggregation], ): self._view = view diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py index 46c30f9049c..027ddf9b320 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/aggregation.py @@ -35,7 +35,6 @@ Asynchronous, Counter, Histogram, - Instrument, ObservableCounter, ObservableGauge, ObservableUpDownCounter, @@ -59,6 +58,7 @@ from opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.logarithm_mapping import ( LogarithmMapping, ) +from opentelemetry.sdk.metrics._internal.instrument import _Instrument from opentelemetry.sdk.metrics._internal.measurement import Measurement from opentelemetry.sdk.metrics._internal.point import Buckets as BucketsPoint from opentelemetry.sdk.metrics._internal.point import ( @@ -1200,7 +1200,7 @@ class Aggregation(ABC): @abstractmethod def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1231,7 +1231,7 @@ class DefaultAggregation(Aggregation): def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1323,7 +1323,7 @@ def __init__( def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1372,7 +1372,7 @@ def __init__( def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1416,7 +1416,7 @@ class SumAggregation(Aggregation): def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1450,7 +1450,7 @@ class LastValueAggregation(Aggregation): def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder @@ -1468,7 +1468,7 @@ class DropAggregation(Aggregation): def _create_aggregation( self, - instrument: Instrument, + instrument: _Instrument, attributes: Attributes, reservoir_factory: Callable[ [Type[_Aggregation]], ExemplarReservoirBuilder diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py index 08d1bc021c2..658bd5a4b09 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py @@ -17,11 +17,19 @@ from logging import getLogger from time import time_ns -from typing import TYPE_CHECKING, Generator, Iterable, List, Sequence, Union +from typing import ( + TYPE_CHECKING, + Generator, + Iterable, + List, + Sequence, + Union, + cast, +) # This kind of import is needed to avoid Sphinx errors. from opentelemetry.context import Context, get_current -from opentelemetry.metrics import CallbackT +from opentelemetry.metrics import Asynchronous, CallbackT, Synchronous from opentelemetry.metrics import Counter as APICounter from opentelemetry.metrics import Histogram as APIHistogram from opentelemetry.metrics import ObservableCounter as APIObservableCounter @@ -55,7 +63,14 @@ ) -class _Synchronous: +class _Instrument: + name: str + unit: str + description: str + instrumentation_scope: InstrumentationScope + + +class _Synchronous(_Instrument, Synchronous): def __init__( self, name: str, @@ -79,7 +94,7 @@ def __init__( name = result["name"] unit = result["unit"] - description = result["description"] + description = cast(str, result["description"]) self.name = name.lower() self.unit = unit @@ -93,13 +108,13 @@ def _is_enabled(self) -> bool: return self._meter_config is None or self._meter_config.is_enabled -class _Asynchronous: +class _Asynchronous(_Instrument, Asynchronous): def __init__( self, name: str, instrumentation_scope: InstrumentationScope, measurement_consumer: MeasurementConsumer, - callbacks: Iterable[CallbackT] | None = None, + callbacks: Sequence[CallbackT] | None = None, unit: str = "", description: str = "", *, @@ -118,7 +133,7 @@ def __init__( name = result["name"] unit = result["unit"] - description = result["description"] + description = cast(str, result["description"]) self.name = name.lower() self.unit = unit diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement.py index a73d6001a1a..1d2137be802 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/measurement.py @@ -12,13 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from dataclasses import dataclass -from typing import Union +from typing import TYPE_CHECKING, Union from opentelemetry.context import Context -from opentelemetry.metrics import Instrument from opentelemetry.util.types import Attributes +if TYPE_CHECKING: + from opentelemetry.sdk.metrics._internal.instrument import _Instrument + @dataclass(frozen=True) class Measurement: @@ -35,6 +39,6 @@ class Measurement: value: Union[int, float] time_unix_nano: int - instrument: Instrument + instrument: _Instrument context: Context attributes: Attributes = None diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py index 8b151da9765..93c2ebcc61d 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/metric_reader_storage.py @@ -20,7 +20,6 @@ from opentelemetry.metrics import ( Asynchronous, Counter, - Instrument, ObservableCounter, ) from opentelemetry.sdk.metrics._internal._view_instrument_match import ( @@ -36,6 +35,7 @@ _LastValueAggregation, _SumAggregation, ) +from opentelemetry.sdk.metrics._internal.instrument import _Instrument from opentelemetry.sdk.metrics._internal.measurement import Measurement from opentelemetry.sdk.metrics._internal.point import ( ExponentialHistogram, @@ -70,13 +70,13 @@ def __init__( self._lock = RLock() self._sdk_config = sdk_config self._instrument_view_instrument_matches: Dict[ - Instrument, List[_ViewInstrumentMatch] + _Instrument, List[_ViewInstrumentMatch] ] = {} self._instrument_class_temporality = instrument_class_temporality self._instrument_class_aggregation = instrument_class_aggregation def _get_or_init_view_instrument_match( - self, instrument: Instrument + self, instrument: _Instrument ) -> List[_ViewInstrumentMatch]: # Optimistically get the relevant views for the given instrument. Once set for a given # instrument, the mapping will never change @@ -253,7 +253,7 @@ def collect(self) -> Optional[MetricsData]: def _handle_view_instrument_match( self, - instrument: Instrument, + instrument: _Instrument, view_instrument_matches: List["_ViewInstrumentMatch"], ) -> None: for view in self._sdk_config.views: @@ -292,7 +292,7 @@ def _handle_view_instrument_match( @staticmethod def _check_view_instrument_compatibility( - view: View, instrument: Instrument + view: View, instrument: _Instrument ) -> bool: """ Checks if a view and an instrument are compatible. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/view.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/view.py index b3fa029d6c7..ef0b04950b4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/view.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/view.py @@ -30,6 +30,7 @@ ExemplarReservoirBuilder, SimpleFixedSizeExemplarReservoir, ) +from opentelemetry.sdk.metrics._internal.instrument import _Instrument _logger = getLogger(__name__) @@ -164,7 +165,7 @@ def __init__( # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches - def _match(self, instrument: Instrument) -> bool: + def _match(self, instrument: _Instrument) -> bool: if self._instrument_type is not None: if not isinstance(instrument, self._instrument_type): return False From f2bb361bae24230d702ab8d218bf51d12ec97ba7 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 15 Apr 2026 11:58:49 +0200 Subject: [PATCH 2/2] Make the _Instrument runtime checkable --- .../src/opentelemetry/sdk/metrics/_internal/instrument.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py index 658bd5a4b09..ecd1996d0a4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py @@ -22,9 +22,11 @@ Generator, Iterable, List, + Protocol, Sequence, Union, cast, + runtime_checkable, ) # This kind of import is needed to avoid Sphinx errors. @@ -63,7 +65,8 @@ ) -class _Instrument: +@runtime_checkable +class _Instrument(Protocol): name: str unit: str description: str