Skip to content
Draft
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- `opentelemetry-sdk`: add propagator plugin loading to declarative file configuration via the `opentelemetry_propagator` entry point group, matching the spec's PluginComponentProvider mechanism
([#5070](https://github.com/open-telemetry/opentelemetry-python/pull/5070))
- `opentelemetry-sdk`: add `load_entry_point` shared utility to declarative file configuration for loading plugins via entry points; refactor propagator loading to use it
([#5093](https://github.com/open-telemetry/opentelemetry-python/pull/5093))
- `opentelemetry-sdk`: fix YAML structure injection via environment variable substitution in declarative file configuration; values containing newlines are now emitted as quoted YAML scalars per spec requirement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,33 @@
from opentelemetry.sdk._configuration.models import (
Propagator as PropagatorConfig,
)
from opentelemetry.sdk._configuration.models import (
TextMapPropagator as TextMapPropagatorConfig,
)
from opentelemetry.trace.propagation.tracecontext import (
TraceContextTextMapPropagator,
)


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


def _propagators_from_textmap_config(
config: TextMapPropagatorConfig,
config: dict,
) -> list[TextMapPropagator]:
"""Resolve a single TextMapPropagator config entry to a list of propagators."""
"""Resolve a TextMapPropagator config dict to a list of propagators.

Each key in the dict names a propagator. Known names (tracecontext, baggage)
are bootstrapped directly. All other names — including b3, b3multi, and
custom plugin propagators — are loaded via the ``opentelemetry_propagator``
entry point group, matching the spec's PluginComponentProvider mechanism.
"""
result: list[TextMapPropagator] = []
if config.tracecontext is not None:
result.append(TraceContextTextMapPropagator())
if config.baggage is not None:
result.append(W3CBaggagePropagator())
if config.b3 is not None:
result.append(_load_entry_point_propagator("b3"))
if config.b3multi is not None:
result.append(_load_entry_point_propagator("b3multi"))
for name, prop_config in config.items():
if name in _PROPAGATOR_REGISTRY:
result.append(_PROPAGATOR_REGISTRY[name](prop_config))
else:
result.append(load_entry_point("opentelemetry_propagator", name)())
return result


Expand Down Expand Up @@ -85,7 +86,7 @@ def create_propagator(
name = name.strip()
if not name or name.lower() == "none":
continue
propagator = _load_entry_point_propagator(name)
propagator = load_entry_point("opentelemetry_propagator", name)()
propagators.setdefault(type(propagator), propagator)

return CompositePropagator(list(propagators.values()))
Expand Down
61 changes: 34 additions & 27 deletions opentelemetry-sdk/tests/_configuration/test_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@
from opentelemetry.sdk._configuration.models import (
Propagator as PropagatorConfig,
)
from opentelemetry.sdk._configuration.models import (
TextMapPropagator as TextMapPropagatorConfig,
)
from opentelemetry.trace.propagation.tracecontext import (
TraceContextTextMapPropagator,
)
Expand All @@ -50,9 +47,7 @@ def test_empty_config_returns_empty_composite(self):
self.assertEqual(result._propagators, []) # type: ignore[attr-defined]

def test_tracecontext_only(self):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(tracecontext={})]
)
config = PropagatorConfig(composite=[{"tracecontext": {}}])
result = create_propagator(config)
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
self.assertIsInstance(
Expand All @@ -61,18 +56,16 @@ def test_tracecontext_only(self):
)

def test_baggage_only(self):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(baggage={})]
)
config = PropagatorConfig(composite=[{"baggage": {}}])
result = create_propagator(config)
self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
self.assertIsInstance(result._propagators[0], W3CBaggagePropagator) # type: ignore[attr-defined]

def test_tracecontext_and_baggage(self):
config = PropagatorConfig(
composite=[
TextMapPropagatorConfig(tracecontext={}),
TextMapPropagatorConfig(baggage={}),
{"tracecontext": {}},
{"baggage": {}},
]
)
result = create_propagator(config)
Expand All @@ -92,9 +85,7 @@ def test_b3_via_entry_point(self):
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[mock_ep],
):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(b3={})]
)
config = PropagatorConfig(composite=[{"b3": {}}])
result = create_propagator(config)

self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
Expand All @@ -109,9 +100,7 @@ def test_b3multi_via_entry_point(self):
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[mock_ep],
):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(b3multi={})]
)
config = PropagatorConfig(composite=[{"b3multi": {}}])
result = create_propagator(config)

self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
Expand All @@ -121,9 +110,7 @@ def test_b3_not_installed_raises_configuration_error(self):
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[],
):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(b3={})]
)
config = PropagatorConfig(composite=[{"b3": {}}])
with self.assertRaises(ConfigurationError) as ctx:
create_propagator(config)
self.assertIn("b3", str(ctx.exception))
Expand Down Expand Up @@ -214,7 +201,7 @@ def test_deduplication_across_composite_and_composite_list(self):
return_value=[mock_ep],
):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(tracecontext={})],
composite=[{"tracecontext": {}}],
composite_list="tracecontext",
)
result = create_propagator(config)
Expand All @@ -236,6 +223,30 @@ def test_unknown_composite_list_propagator_raises(self):
with self.assertRaises(ConfigurationError):
create_propagator(config)

def test_plugin_propagator_via_entry_point(self):
mock_propagator = MagicMock()
mock_ep = MagicMock()
mock_ep.load.return_value = lambda: mock_propagator

with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[mock_ep],
):
config = PropagatorConfig(composite=[{"my_custom_propagator": {}}])
result = create_propagator(config)

self.assertEqual(len(result._propagators), 1) # type: ignore[attr-defined]
self.assertIs(result._propagators[0], mock_propagator) # type: ignore[attr-defined]

def test_unknown_composite_propagator_raises(self):
with patch(
"opentelemetry.sdk._configuration._common.entry_points",
return_value=[],
):
config = PropagatorConfig(composite=[{"nonexistent": {}}])
with self.assertRaises(ConfigurationError):
create_propagator(config)


class TestConfigurePropagator(unittest.TestCase):
def test_configure_propagator_calls_set_global_textmap(self):
Expand All @@ -248,9 +259,7 @@ def test_configure_propagator_calls_set_global_textmap(self):
self.assertIsInstance(arg, CompositePropagator)

def test_configure_propagator_with_config(self):
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(tracecontext={})]
)
config = PropagatorConfig(composite=[{"tracecontext": {}}])
with patch(
"opentelemetry.sdk._configuration._propagator.set_global_textmap"
) as mock_set:
Expand All @@ -263,9 +272,7 @@ def test_configure_propagator_with_config(self):
@patch.dict(environ, {OTEL_PROPAGATORS: "baggage"})
def test_otel_propagators_env_var_ignored(self):
"""OTEL_PROPAGATORS env var must not influence configure_propagator output."""
config = PropagatorConfig(
composite=[TextMapPropagatorConfig(tracecontext={})]
)
config = PropagatorConfig(composite=[{"tracecontext": {}}])
with patch(
"opentelemetry.sdk._configuration._propagator.set_global_textmap"
) as mock_set:
Expand Down
Loading