From 13d5735030649acf201e8b6722aefd1004fc1926 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Thu, 24 Aug 2023 00:00:11 +0000 Subject: [PATCH] Update opentelemetry_metrics_exporter entrypoint to a MetricReader factory --- CHANGELOG.md | 3 + .../pyproject.toml | 2 +- .../proto/grpc/metric_exporter/__init__.py | 8 ++- .../tests/test_entrypoints.py | 55 +++++++++++++++++++ .../pyproject.toml | 2 +- .../proto/http/metric_exporter/__init__.py | 9 ++- .../tests/test_entrypoints.py | 55 +++++++++++++++++++ .../pyproject.toml | 2 +- .../tests/test_entrypoints.py | 55 +++++++++++++++++++ opentelemetry-sdk/pyproject.toml | 2 +- .../sdk/_configuration/__init__.py | 45 ++++++++------- .../sdk/metrics/_internal/export/__init__.py | 7 ++- opentelemetry-sdk/tests/test_configurator.py | 28 +++++----- 13 files changed, 229 insertions(+), 44 deletions(-) create mode 100644 exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_entrypoints.py create mode 100644 exporter/opentelemetry-exporter-otlp-proto-http/tests/test_entrypoints.py create mode 100644 exporter/opentelemetry-exporter-otlp/tests/test_entrypoints.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b2c9ad40c34..4354e06433e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modify Prometheus exporter to translate non-monotonic Sums into Gauges ([#3306](https://github.com/open-telemetry/opentelemetry-python/pull/3306)) +- Update `opentelemetry_metrics_exporter` entrypoint to a MetricReader factory + ([#3412](https://github.com/open-telemetry/opentelemetry-python/pull/3412)) + ## Version 1.19.0/0.40b0 (2023-07-13) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml index 475c431aed5..2441252feb3 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/pyproject.toml @@ -45,7 +45,7 @@ test = [ otlp_proto_grpc = "opentelemetry.exporter.otlp.proto.grpc._log_exporter:OTLPLogExporter" [project.entry-points.opentelemetry_metrics_exporter] -otlp_proto_grpc = "opentelemetry.exporter.otlp.proto.grpc.metric_exporter:OTLPMetricExporter" +otlp_proto_grpc = "opentelemetry.exporter.otlp.proto.grpc.metric_exporter:_otlp_metric_exporter_entrypoint" [project.entry-points.opentelemetry_traces_exporter] otlp_proto_grpc = "opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter" diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py index 2560c5c3057..6fd573996fc 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py @@ -54,7 +54,9 @@ Metric, MetricExporter, MetricExportResult, + MetricReader, MetricsData, + PeriodicExportingMetricReader, ResourceMetrics, ScopeMetrics, ) @@ -102,7 +104,6 @@ def __init__( preferred_aggregation: Dict[type, Aggregation] = None, max_export_batch_size: Optional[int] = None, ): - if insecure is None: insecure = environ.get(OTEL_EXPORTER_OTLP_METRICS_INSECURE) if insecure is not None: @@ -260,3 +261,8 @@ def _exporting(self) -> str: def force_flush(self, timeout_millis: float = 10_000) -> bool: """Nothing is buffered in this exporter, so this method does nothing.""" return True + + +def _otlp_metric_exporter_entrypoint() -> MetricReader: + """Implementation of opentelemetry_metrics_exporter entrypoint""" + return PeriodicExportingMetricReader(OTLPMetricExporter()) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_entrypoints.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_entrypoints.py new file mode 100644 index 00000000000..ba6aade8f43 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_entrypoints.py @@ -0,0 +1,55 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase + +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + _otlp_metric_exporter_entrypoint, +) +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( + OTLPSpanExporter, +) +from opentelemetry.sdk._configuration import _import_exporters + + +class TestEntrypoints(TestCase): + def test_import_exporters(self) -> None: + """ + Tests that the entrypoints can be loaded and don't have a typo in the names + """ + ( + trace_exporters, + metric_exporters, + logs_exporters, + ) = _import_exporters( + trace_exporter_names=["otlp_proto_grpc"], + metric_exporter_names=["otlp_proto_grpc"], + log_exporter_names=["otlp_proto_grpc"], + ) + + self.assertEqual( + trace_exporters, + {"otlp_proto_grpc": OTLPSpanExporter}, + ) + self.assertEqual( + metric_exporters, + {"otlp_proto_grpc": _otlp_metric_exporter_entrypoint}, + ) + self.assertEqual( + logs_exporters, + {"otlp_proto_grpc": OTLPLogExporter}, + ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml b/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml index e3b7f36ada1..827c9806a6e 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp-proto-http/pyproject.toml @@ -45,7 +45,7 @@ test = [ otlp_proto_http = "opentelemetry.exporter.otlp.proto.http.trace_exporter:OTLPSpanExporter" [project.entry-points.opentelemetry_metrics_exporter] -otlp_proto_http = "opentelemetry.exporter.otlp.proto.http.metric_exporter:OTLPMetricExporter" +otlp_proto_http = "opentelemetry.exporter.otlp.proto.http.metric_exporter:_otlp_metric_exporter_entrypoint" [project.entry-points.opentelemetry_logs_exporter] otlp_proto_http = "opentelemetry.exporter.otlp.proto.http._log_exporter:OTLPLogExporter" diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py index 2c13601e0a1..c7d484fae7a 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py @@ -63,7 +63,9 @@ AggregationTemporality, MetricExporter, MetricExportResult, + MetricReader, MetricsData, + PeriodicExportingMetricReader, ) from opentelemetry.sdk.metrics.export import ( # noqa: F401 Gauge, @@ -101,7 +103,6 @@ def _expo(*args, **kwargs): class OTLPMetricExporter(MetricExporter, OTLPMetricExporterMixin): - _MAX_RETRY_TIMEOUT = 64 def __init__( @@ -182,7 +183,6 @@ def export( ) -> MetricExportResult: serialized_data = encode_metrics(metrics_data) for delay in _expo(max_value=self._MAX_RETRY_TIMEOUT): - if delay == self._MAX_RETRY_TIMEOUT: return MetricExportResult.FAILURE @@ -247,3 +247,8 @@ def _append_metrics_path(endpoint: str) -> str: if endpoint.endswith("/"): return endpoint + DEFAULT_METRICS_EXPORT_PATH return endpoint + f"/{DEFAULT_METRICS_EXPORT_PATH}" + + +def _otlp_metric_exporter_entrypoint() -> MetricReader: + """Implementation of opentelemetry_metrics_exporter entrypoint""" + return PeriodicExportingMetricReader(OTLPMetricExporter()) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_entrypoints.py b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_entrypoints.py new file mode 100644 index 00000000000..094d22e8b8d --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp-proto-http/tests/test_entrypoints.py @@ -0,0 +1,55 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase + +from opentelemetry.exporter.otlp.proto.http._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + _otlp_metric_exporter_entrypoint, +) +from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter, +) +from opentelemetry.sdk._configuration import _import_exporters + + +class TestEntrypoints(TestCase): + def test_import_exporters(self) -> None: + """ + Tests that the entrypoints can be loaded and don't have a typo in the names + """ + ( + trace_exporters, + metric_exporters, + logs_exporters, + ) = _import_exporters( + trace_exporter_names=["otlp_proto_http"], + metric_exporter_names=["otlp_proto_http"], + log_exporter_names=["otlp_proto_http"], + ) + + self.assertEqual( + trace_exporters, + {"otlp_proto_http": OTLPSpanExporter}, + ) + self.assertEqual( + metric_exporters, + {"otlp_proto_http": _otlp_metric_exporter_entrypoint}, + ) + self.assertEqual( + logs_exporters, + {"otlp_proto_http": OTLPLogExporter}, + ) diff --git a/exporter/opentelemetry-exporter-otlp/pyproject.toml b/exporter/opentelemetry-exporter-otlp/pyproject.toml index 61aeb44f0d0..827ba2060fd 100644 --- a/exporter/opentelemetry-exporter-otlp/pyproject.toml +++ b/exporter/opentelemetry-exporter-otlp/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ otlp = "opentelemetry.exporter.otlp.proto.grpc._log_exporter:OTLPLogExporter" [project.entry-points.opentelemetry_metrics_exporter] -otlp = "opentelemetry.exporter.otlp.proto.grpc.metric_exporter:OTLPMetricExporter" +otlp = "opentelemetry.exporter.otlp.proto.grpc.metric_exporter:_otlp_metric_exporter_entrypoint" [project.entry-points.opentelemetry_traces_exporter] otlp = "opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter" diff --git a/exporter/opentelemetry-exporter-otlp/tests/test_entrypoints.py b/exporter/opentelemetry-exporter-otlp/tests/test_entrypoints.py new file mode 100644 index 00000000000..9ab43ac2274 --- /dev/null +++ b/exporter/opentelemetry-exporter-otlp/tests/test_entrypoints.py @@ -0,0 +1,55 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase + +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( + OTLPLogExporter, +) +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + _otlp_metric_exporter_entrypoint, +) +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( + OTLPSpanExporter, +) +from opentelemetry.sdk._configuration import _import_exporters + + +class TestEntrypoints(TestCase): + def test_import_exporters(self) -> None: + """ + Tests that the entrypoints can be loaded and don't have a typo in the names + """ + ( + trace_exporters, + metric_exporters, + logs_exporters, + ) = _import_exporters( + trace_exporter_names=["otlp"], + metric_exporter_names=["otlp"], + log_exporter_names=["otlp"], + ) + + self.assertEqual( + trace_exporters, + {"otlp": OTLPSpanExporter}, + ) + self.assertEqual( + metric_exporters, + {"otlp": _otlp_metric_exporter_entrypoint}, + ) + self.assertEqual( + logs_exporters, + {"otlp": OTLPLogExporter}, + ) diff --git a/opentelemetry-sdk/pyproject.toml b/opentelemetry-sdk/pyproject.toml index f9edd23542e..783e1455a02 100644 --- a/opentelemetry-sdk/pyproject.toml +++ b/opentelemetry-sdk/pyproject.toml @@ -58,7 +58,7 @@ console = "opentelemetry.sdk._logs.export:ConsoleLogExporter" sdk_meter_provider = "opentelemetry.sdk.metrics:MeterProvider" [project.entry-points.opentelemetry_metrics_exporter] -console = "opentelemetry.sdk.metrics.export:ConsoleMetricExporter" +console = "opentelemetry.sdk.metrics._internal.export:_console_metric_exporter_entrypoint" [project.entry-points.opentelemetry_tracer_provider] sdk_tracer_provider = "opentelemetry.sdk.trace:TracerProvider" diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py index 958a50394e9..991458d216f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py @@ -45,10 +45,7 @@ OTEL_TRACES_SAMPLER_ARG, ) from opentelemetry.sdk.metrics import MeterProvider -from opentelemetry.sdk.metrics.export import ( - MetricExporter, - PeriodicExportingMetricReader, -) +from opentelemetry.sdk.metrics.export import MetricReader from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter @@ -84,13 +81,15 @@ _OTEL_SAMPLER_ENTRY_POINT_GROUP = "opentelemetry_traces_sampler" +# opentelemetry_metrics_exporter entrypoint implementations should implement this type +_MetricReaderFactory = Callable[[], MetricReader] + _logger = logging.getLogger(__name__) def _import_config_components( selected_components: List[str], entry_point_name: str ) -> Sequence[Tuple[str, object]]: - component_implementations = [] for selected_component in selected_components: @@ -108,13 +107,11 @@ def _import_config_components( ) ) except KeyError: - raise RuntimeError( f"Requested entry point '{entry_point_name}' not found" ) except StopIteration: - raise RuntimeError( f"Requested component '{selected_component}' not found in " f"entry point '{entry_point_name}'" @@ -210,16 +207,13 @@ def _init_tracing( def _init_metrics( - exporters: Dict[str, Type[MetricExporter]], + factories: Dict[str, _MetricReaderFactory], resource: Resource = None, ): metric_readers = [] - for _, exporter_class in exporters.items(): - exporter_args = {} - metric_readers.append( - PeriodicExportingMetricReader(exporter_class(**exporter_args)) - ) + for _, factory in factories.items(): + metric_readers.append(factory()) provider = MeterProvider(resource=resource, metric_readers=metric_readers) set_meter_provider(provider) @@ -249,11 +243,11 @@ def _import_exporters( log_exporter_names: Sequence[str], ) -> Tuple[ Dict[str, Type[SpanExporter]], - Dict[str, Type[MetricExporter]], + Dict[str, _MetricReaderFactory], Dict[str, Type[LogExporter]], ]: trace_exporters = {} - metric_exporters = {} + metric_reader_factories = {} log_exporters = {} for (exporter_name, exporter_impl,) in _import_config_components( @@ -264,13 +258,15 @@ def _import_exporters( else: raise RuntimeError(f"{exporter_name} is not a trace exporter") - for (exporter_name, exporter_impl,) in _import_config_components( + for exporter_name, metric_reader_factory in _import_config_components( metric_exporter_names, "opentelemetry_metrics_exporter" ): - if issubclass(exporter_impl, MetricExporter): - metric_exporters[exporter_name] = exporter_impl + if callable(metric_reader_factory): + metric_reader_factories[exporter_name] = metric_reader_factory else: - raise RuntimeError(f"{exporter_name} is not a metric exporter") + raise RuntimeError( + f"{exporter_name} is not a callable, should be a {_MetricReaderFactory}" + ) for (exporter_name, exporter_impl,) in _import_config_components( log_exporter_names, "opentelemetry_logs_exporter" @@ -280,7 +276,7 @@ def _import_exporters( else: raise RuntimeError(f"{exporter_name} is not a log exporter") - return trace_exporters, metric_exporters, log_exporters + return trace_exporters, metric_reader_factories, log_exporters def _import_sampler_factory(sampler_name: str) -> Callable[[str], Sampler]: @@ -335,7 +331,11 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator: def _initialize_components(auto_instrumentation_version): - trace_exporters, metric_exporters, log_exporters = _import_exporters( + ( + trace_exporters, + metric_reader_factories, + log_exporters, + ) = _import_exporters( _get_exporter_names("traces"), _get_exporter_names("metrics"), _get_exporter_names("logs"), @@ -360,7 +360,7 @@ def _initialize_components(auto_instrumentation_version): sampler=sampler, resource=resource, ) - _init_metrics(metric_exporters, resource) + _init_metrics(metric_reader_factories, resource) logging_enabled = os.getenv( _OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false" ) @@ -380,7 +380,6 @@ class _BaseConfigurator(ABC): _is_instrumented = False def __new__(cls, *args, **kwargs): - if cls._instance is None: cls._instance = object.__new__(cls, *args, **kwargs) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py index 5bd94d5aacc..281fe98d6b5 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/export/__init__.py @@ -392,7 +392,7 @@ def __init__( def get_metrics_data( self, - ) -> ("opentelemetry.sdk.metrics.export.MetricsData"): + ) -> "opentelemetry.sdk.metrics.export.MetricsData": """Reads and returns current metrics from the SDK""" with self._lock: self.collect() @@ -549,3 +549,8 @@ def force_flush(self, timeout_millis: float = 10_000) -> bool: super().force_flush(timeout_millis=timeout_millis) self._exporter.force_flush(timeout_millis=timeout_millis) return True + + +def _console_metric_exporter_entrypoint() -> MetricReader: + """Implementation of opentelemetry_metrics_exporter entrypoint for the console exporter""" + return PeriodicExportingMetricReader(ConsoleMetricExporter()) diff --git a/opentelemetry-sdk/tests/test_configurator.py b/opentelemetry-sdk/tests/test_configurator.py index e64e64ade0c..f74c9008c50 100644 --- a/opentelemetry-sdk/tests/test_configurator.py +++ b/opentelemetry-sdk/tests/test_configurator.py @@ -48,6 +48,9 @@ OTEL_TRACES_SAMPLER_ARG, ) from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics._internal.export import ( + PeriodicExportingMetricReader, +) from opentelemetry.sdk.metrics.export import ( AggregationTemporality, ConsoleMetricExporter, @@ -169,6 +172,10 @@ def shutdown(self): pass +def dummy_otlp_metric_exporter_factory() -> MetricReader: + return DummyMetricReader(DummyOTLPMetricExporter()) + + class Exporter: def __init__(self): tracer_provider = trace.get_tracer_provider() @@ -309,7 +316,6 @@ def tearDown(self): environ, {"OTEL_RESOURCE_ATTRIBUTES": "service.name=my-test-service"} ) def test_trace_init_default(self): - auto_resource = Resource.create( { "telemetry.auto.version": "test-version", @@ -682,10 +688,6 @@ def test_initialize_components_resource( class TestMetricsInit(TestCase): def setUp(self): - self.metric_reader_patch = patch( - "opentelemetry.sdk._configuration.PeriodicExportingMetricReader", - DummyMetricReader, - ) self.provider_patch = patch( "opentelemetry.sdk._configuration.MeterProvider", DummyMeterProvider, @@ -694,12 +696,10 @@ def setUp(self): "opentelemetry.sdk._configuration.set_meter_provider" ) - self.metric_reader_mock = self.metric_reader_patch.start() self.provider_mock = self.provider_patch.start() self.set_provider_mock = self.set_provider_patch.start() def tearDown(self): - self.metric_reader_patch.stop() self.set_provider_patch.stop() self.provider_patch.stop() @@ -727,7 +727,9 @@ def test_metrics_init_empty(self): ) def test_metrics_init_exporter(self): resource = Resource.create({}) - _init_metrics({"otlp": DummyOTLPMetricExporter}, resource=resource) + _init_metrics( + {"otlp": dummy_otlp_metric_exporter_factory}, resource=resource + ) self.assertEqual(self.set_provider_mock.call_count, 1) provider = self.set_provider_mock.call_args[0][0] self.assertIsInstance(provider, DummyMeterProvider) @@ -830,10 +832,12 @@ def test_console_exporters(self): self.assertEqual( logs_exporters["console"].__class__, ConsoleLogExporter.__class__ ) - self.assertEqual( - metric_exporterts["console"].__class__, - ConsoleMetricExporter.__class__, + metric_reader = metric_exporterts["console"]() + self.assertIsInstance( + metric_reader, + PeriodicExportingMetricReader, ) + self.assertIsInstance(metric_reader._exporter, ConsoleMetricExporter) class TestImportConfigComponents(TestCase): @@ -844,7 +848,6 @@ class TestImportConfigComponents(TestCase): def test__import_config_components_missing_entry_point( self, mock_entry_points ): - with raises(RuntimeError) as error: _import_config_components(["a", "b", "c"], "name") self.assertEqual( @@ -858,7 +861,6 @@ def test__import_config_components_missing_entry_point( def test__import_config_components_missing_component( self, mock_entry_points ): - with raises(RuntimeError) as error: _import_config_components(["a", "b", "c"], "name") self.assertEqual(