Skip to content

Commit ce15811

Browse files
committed
load container detector via entry point instead of lazy import
Matches the env-var config counterpart (OTEL_EXPERIMENTAL_RESOURCE_DETECTORS) and avoids a hard import dependency on the contrib package. Tests updated to mock entry_points instead of sys.modules. Assisted-by: Claude Sonnet 4.6
1 parent c59bdf1 commit ce15811

2 files changed

Lines changed: 32 additions & 29 deletions

File tree

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
SERVICE_NAME,
3232
Resource,
3333
)
34+
from opentelemetry.util._importlib_metadata import entry_points
3435

3536
_logger = logging.getLogger(__name__)
3637

@@ -151,26 +152,28 @@ def _run_detectors(
151152
"""
152153
if detector_config.container is not None:
153154
# The container detector is not part of the core SDK. It is provided
154-
# by the opentelemetry-resource-detector-containerid contrib package.
155-
# We attempt a lazy import so the core SDK has no hard dependency on
156-
# contrib; if the package is absent we log an actionable warning rather
157-
# than raising an error. Other SDKs (e.g. JS) similarly skip container
158-
# detection when no implementation is available. See also:
155+
# by the opentelemetry-resource-detector-containerid contrib package,
156+
# which registers itself under the opentelemetry_resource_detector
157+
# entry point group as "container". Loading via entry point matches
158+
# the env-var config counterpart (OTEL_EXPERIMENTAL_RESOURCE_DETECTORS)
159+
# and avoids a hard import dependency on contrib. See also:
159160
# https://github.com/open-telemetry/opentelemetry-configuration/issues/570
160-
try:
161-
from opentelemetry.resource.detector.containerid import ( # type: ignore[import-not-found] # noqa: PLC0415 # pylint: disable=import-outside-toplevel,no-name-in-module
162-
ContainerResourceDetector,
163-
)
164-
165-
detected_attrs.update(
166-
ContainerResourceDetector().detect().attributes
167-
)
168-
except ImportError:
161+
ep = next(
162+
iter(
163+
entry_points(
164+
group="opentelemetry_resource_detector", name="container"
165+
)
166+
),
167+
None,
168+
)
169+
if ep is None:
169170
_logger.warning(
170171
"container resource detector requested but "
171172
"'opentelemetry-resource-detector-containerid' is not "
172173
"installed; install it to enable container detection"
173174
)
175+
else:
176+
detected_attrs.update(ep.load()().detect().attributes)
174177

175178

176179
def _filter_attributes(

opentelemetry-sdk/tests/_configuration/test_resource.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,10 @@ def test_container_detector_not_run_when_detectors_list_empty(self):
327327
self.assertNotIn(CONTAINER_ID, resource.attributes)
328328

329329
def test_container_detector_warns_when_package_missing(self):
330-
"""A warning is logged when the contrib package is not installed."""
331-
with patch.dict(
332-
"sys.modules",
333-
{"opentelemetry.resource.detector.containerid": None},
330+
"""A warning is logged when the contrib entry point is not found."""
331+
with patch(
332+
"opentelemetry.sdk._configuration._resource.entry_points",
333+
return_value=[],
334334
):
335335
with self.assertLogs(
336336
"opentelemetry.sdk._configuration._resource", level="WARNING"
@@ -345,16 +345,16 @@ def test_container_detector_warns_when_package_missing(self):
345345
)
346346

347347
def test_container_detector_uses_contrib_when_available(self):
348-
"""When the contrib package is installed, container.id is detected."""
348+
"""When the contrib entry point is registered, container.id is detected."""
349349
mock_resource = Resource({CONTAINER_ID: "abc123"})
350350
mock_detector = MagicMock()
351351
mock_detector.return_value.detect.return_value = mock_resource
352-
mock_module = MagicMock()
353-
mock_module.ContainerResourceDetector = mock_detector
352+
mock_ep = MagicMock()
353+
mock_ep.load.return_value = mock_detector
354354

355-
with patch.dict(
356-
"sys.modules",
357-
{"opentelemetry.resource.detector.containerid": mock_module},
355+
with patch(
356+
"opentelemetry.sdk._configuration._resource.entry_points",
357+
return_value=[mock_ep],
358358
):
359359
resource = create_resource(self._config_with_container())
360360

@@ -365,8 +365,8 @@ def test_explicit_attributes_override_container_detector(self):
365365
mock_resource = Resource({CONTAINER_ID: "detected-id"})
366366
mock_detector = MagicMock()
367367
mock_detector.return_value.detect.return_value = mock_resource
368-
mock_module = MagicMock()
369-
mock_module.ContainerResourceDetector = mock_detector
368+
mock_ep = MagicMock()
369+
mock_ep.load.return_value = mock_detector
370370

371371
config = ResourceConfig(
372372
attributes=[
@@ -376,9 +376,9 @@ def test_explicit_attributes_override_container_detector(self):
376376
detectors=[ExperimentalResourceDetector(container={})]
377377
),
378378
)
379-
with patch.dict(
380-
"sys.modules",
381-
{"opentelemetry.resource.detector.containerid": mock_module},
379+
with patch(
380+
"opentelemetry.sdk._configuration._resource.entry_points",
381+
return_value=[mock_ep],
382382
):
383383
resource = create_resource(config)
384384

0 commit comments

Comments
 (0)