Skip to content

feat(config): add public opentelemetry.sdk.configuration namespace#5276

Draft
MikeGoldsmith wants to merge 24 commits into
open-telemetry:mainfrom
MikeGoldsmith:mike/config-public-namespace
Draft

feat(config): add public opentelemetry.sdk.configuration namespace#5276
MikeGoldsmith wants to merge 24 commits into
open-telemetry:mainfrom
MikeGoldsmith:mike/config-public-namespace

Conversation

@MikeGoldsmith

Copy link
Copy Markdown
Member

Description

Adds a non-underscore public module opentelemetry.sdk.configuration that re-exports the user-facing declarative-config surface:

  • configure_sdk(config) — apply a parsed configuration to the global SDK
  • load_config_file(path) — load and validate a YAML/JSON file into a typed config
  • OpenTelemetryConfiguration — root model for programmatic construction
  • ConfigurationError — exception users may catch
from opentelemetry.sdk.configuration import load_config_file, configure_sdk

config = load_config_file("otel-config.yaml")
configure_sdk(config)

Lazy file extras

load_config_file is resolved through module-level __getattr__ rather than a top-level binding. This keeps the optional [file-configuration] extras (pyyaml, jsonschema) optional: callers who build an OpenTelemetryConfiguration programmatically can import opentelemetry.sdk.configuration and call configure_sdk without those packages installed.

This finishes the "Complete Integration & Public API" item under #3631 — users no longer need to reach into opentelemetry.sdk._configuration.* private modules to use declarative config.

Refs #3631

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

4 new tests in tests/_configuration/test_public_namespace.py:

  • All four public names resolve on the module
  • Each public name is the same object as its private counterpart (assertIs, not assertEqual)
  • Accessing an unknown attribute raises AttributeError
  • __all__ matches the documented public surface exactly

Smoke-tested end-to-end with OTEL_CONFIG_FILE=otel.yaml python -c "from opentelemetry.sdk.configuration import load_config_file, configure_sdk; ...". Verified lazy import: import opentelemetry.sdk.configuration does not pull yaml into sys.modules.

Does This PR Require a Contrib Repo Change?

  • Yes.
  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

Note

This PR is stacked on top of #5269#5270#5271. The diff includes those upstream PRs' changes until they land; rebase will clean it up. Transient failures expected until then:

  • changelog — validator flags 5269.added, 5270.added, and 5271.added as added on this PR (resolves as each lands)
  • public-symbols-check — adds four new public symbols on the new opentelemetry.sdk.configuration module. Intentional — that's the whole point of this PR. Needs an "Approve Public API check" label from a maintainer.

Adds `_dict_to_dataclass` in `_conversion.py` which walks each field's
type annotation and converts:
- nested dicts → typed dataclass instances
- lists of dicts → lists of typed dataclasses
- string/value → Enum members (e.g. log_level: info)
- unknown keys → routed to the @_additional_properties decorator

The loader's `_dict_to_model` now produces a fully-typed
OpenTelemetryConfiguration tree end-to-end. Factory functions can rely
on typed attribute access (config.tracer_provider.processors[0].batch
.exporter.otlp_http.endpoint) instead of failing on raw dicts.

This closes the gap between load_config_file() and the factory
functions — YAML/JSON config → SDK objects now works end-to-end.

Closes open-telemetry#5127

Assisted-by: Claude Opus 4.6
- Use TypeVar for _dict_to_dataclass return — callers now get the
  correct type instead of Any
- Use collections.abc.Mapping for input (more permissive than dict)
- Add explicit is_dataclass check at entry — raises TypeError with a
  descriptive message instead of failing later in dataclasses.fields

Assisted-by: Claude Opus 4.6
Astroid 3.x (used by pylint 3.x) follows typing.get_type_hints into
Python 3.14's annotationlib, which contains t-string literals it can't
parse and crashes with AttributeError on 'visit_templatestr'. Wrapping
the call in a helper that returns dict[str, Any] stops the inference at
the declared return type.

Assisted-by: Claude Opus 4.7
Same effect as the prior helper — declaring the local as ``dict[str, Any]``
stops astroid's inference at the annotation rather than tracing into the
typing internals.

Assisted-by: Claude Opus 4.7
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 — when
true, no globals are touched.

The orchestrator is a thin composition of the existing per-signal
configure_* factories; the deeper unification with the env-var path
(see open-telemetry#5126) is left for follow-up.

Refs open-telemetry#3631
Refs open-telemetry#5126

Assisted-by: Claude Opus 4.7
When the environment variable is set, route the SDK through the
declarative config path — load the file via load_config_file() and
apply it via configure_sdk() — in place of the env-var-based
_initialize_components(). Other OTEL_* vars are ignored (per spec
v1.0.0: when a config file is given, it is the sole source of truth).

Kwargs passed to _OTelSDKConfigurator._configure are ignored with a
warning when the file path is set, so distros that inject kwargs via
super() see a clear signal rather than silent drops.

The file-loader imports (pyyaml, jsonschema) stay lazy so installs
without the file-configuration extras are not affected.

Refs open-telemetry#3631

Assisted-by: Claude Opus 4.7
… codespell

Replace the bespoke _Level enum (which violated pylint's invalid-name on
lowercase members) with the real ExemplarFilter enum from models.py — the
generated models use lowercase values verbatim from the JSON schema, so
using one of them avoids fighting the linter and exercises the same code
path with real data shapes.

Add 'astroid' to codespell's ignore-words-list; the prior commit's
explanatory comment mentions the library by name and codespell flagged it
as a misspelling of 'asteroid'.

Assisted-by: Claude Opus 4.7
Move ``SdkTracerProvider`` import to module top (ruff PLC0415 /
pylint C0415) and add explicit ``# pylint: disable=no-self-use``
on the three mock-only tests that intentionally do not touch
``self``.

Assisted-by: Claude Opus 4.7
The configure_sdk / load_config_file imports inside ``_configure``
are deliberately deferred so that the SDK does not pull in the
optional file-configuration extras (pyyaml, jsonschema) unless
``OTEL_CONFIG_FILE`` is actually set. Annotate with the corresponding
pylint and ruff suppressions; the existing comment already explains
why.

Assisted-by: Claude Opus 4.7
Re-exports the core declarative-config entry points from a non-
underscore module:

  - configure_sdk
  - load_config_file
  - OpenTelemetryConfiguration
  - ConfigurationError

``load_config_file`` is resolved through module-level ``__getattr__``
so the optional file-configuration extras (pyyaml, jsonschema) are
only required when a caller actually reaches for it; callers that
build an ``OpenTelemetryConfiguration`` programmatically can use
``configure_sdk`` without installing the extras.

Refs open-telemetry#3631

Assisted-by: Claude Opus 4.7
@MikeGoldsmith MikeGoldsmith added the Approve Public API check This label shows that the public symbols added or changed in a PR are strictly necessary label Jun 5, 2026
@MikeGoldsmith MikeGoldsmith moved this to Ready for review in Python PR digest Jun 5, 2026
The conversion module has unit tests that exercise _dict_to_dataclass
in isolation, but nothing verified the full pipeline: load a real
YAML file, get back fully-typed nested dataclasses, and feed the
result into a downstream factory function.

Adds two checks built on a representative nested fixture (tracer
provider with a parent-based / trace-id-ratio sampler and a batch
processor with console exporter):

  - nested fields (sampler, processors[*].batch) come back as the
    expected typed dataclasses, not raw dicts
  - the typed result is accepted by ``create_tracer_provider`` and
    produces an SDK ``TracerProvider``

This is the integration coverage requested in PR review feedback;
the inline example in the PR description is now an actual regression
test.

Assisted-by: Claude Opus 4.7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approve Public API check This label shows that the public symbols added or changed in a PR are strictly necessary

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

1 participant