Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions opentelemetry-sdk/codegen/dataclass.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
JSON Schema additionalProperties. When a schema type allows additional
properties (e.g. Sampler, SpanExporter), this template adds:
- @_additional_properties decorator (captures user-defined kwargs)
- additional_properties: ClassVar[dict[str, Any]] annotation (for type checkers)
- additional_properties instance field matching the decorator runtime behavior

The template checks two context variables set by datamodel-codegen:
- additionalProperties: set when the schema value is a boolean (true/false)
Expand Down Expand Up @@ -69,5 +69,5 @@ class {{ class_name }}:
{%- endif %}
{%- endfor -%}
{%- if has_additional %}
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(default_factory=dict, init=False)
{%- endif -%}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _additional_properties(cls):
"""
original_init = cls.__init__
original_sig = inspect.signature(original_init)
known_fields = frozenset(f.name for f in dataclasses.fields(cls))
known_fields = frozenset(f.name for f in dataclasses.fields(cls) if f.init)

def _init(self, **kwargs):
known = {k: v for k, v in kwargs.items() if k in known_fields}
Expand Down Expand Up @@ -81,10 +81,8 @@ class _ComponentConfig(Protocol):
for ``**kwargs`` splatting to the user-defined component class) or
``None`` (when the YAML uses ``my_plugin:`` or ``my_plugin: null``).

Note: the generated models declare ``additional_properties`` as a
``ClassVar`` even though the decorator assigns it as an instance
attribute at runtime. This is tolerated by pyright in ``standard``
mode but flagged in ``strict`` mode. See #5268.
The generated models declare ``additional_properties`` as an instance
field to match the decorator's runtime assignment.
"""

additional_properties: dict[str, dict[str, Any] | None]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,20 @@ def _run_detectors(
The detected_attrs dict is updated in-place; later detectors overwrite
earlier ones for the same key.
"""
for name in dataclasses.fields(detector_config):
value = getattr(detector_config, name.name, None)
for field in dataclasses.fields(detector_config):
if not field.init:
continue

value = getattr(detector_config, field.name, None)
if value is None:
continue
if name.name in _RESOURCE_DETECTOR_REGISTRY:
if field.name in _RESOURCE_DETECTOR_REGISTRY:
detected_attrs.update(
_RESOURCE_DETECTOR_REGISTRY[name.name](value)
_RESOURCE_DETECTOR_REGISTRY[field.name](value)
)
else:
cls = load_entry_point(
"opentelemetry_resource_detector", name.name
"opentelemetry_resource_detector", field.name
)
detected_attrs.update(cls(**(value or {})).detect().attributes)

Expand Down
52 changes: 38 additions & 14 deletions opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, ClassVar, TypeAlias
from typing import Any, TypeAlias

from opentelemetry.sdk._configuration._common import _additional_properties

Expand Down Expand Up @@ -359,7 +359,9 @@ class SpanExporter:
otlp_grpc: OtlpGrpcExporter | None = None
otlp_file_development: ExperimentalOtlpFileExporter | None = None
console: ConsoleExporter | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


class SpanKind(Enum):
Expand Down Expand Up @@ -511,7 +513,9 @@ class ExperimentalResourceDetector:
host: ExperimentalHostResourceDetector | None = None
process: ExperimentalProcessResourceDetector | None = None
service: ExperimentalServiceResourceDetector | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand All @@ -537,21 +541,27 @@ class LogRecordExporter:
otlp_grpc: OtlpGrpcExporter | None = None
otlp_file_development: ExperimentalOtlpFileExporter | None = None
console: ConsoleExporter | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@_additional_properties
@dataclass
class MetricProducer:
opencensus: OpenCensusMetricProducer | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@_additional_properties
@dataclass
class PullMetricExporter:
prometheus_development: ExperimentalPrometheusMetricExporter | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand All @@ -568,7 +578,9 @@ class PushMetricExporter:
otlp_grpc: OtlpGrpcMetricExporter | None = None
otlp_file_development: ExperimentalOtlpFileMetricExporter | None = None
console: ConsoleMetricExporter | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand All @@ -586,7 +598,9 @@ class SimpleSpanProcessor:
class SpanProcessor:
batch: BatchSpanProcessor | None = None
simple: SimpleSpanProcessor | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@_additional_properties
Expand All @@ -596,7 +610,9 @@ class TextMapPropagator:
baggage: BaggagePropagator | None = None
b3: B3Propagator | None = None
b3multi: B3MultiPropagator | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand Down Expand Up @@ -662,7 +678,9 @@ class ExperimentalResourceDetection:
class LogRecordProcessor:
batch: BatchLogRecordProcessor | None = None
simple: SimpleLogRecordProcessor | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand Down Expand Up @@ -731,7 +749,9 @@ class OpenTelemetryConfiguration:
resource: Resource | None = None
instrumentation_development: ExperimentalInstrumentation | None = None
distribution: Distribution | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand Down Expand Up @@ -773,7 +793,9 @@ class ExperimentalComposableSampler:
)
probability: ExperimentalComposableProbabilitySampler | None = None
rule_based: ExperimentalComposableRuleBasedSampler | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand Down Expand Up @@ -802,7 +824,9 @@ class Sampler:
parent_based: ParentBasedSampler | None = None
probability_development: ExperimentalProbabilitySampler | None = None
trace_id_ratio_based: TraceIdRatioBasedSampler | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)


@dataclass
Expand Down
24 changes: 20 additions & 4 deletions opentelemetry-sdk/tests/_configuration/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import inspect
import unittest
from dataclasses import dataclass
from dataclasses import dataclass, field, fields
from types import SimpleNamespace
from typing import Any, ClassVar
from typing import Any
from unittest.mock import MagicMock, patch

from opentelemetry.sdk._configuration._common import (
Expand Down Expand Up @@ -213,7 +213,9 @@ def setUp(self):
class _SampleConfig:
known_field: dict | None = None
another_field: str | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)

self.cls = _SampleConfig

Expand Down Expand Up @@ -265,11 +267,23 @@ class TestGeneratedModelsHaveAdditionalProperties(unittest.TestCase):
def _assert_supports_additional_properties(self, model_cls):
# pylint: disable=unexpected-keyword-arg
obj = model_cls(_test_plugin_key={})
other = model_cls()
self.assertTrue(
hasattr(obj, "additional_properties"),
f"{model_cls.__name__} missing additional_properties attribute",
)
self.assertIn("_test_plugin_key", obj.additional_properties)
self.assertEqual(other.additional_properties, {})
self.assertIsNot(
obj.additional_properties, other.additional_properties
)

additional_properties_field = next(
field
for field in fields(model_cls)
if field.name == "additional_properties"
)
self.assertFalse(additional_properties_field.init)

def test_sampler(self):
self._assert_supports_additional_properties(Sampler)
Expand Down Expand Up @@ -299,7 +313,9 @@ def setUp(self):
class _Config:
builtin_a: dict | None = None
builtin_b: str | None = None
additional_properties: ClassVar[dict[str, Any]]
additional_properties: dict[str, dict[str, Any] | None] = field(
default_factory=dict, init=False
)

self.cls = _Config
self.registry = {
Expand Down