Skip to content

Commit 78e880f

Browse files
committed
add propagator plugin loading to declarative config via entry points
TextMapPropagator is changed from @DataClass to TypeAlias = dict[str, Any] in models.py, preserving unknown propagator names (plugin names) as dict keys through the config pipeline — same approach as Sampler. _propagators_from_textmap_config() now iterates the dict's key-value pairs directly. Known names (tracecontext, baggage) are bootstrapped from _PROPAGATOR_REGISTRY. All other names — including b3, b3multi, and custom plugin propagators — fall back to load_entry_point("opentelemetry_propagator", name), matching the spec's PluginComponentProvider mechanism. Assisted-by: Claude Sonnet 4.6
1 parent a3ad87d commit 78e880f

4 files changed

Lines changed: 59 additions & 51 deletions

File tree

CHANGELOG.md

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

1313
## Unreleased
1414

15+
- `opentelemetry-sdk`: add propagator plugin loading to declarative file configuration via the `opentelemetry_propagator` entry point group, matching the spec's PluginComponentProvider mechanism
16+
([#5070](https://github.com/open-telemetry/opentelemetry-python/pull/5070))
1517
- `opentelemetry-sdk`: add `load_entry_point` shared utility to declarative file configuration for loading plugins via entry points; refactor propagator loading to use it
1618
([#5093](https://github.com/open-telemetry/opentelemetry-python/pull/5093))
1719
- `opentelemetry-sdk`: Add `create_logger_provider`/`configure_logger_provider` to declarative file configuration, enabling LoggerProvider instantiation from config files without reading env vars

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_propagator.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,33 @@
2424
from opentelemetry.sdk._configuration.models import (
2525
Propagator as PropagatorConfig,
2626
)
27-
from opentelemetry.sdk._configuration.models import (
28-
TextMapPropagator as TextMapPropagatorConfig,
29-
)
3027
from opentelemetry.trace.propagation.tracecontext import (
3128
TraceContextTextMapPropagator,
3229
)
3330

34-
35-
def _load_entry_point_propagator(name: str) -> TextMapPropagator:
36-
"""Load and instantiate a propagator by name."""
37-
return load_entry_point("opentelemetry_propagator", name)()
31+
# Propagators bundled with the SDK — no entry point lookup needed.
32+
_PROPAGATOR_REGISTRY: dict = {
33+
"tracecontext": lambda _: TraceContextTextMapPropagator(),
34+
"baggage": lambda _: W3CBaggagePropagator(),
35+
}
3836

3937

4038
def _propagators_from_textmap_config(
41-
config: TextMapPropagatorConfig,
39+
config: dict,
4240
) -> list[TextMapPropagator]:
43-
"""Resolve a single TextMapPropagator config entry to a list of propagators."""
41+
"""Resolve a TextMapPropagator config dict to a list of propagators.
42+
43+
Each key in the dict names a propagator. Known names (tracecontext, baggage)
44+
are bootstrapped directly. All other names — including b3, b3multi, and
45+
custom plugin propagators — are loaded via the ``opentelemetry_propagator``
46+
entry point group, matching the spec's PluginComponentProvider mechanism.
47+
"""
4448
result: list[TextMapPropagator] = []
45-
if config.tracecontext is not None:
46-
result.append(TraceContextTextMapPropagator())
47-
if config.baggage is not None:
48-
result.append(W3CBaggagePropagator())
49-
if config.b3 is not None:
50-
result.append(_load_entry_point_propagator("b3"))
51-
if config.b3multi is not None:
52-
result.append(_load_entry_point_propagator("b3multi"))
49+
for name, prop_config in config.items():
50+
if name in _PROPAGATOR_REGISTRY:
51+
result.append(_PROPAGATOR_REGISTRY[name](prop_config))
52+
else:
53+
result.append(load_entry_point("opentelemetry_propagator", name)())
5354
return result
5455

5556

@@ -85,7 +86,7 @@ def create_propagator(
8586
name = name.strip()
8687
if not name or name.lower() == "none":
8788
continue
88-
propagator = _load_entry_point_propagator(name)
89+
propagator = load_entry_point("opentelemetry_propagator", name)()
8990
propagators.setdefault(type(propagator), propagator)
9091

9192
return CompositePropagator(list(propagators.values()))

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/models.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,12 +573,10 @@ class SpanProcessor:
573573
simple: SimpleSpanProcessor | None = None
574574

575575

576-
@dataclass
577-
class TextMapPropagator:
578-
tracecontext: TraceContextPropagator | None = None
579-
baggage: BaggagePropagator | None = None
580-
b3: B3Propagator | None = None
581-
b3multi: B3MultiPropagator | None = None
576+
# Diverges from codegen: TextMapPropagator is typed as dict[str, Any] rather
577+
# than a dataclass so that unknown propagator names (plugin/custom propagators)
578+
# are preserved as dict keys through the config pipeline.
579+
TextMapPropagator: TypeAlias = dict[str, Any]
582580

583581

584582
@dataclass

opentelemetry-sdk/tests/_configuration/test_propagator.py

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@
3030
from opentelemetry.sdk._configuration.models import (
3131
Propagator as PropagatorConfig,
3232
)
33-
from opentelemetry.sdk._configuration.models import (
34-
TextMapPropagator as TextMapPropagatorConfig,
35-
)
3633
from opentelemetry.trace.propagation.tracecontext import (
3734
TraceContextTextMapPropagator,
3835
)
@@ -50,9 +47,7 @@ def test_empty_config_returns_empty_composite(self):
5047
self.assertEqual(result._propagators, []) # type: ignore[attr-defined]
5148

5249
def test_tracecontext_only(self):
53-
config = PropagatorConfig(
54-
composite=[TextMapPropagatorConfig(tracecontext={})]
55-
)
50+
config = PropagatorConfig(composite=[{"tracecontext": {}}])
5651
result = create_propagator(config)
5752
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
5853
self.assertIsInstance(
@@ -61,18 +56,16 @@ def test_tracecontext_only(self):
6156
)
6257

6358
def test_baggage_only(self):
64-
config = PropagatorConfig(
65-
composite=[TextMapPropagatorConfig(baggage={})]
66-
)
59+
config = PropagatorConfig(composite=[{"baggage": {}}])
6760
result = create_propagator(config)
6861
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
6962
self.assertIsInstance(result._propagators[0], W3CBaggagePropagator) # type: ignore[attr-defined]
7063

7164
def test_tracecontext_and_baggage(self):
7265
config = PropagatorConfig(
7366
composite=[
74-
TextMapPropagatorConfig(tracecontext={}),
75-
TextMapPropagatorConfig(baggage={}),
67+
{"tracecontext": {}},
68+
{"baggage": {}},
7669
]
7770
)
7871
result = create_propagator(config)
@@ -92,9 +85,7 @@ def test_b3_via_entry_point(self):
9285
"opentelemetry.sdk._configuration._common.entry_points",
9386
return_value=[mock_ep],
9487
):
95-
config = PropagatorConfig(
96-
composite=[TextMapPropagatorConfig(b3={})]
97-
)
88+
config = PropagatorConfig(composite=[{"b3": {}}])
9889
result = create_propagator(config)
9990

10091
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
@@ -109,9 +100,7 @@ def test_b3multi_via_entry_point(self):
109100
"opentelemetry.sdk._configuration._common.entry_points",
110101
return_value=[mock_ep],
111102
):
112-
config = PropagatorConfig(
113-
composite=[TextMapPropagatorConfig(b3multi={})]
114-
)
103+
config = PropagatorConfig(composite=[{"b3multi": {}}])
115104
result = create_propagator(config)
116105

117106
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
@@ -121,9 +110,7 @@ def test_b3_not_installed_raises_configuration_error(self):
121110
"opentelemetry.sdk._configuration._common.entry_points",
122111
return_value=[],
123112
):
124-
config = PropagatorConfig(
125-
composite=[TextMapPropagatorConfig(b3={})]
126-
)
113+
config = PropagatorConfig(composite=[{"b3": {}}])
127114
with self.assertRaises(ConfigurationError) as ctx:
128115
create_propagator(config)
129116
self.assertIn("b3", str(ctx.exception))
@@ -214,7 +201,7 @@ def test_deduplication_across_composite_and_composite_list(self):
214201
return_value=[mock_ep],
215202
):
216203
config = PropagatorConfig(
217-
composite=[TextMapPropagatorConfig(tracecontext={})],
204+
composite=[{"tracecontext": {}}],
218205
composite_list="tracecontext",
219206
)
220207
result = create_propagator(config)
@@ -236,6 +223,30 @@ def test_unknown_composite_list_propagator_raises(self):
236223
with self.assertRaises(ConfigurationError):
237224
create_propagator(config)
238225

226+
def test_plugin_propagator_via_entry_point(self):
227+
mock_propagator = MagicMock()
228+
mock_ep = MagicMock()
229+
mock_ep.load.return_value = lambda: mock_propagator
230+
231+
with patch(
232+
"opentelemetry.sdk._configuration._common.entry_points",
233+
return_value=[mock_ep],
234+
):
235+
config = PropagatorConfig(composite=[{"my_custom_propagator": {}}])
236+
result = create_propagator(config)
237+
238+
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
239+
self.assertIs(result._propagators[0], mock_propagator) # type: ignore[attr-defined]
240+
241+
def test_unknown_composite_propagator_raises(self):
242+
with patch(
243+
"opentelemetry.sdk._configuration._common.entry_points",
244+
return_value=[],
245+
):
246+
config = PropagatorConfig(composite=[{"nonexistent": {}}])
247+
with self.assertRaises(ConfigurationError):
248+
create_propagator(config)
249+
239250

240251
class TestConfigurePropagator(unittest.TestCase):
241252
def test_configure_propagator_calls_set_global_textmap(self):
@@ -248,9 +259,7 @@ def test_configure_propagator_calls_set_global_textmap(self):
248259
self.assertIsInstance(arg, CompositePropagator)
249260

250261
def test_configure_propagator_with_config(self):
251-
config = PropagatorConfig(
252-
composite=[TextMapPropagatorConfig(tracecontext={})]
253-
)
262+
config = PropagatorConfig(composite=[{"tracecontext": {}}])
254263
with patch(
255264
"opentelemetry.sdk._configuration._propagator.set_global_textmap"
256265
) as mock_set:
@@ -263,9 +272,7 @@ def test_configure_propagator_with_config(self):
263272
@patch.dict(environ, {OTEL_PROPAGATORS: "baggage"})
264273
def test_otel_propagators_env_var_ignored(self):
265274
"""OTEL_PROPAGATORS env var must not influence configure_propagator output."""
266-
config = PropagatorConfig(
267-
composite=[TextMapPropagatorConfig(tracecontext={})]
268-
)
275+
config = PropagatorConfig(composite=[{"tracecontext": {}}])
269276
with patch(
270277
"opentelemetry.sdk._configuration._propagator.set_global_textmap"
271278
) as mock_set:

0 commit comments

Comments
 (0)