-
Notifications
You must be signed in to change notification settings - Fork 902
feat(config): add public opentelemetry.sdk.configuration namespace #5276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
MikeGoldsmith
wants to merge
24
commits into
open-telemetry:main
Choose a base branch
from
MikeGoldsmith:mike/config-public-namespace
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
e5d6932
recursively convert parsed dicts to typed dataclasses in loader
MikeGoldsmith 582c37f
rename changelog fragment to PR #5269
MikeGoldsmith b302f93
tighten typing on conversion module
MikeGoldsmith 3a6fd21
isolate typing.get_type_hints call to placate astroid 3.x on py3.14
MikeGoldsmith 131378c
inline the typing.get_type_hints wrap
MikeGoldsmith 3720621
add configure_sdk orchestrator for declarative config
MikeGoldsmith fd6c20a
rename changelog fragment to PR #5270
MikeGoldsmith 7066419
honor OTEL_CONFIG_FILE in the SDK configurator
MikeGoldsmith da80d63
rename changelog fragment to PR #5271
MikeGoldsmith b6e4702
use ExemplarFilter for enum coercion test fixture; allow 'astroid' in…
MikeGoldsmith 83e17bd
Merge branch 'mike/config-recursive-dict-conversion' into mike/config…
MikeGoldsmith 52365e2
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith 3fc2669
fix lint on test_sdk.py: hoist import, disable no-self-use
MikeGoldsmith 119bc83
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith 417d451
silence pylint/ruff on intentional lazy imports
MikeGoldsmith 41667ca
remove extra blank line after imports (ruff I001)
MikeGoldsmith 297da35
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith 2b2d47b
collapse multi-line @patch decorators (ruff format)
MikeGoldsmith f844bb6
add public opentelemetry.sdk.configuration namespace
MikeGoldsmith 7ca840f
rename changelog fragment to PR #5276
MikeGoldsmith 70c93d9
add end-to-end loader tests covering YAML -> typed config -> factory
MikeGoldsmith 828c54b
Merge branch 'mike/config-recursive-dict-conversion' into mike/config…
MikeGoldsmith 62cdfa4
Merge branch 'mike/config-orchestrator' into mike/config-file-env-rou…
MikeGoldsmith c85333d
Merge branch 'mike/config-file-env-routing' into mike/config-public-n…
MikeGoldsmith File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-sdk`: declarative config loader now recursively converts parsed dicts into typed dataclass instances, including nested dataclasses, lists of dataclasses, and enum values. End-to-end YAML/JSON → SDK configuration now works via the factory functions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-sdk`: add `configure_sdk(config)` to the declarative configuration API. Single entry point that takes a parsed `OpenTelemetryConfiguration`, builds the resource, and applies the tracer/meter/logger providers and propagator globally. Honors the top-level `disabled` flag. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-sdk`: the SDK configurator now honors the `OTEL_CONFIG_FILE` environment variable. When set, the SDK loads and applies the referenced declarative configuration file (YAML or JSON) in place of the env-var-based init path. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-sdk`: add public `opentelemetry.sdk.configuration` module that re-exports `configure_sdk`, `load_config_file`, `OpenTelemetryConfiguration`, and `ConfigurationError`. `load_config_file` is resolved lazily so the file-configuration extras (pyyaml, jsonschema) remain optional for callers that build an `OpenTelemetryConfiguration` programmatically. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| [codespell] | ||
| # skipping auto generated folders | ||
| skip = ./.tox,./.mypy_cache,./docs/_build,./target,*/LICENSE,./venv,.git,./opentelemetry-semantic-conventions,*-requirements*.txt | ||
| ignore-words-list = ans,ue,ot,hist,ro | ||
| ignore-words-list = ans,ue,ot,hist,ro,astroid |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_conversion.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # Copyright The OpenTelemetry Authors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Recursive dict-to-dataclass conversion for parsed config data. | ||
|
|
||
| The YAML/JSON loader produces nested dicts. Factory functions expect typed | ||
| dataclass instances (e.g. ``TracerProvider``, ``SpanProcessor``). This module | ||
| walks each field's type annotation and converts nested dicts into their | ||
| corresponding dataclass types. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import dataclasses | ||
| import enum | ||
| import types | ||
| import typing | ||
| from collections.abc import Mapping | ||
| from typing import Any, TypeVar, Union, get_args, get_origin | ||
|
|
||
| _T = TypeVar("_T") | ||
|
|
||
|
|
||
| def _unwrap_optional(type_hint: Any) -> Any: | ||
| """Strip ``None`` from a ``X | None`` / ``Optional[X]`` annotation. | ||
|
|
||
| Returns the unwrapped type, or the original hint if not a Union with None. | ||
| """ | ||
| origin = get_origin(type_hint) | ||
| if origin is Union or origin is types.UnionType: | ||
| non_none = [t for t in get_args(type_hint) if t is not type(None)] | ||
| if len(non_none) == 1: | ||
| return non_none[0] | ||
| return type_hint | ||
|
|
||
|
|
||
| def _convert_value(value: Any, type_hint: Any) -> Any: | ||
| """Convert a value according to its type hint. | ||
|
|
||
| Recursively converts dicts to dataclasses and lists of dicts to lists of | ||
| dataclasses. Other values (primitives, enums, ``dict[str, Any]`` aliases) | ||
| pass through unchanged. | ||
| """ | ||
| if value is None: | ||
| return None | ||
|
|
||
| unwrapped = _unwrap_optional(type_hint) | ||
| origin = get_origin(unwrapped) | ||
|
|
||
| # list[X] — recurse on each element | ||
| if origin is list and isinstance(value, list): | ||
| args = get_args(unwrapped) | ||
| if args: | ||
| item_type = args[0] | ||
| return [_convert_value(item, item_type) for item in value] | ||
| return value | ||
|
|
||
| # Direct dataclass type — recurse | ||
| if ( | ||
| isinstance(unwrapped, type) | ||
| and dataclasses.is_dataclass(unwrapped) | ||
| and isinstance(value, dict) | ||
| ): | ||
| return _dict_to_dataclass(value, unwrapped) | ||
|
|
||
| # Enum type — coerce string/value to the Enum member | ||
| if ( | ||
| isinstance(unwrapped, type) | ||
| and issubclass(unwrapped, enum.Enum) | ||
| and not isinstance(value, unwrapped) | ||
| ): | ||
| return unwrapped(value) | ||
|
|
||
| return value | ||
|
|
||
|
|
||
| def _dict_to_dataclass(data: Mapping[str, Any], cls: type[_T]) -> _T: | ||
| """Recursively convert a mapping to a dataclass instance. | ||
|
|
||
| For each key in ``data``: | ||
| - If it matches a known dataclass field, the value is converted according | ||
| to that field's type annotation (recursing for nested dataclasses). | ||
| - Unknown keys are passed through as kwargs; classes decorated with | ||
| ``@_additional_properties`` will capture them on the instance's | ||
| ``additional_properties`` attribute. | ||
|
|
||
| ``ClassVar`` fields (e.g. the ``additional_properties`` annotation on | ||
| decorated dataclasses) are ignored as expected. | ||
|
|
||
| Raises: | ||
| TypeError: If ``cls`` is not a dataclass type. | ||
| """ | ||
| if not dataclasses.is_dataclass(cls): | ||
| raise TypeError(f"{cls.__name__} is not a dataclass") | ||
|
|
||
| # Annotated as ``dict[str, Any]`` so astroid stops tracing into | ||
| # ``typing.get_type_hints`` — under pylint 3.x that path leads into | ||
| # Python 3.14's ``annotationlib`` (which uses t-strings) and crashes. | ||
| hints: dict[str, Any] = dict( | ||
| typing.get_type_hints(cls, include_extras=False) | ||
| ) | ||
| known_fields = {f.name for f in dataclasses.fields(cls)} | ||
| kwargs: dict[str, Any] = {} | ||
|
|
||
| for key, value in data.items(): | ||
| if key in known_fields: | ||
| type_hint = hints.get(key) | ||
| kwargs[key] = _convert_value(value, type_hint) | ||
| else: | ||
| # Unknown key — @_additional_properties decorator will capture it. | ||
| kwargs[key] = value | ||
|
|
||
| return cls(**kwargs) |
64 changes: 64 additions & 0 deletions
64
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_sdk.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Copyright The OpenTelemetry Authors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Top-level orchestrator for declarative SDK configuration. | ||
|
|
||
| Takes a parsed ``OpenTelemetryConfiguration`` and applies it by calling | ||
| each per-signal ``configure_*`` factory in order. This is the single | ||
| entry point for "apply this config" on the declarative path. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
|
|
||
| from opentelemetry.sdk._configuration._logger_provider import ( | ||
| configure_logger_provider, | ||
| ) | ||
| from opentelemetry.sdk._configuration._meter_provider import ( | ||
| configure_meter_provider, | ||
| ) | ||
| from opentelemetry.sdk._configuration._propagator import configure_propagator | ||
| from opentelemetry.sdk._configuration._resource import create_resource | ||
| from opentelemetry.sdk._configuration._tracer_provider import ( | ||
| configure_tracer_provider, | ||
| ) | ||
| from opentelemetry.sdk._configuration.models import OpenTelemetryConfiguration | ||
|
|
||
| _logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def configure_sdk(config: OpenTelemetryConfiguration) -> None: | ||
| """Configure the global SDK from a parsed declarative configuration. | ||
|
|
||
| Builds a :class:`Resource` from ``config.resource`` and applies it to | ||
| each signal provider. Sets the global tracer provider, meter provider, | ||
| logger provider, and text map propagator from their respective config | ||
| sections. Sections absent from the config (``None``) leave the | ||
| corresponding global untouched — matching the spec's "noop default" | ||
| behavior. | ||
|
|
||
| Honors the top-level ``disabled`` flag: when true, no globals are set. | ||
|
|
||
| Args: | ||
| config: Parsed ``OpenTelemetryConfiguration`` (typically from | ||
| ``load_config_file``). | ||
|
|
||
| Example: | ||
| >>> from opentelemetry.sdk._configuration.file import ( | ||
| ... load_config_file, configure_sdk, | ||
| ... ) | ||
| >>> config = load_config_file("otel-config.yaml") | ||
| >>> configure_sdk(config) | ||
| """ | ||
| if config.disabled: | ||
| _logger.info( | ||
| "Declarative configuration has disabled=true; skipping SDK setup." | ||
| ) | ||
| return | ||
|
|
||
| resource = create_resource(config.resource) | ||
| configure_tracer_provider(config.tracer_provider, resource) | ||
| configure_meter_provider(config.meter_provider, resource) | ||
| configure_logger_provider(config.logger_provider, resource) | ||
| configure_propagator(config.propagator) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
opentelemetry-sdk/src/opentelemetry/sdk/configuration/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # Copyright The OpenTelemetry Authors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Public API for the OpenTelemetry SDK's declarative configuration. | ||
|
|
||
| Load a parsed configuration from a YAML/JSON file and apply it to the | ||
| process-global SDK providers: | ||
|
|
||
| >>> from opentelemetry.sdk.configuration import ( | ||
| ... load_config_file, configure_sdk, | ||
| ... ) | ||
| >>> config = load_config_file("otel-config.yaml") | ||
| >>> configure_sdk(config) | ||
|
|
||
| Construct a configuration programmatically and apply it: | ||
|
|
||
| >>> from opentelemetry.sdk.configuration import ( | ||
| ... OpenTelemetryConfiguration, configure_sdk, | ||
| ... ) | ||
| >>> configure_sdk(OpenTelemetryConfiguration(file_format="1.0-rc.1")) | ||
|
|
||
| Loading from a file requires the optional ``[file-configuration]`` extras | ||
| (``pyyaml`` and ``jsonschema``). ``configure_sdk`` itself has no extra | ||
| dependencies — callers that construct an ``OpenTelemetryConfiguration`` | ||
| directly can use it without installing the extras. | ||
| """ | ||
|
|
||
| from opentelemetry.sdk._configuration._exceptions import ConfigurationError | ||
| from opentelemetry.sdk._configuration._sdk import configure_sdk | ||
| from opentelemetry.sdk._configuration.models import OpenTelemetryConfiguration | ||
|
|
||
|
|
||
| def __getattr__(name: str): | ||
| # ``load_config_file`` lives behind the optional file-configuration | ||
| # extras (pyyaml, jsonschema). Resolve it lazily so importing this | ||
| # module does not require those extras for callers that only use | ||
| # ``configure_sdk`` with a programmatically built configuration. | ||
| if name == "load_config_file": | ||
| # pylint: disable=import-outside-toplevel | ||
| from opentelemetry.sdk._configuration.file._loader import ( # noqa: PLC0415 | ||
| load_config_file, | ||
| ) | ||
|
|
||
| return load_config_file | ||
| raise AttributeError(f"module {__name__!r} has no attribute {name!r}") | ||
|
|
||
|
|
||
| # ``load_config_file`` is exposed via ``__getattr__`` rather than a module-level | ||
| # binding so the file-configuration extras stay optional. Pylint's static | ||
| # analysis doesn't see ``__getattr__`` and flags it as undefined; suppress. | ||
| __all__ = [ | ||
| "ConfigurationError", | ||
| "OpenTelemetryConfiguration", | ||
| "configure_sdk", | ||
| "load_config_file", # pylint: disable=undefined-all-variable | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might cause some issues for linters/auto-complete. Can we just do something like: