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
1 change: 1 addition & 0 deletions .changelog/5229.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: add `MissingDependencyError` exception for declarative configuration and use it for missing optional dependency errors
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,37 @@ class ConfigurationError(Exception):
- Environment variable substitution errors
- Missing required SDK extensions (e.g., propagator packages not installed)
"""


class MissingDependencyError(ConfigurationError):
"""Raised when an optional dependency is not installed."""

def __init__(
self,
package: str,
feature: str | None = None,
install_name: str | None = None,
extras: str | None = None,
) -> None:
self.package = package
self.feature = feature
self.install_name = install_name or package
self.extras = extras

if extras:
install_cmd = f"pip install '{self.install_name}[{extras}]'"
else:
install_cmd = f"pip install {self.install_name}"

if feature:
message = (
f"{feature} requires '{package}'. "
f"Install it with: {install_cmd}"
)
else:
message = (
f"'{package}' is required but not installed. "
f"Install it with: {install_cmd}"
)

super().__init__(message)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)
from opentelemetry.sdk._configuration.models import (
BatchLogRecordProcessor as BatchLogRecordProcessorConfig,
)
Expand Down Expand Up @@ -69,9 +72,9 @@ def _create_otlp_http_log_exporter(
OTLPLogExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_http log exporter requires 'opentelemetry-exporter-otlp-proto-http'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-http",
feature="otlp_http log exporter",
) from exc

compression = _map_compression(
Expand Down Expand Up @@ -100,9 +103,9 @@ def _create_otlp_grpc_log_exporter(
OTLPLogExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_grpc log exporter requires 'opentelemetry-exporter-otlp-proto-grpc'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-grpc",
feature="otlp_grpc log exporter",
) from exc

compression = _map_compression(config.compression, grpc.Compression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)
from opentelemetry.sdk._configuration.models import (
Aggregation as AggregationConfig,
)
Expand Down Expand Up @@ -279,9 +282,9 @@ def _create_otlp_http_metric_exporter(
OTLPMetricExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_http metric exporter requires 'opentelemetry-exporter-otlp-proto-http'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-http",
feature="otlp_http metric exporter",
) from exc

compression = _map_compression(
Expand Down Expand Up @@ -316,9 +319,9 @@ def _create_otlp_grpc_metric_exporter(
OTLPMetricExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_grpc metric exporter requires 'opentelemetry-exporter-otlp-proto-grpc'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-grpc",
feature="otlp_grpc metric exporter",
) from exc

compression = _map_compression(config.compression, grpc.Compression)
Expand Down Expand Up @@ -417,10 +420,9 @@ def _create_prometheus_metric_reader(
start_http_server,
)
except ImportError as exc:
raise ConfigurationError(
"prometheus pull metric exporter requires "
"'opentelemetry-exporter-prometheus'. "
"Install it with: pip install opentelemetry-exporter-prometheus"
raise MissingDependencyError(
package="opentelemetry-exporter-prometheus",
feature="prometheus pull metric exporter",
) from exc

disable_target_info = bool(config.without_target_info_development)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
_parse_headers,
load_entry_point,
)
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)
from opentelemetry.sdk._configuration.models import (
OtlpGrpcExporter as OtlpGrpcExporterConfig,
)
Expand Down Expand Up @@ -79,9 +82,9 @@ def _create_otlp_http_span_exporter(
OTLPSpanExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_http span exporter requires 'opentelemetry-exporter-otlp-proto-http'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-http",
feature="otlp_http span exporter",
) from exc

compression = _map_compression(
Expand Down Expand Up @@ -110,9 +113,9 @@ def _create_otlp_grpc_span_exporter(
OTLPSpanExporter,
)
except ImportError as exc:
raise ConfigurationError(
"otlp_grpc span exporter requires 'opentelemetry-exporter-otlp-proto-grpc'. "
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
raise MissingDependencyError(
package="opentelemetry-exporter-otlp-proto-grpc",
feature="otlp_grpc span exporter",
) from exc

compression = _map_compression(config.compression, grpc.Compression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
'1.0'
"""

from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)
from opentelemetry.sdk._configuration._logger_provider import (
configure_logger_provider,
create_logger_provider,
Expand Down Expand Up @@ -41,6 +44,7 @@
"load_config_file",
"substitute_env_vars",
"ConfigurationError",
"MissingDependencyError",
"EnvSubstitutionError",
"create_resource",
"create_propagator",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
from pathlib import Path
from typing import Any

from opentelemetry.sdk._configuration._exceptions import ConfigurationError
from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)
from opentelemetry.sdk._configuration.file._env_substitution import (
substitute_env_vars,
)
Expand All @@ -18,17 +21,21 @@
try:
import yaml
except ImportError as exc:
raise ImportError(
"File configuration requires pyyaml. "
"Install with: pip install opentelemetry-sdk[file-configuration]"
raise MissingDependencyError(
package="pyyaml",
feature="File configuration",
install_name="opentelemetry-sdk",
extras="file-configuration",
) from exc

try:
import jsonschema
except ImportError as exc:
raise ImportError(
"File configuration requires jsonschema. "
"Install with: pip install opentelemetry-sdk[file-configuration]"
raise MissingDependencyError(
package="jsonschema",
feature="File configuration",
install_name="opentelemetry-sdk",
extras="file-configuration",
) from exc

_schema_cache: list[dict] = []
Expand Down
62 changes: 62 additions & 0 deletions opentelemetry-sdk/tests/_configuration/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

import unittest

from opentelemetry.sdk._configuration._exceptions import (
ConfigurationError,
MissingDependencyError,
)


class TestMissingDependencyError(unittest.TestCase):
def test_is_configuration_error_subclass(self):
self.assertTrue(issubclass(MissingDependencyError, ConfigurationError))

def test_minimal_constructor(self):
exc = MissingDependencyError(package="foo")
self.assertEqual(exc.package, "foo")
self.assertIsNone(exc.feature)
self.assertEqual(exc.install_name, "foo")
self.assertIsNone(exc.extras)
self.assertIn("'foo'", str(exc))
self.assertIn("pip install foo", str(exc))

def test_with_feature(self):
exc = MissingDependencyError(package="bar", feature="Baz exporter")
self.assertEqual(exc.package, "bar")
self.assertEqual(exc.feature, "Baz exporter")
self.assertIn("Baz exporter requires 'bar'", str(exc))
self.assertIn("pip install bar", str(exc))

def test_with_custom_install_name(self):
exc = MissingDependencyError(
package="pyyaml",
install_name="opentelemetry-sdk",
extras="file-configuration",
)
self.assertEqual(exc.install_name, "opentelemetry-sdk")
self.assertEqual(exc.extras, "file-configuration")
self.assertIn(
"pip install 'opentelemetry-sdk[file-configuration]'", str(exc)
)

def test_with_feature_and_extras(self):
exc = MissingDependencyError(
package="jsonschema",
feature="File configuration",
install_name="opentelemetry-sdk",
extras="file-configuration",
)
self.assertIn("File configuration requires 'jsonschema'", str(exc))
self.assertIn(
"pip install 'opentelemetry-sdk[file-configuration]'", str(exc)
)

def test_can_be_caught_as_configuration_error(self):
with self.assertRaises(ConfigurationError):
raise MissingDependencyError(package="test")

def test_can_be_caught_as_exception(self):
with self.assertRaises(Exception):
raise MissingDependencyError(package="test")
Loading