feat(config): support OTEL_CONFIG_FILE in the SDK configurator (#5271)
* recursively convert parsed dicts to typed dataclasses in loader
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 #5127
Assisted-by: Claude Opus 4.6
* rename changelog fragment to PR #5269
* tighten typing on conversion module
- 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
* isolate typing.get_type_hints call to placate astroid 3.x on py3.14
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
* inline the typing.get_type_hints wrap
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
* add configure_sdk orchestrator for declarative config
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 #5126) is left for follow-up.
Refs #3631
Refs #5126
Assisted-by: Claude Opus 4.7
* rename changelog fragment to PR #5270
Assisted-by: Claude Opus 4.7
* honor OTEL_CONFIG_FILE in the SDK configurator
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 #3631
Assisted-by: Claude Opus 4.7
* rename changelog fragment to PR #5271
Assisted-by: Claude Opus 4.7
* use ExemplarFilter for enum coercion test fixture; allow 'astroid' in 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
* fix lint on test_sdk.py: hoist import, disable no-self-use
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
* silence pylint/ruff on intentional lazy imports
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
* remove extra blank line after imports (ruff I001)
Assisted-by: Claude Opus 4.7
* collapse multi-line @patch decorators (ruff format)
Assisted-by: Claude Opus 4.7
* add end-to-end loader tests covering YAML -> typed config -> factory
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
* address review feedback on OTEL_CONFIG_FILE routing
Use a walrus operator in _configure, simplify singleton reset to tearDown
only, and hoist no-self-use pylint disable to file scope.
* tighten OTEL_CONFIG_FILE docstring (review feedback from herin049)
The previous wording overstated the env-var contract by implying all
``OTEL_*`` variables are ignored when ``OTEL_CONFIG_FILE`` is set.
That's only true for spec-defined variables with schema equivalents:
* resource detectors enabled in the config can still read env vars
at runtime (e.g. ``OTEL_RESOURCE_ATTRIBUTES``, ``OTEL_SERVICE_NAME``)
* ``${env:VAR}`` substitutions inside the file remain in effect
Reword to be precise about both.
Assisted-by: Claude Opus 4.7 M
Mike Goldsmith committed
ac7a3df2fb5a3de775c1df3cd2a6c71eb53aec73
Parent: fa75422
Committed by GitHub <noreply@github.com>
on 6/19/2026, 8:29:33 AM