Skip to content

feat(config): add resource and propagator creation from declarative config#4979

Open
MikeGoldsmith wants to merge 18 commits intoopen-telemetry:mainfrom
MikeGoldsmith:mike/config-resource-propagator
Open

feat(config): add resource and propagator creation from declarative config#4979
MikeGoldsmith wants to merge 18 commits intoopen-telemetry:mainfrom
MikeGoldsmith:mike/config-resource-propagator

Conversation

@MikeGoldsmith
Copy link
Member

@MikeGoldsmith MikeGoldsmith commented Mar 13, 2026

Description

Implements step 3 of the declarative configuration work (see below), resource and propagator creation from a parsed config file.

Adds:

  • create_resource(config) — builds an SDK Resource from the declarative config model without reading OTEL_RESOURCE_ATTRIBUTES or running any resource detectors (matches Java/JS SDK behavior). Starts from SDK telemetry defaults (telemetry.sdk.*), merges attributes (with AttributeType coercion) and attributes_list (URL-decoded, lower priority), and adds service.name=unknown_service if not specified.
  • create_propagator(config) — builds a CompositePropagator from the config's composite list and/or composite_list string. Deduplicates by propagator type. Returns an empty CompositePropagator (noop) when no propagator is configured.
  • configure_propagator(config) — calls set_global_textmap to override Python's default env-var-based propagator setup.
  • ConfigurationError moved to _exceptions.py to break a circular import introduced when file/__init__.py re-exports the new functions.

All functions are exported from opentelemetry.sdk._configuration.file.

Type of change

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

How Has This Been Tested?

43 new unit tests covering:

  • All AttributeType coercions (string, bool, int, double, and array variants)
  • attributes_list parsing: priority vs explicit attributes, URL decoding, = in values, invalid pairs
  • schema_url passthrough
  • OTEL_RESOURCE_ATTRIBUTES env var is NOT read
  • All four built-in propagators (tracecontext, baggage, b3, b3multi)
  • composite + composite_list merging and deduplication
  • none and empty entries in composite_list skipped
  • Missing entry point raises ConfigurationError
  • configure_propagator calls set_global_textmap

Does This PR Require a Contrib Repo Change?

  • No.

Checklist:

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

Assisted-by: Claude Sonnet 4.6

Implements create_resource() and create_propagator()/configure_propagator()
for the declarative file configuration. Resource creation does not read
OTEL_RESOURCE_ATTRIBUTES or run any detectors (matches Java/JS SDK behavior).
Propagator configuration always calls set_global_textmap to override Python's
default tracecontext+baggage, setting a noop CompositePropagator when no
propagator is configured.

Assisted-by: Claude Sonnet 4.6
@MikeGoldsmith MikeGoldsmith requested a review from a team as a code owner March 13, 2026 13:11
@MikeGoldsmith MikeGoldsmith moved this to Ready for review in Python PR digest Mar 13, 2026
Assisted-by: Claude Sonnet 4.6
- _resource.py: refactor _coerce_attribute_value to dispatch table to
  avoid too-many-return-statements; fix short variable names k/v ->
  attr_key/attr_val; fix return type of _sdk_default_attributes to
  dict[str, str] to satisfy pyright
- _propagator.py: rename short variable names e -> exc, p -> propagator
- test_resource.py: move imports to top level; split TestCreateResource
  (25 methods) into three focused classes to satisfy too-many-public-methods
- test_propagator.py: add pylint disable for protected-access

Assisted-by: Claude Sonnet 4.6
Copy link
Member

@pmcollins pmcollins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Minor comments below.

- replace _sdk_default_attributes() with _DEFAULT_RESOURCE from resources module
- move _coerce_bool into dispatch tables for both scalar and array bool types,
  fixing a bug where bool_array with string values like "false" would coerce
  incorrectly via plain bool() (non-empty string -> True)
- add test for bool_array with string values to cover the bug

Assisted-by: Claude Sonnet 4.6
seen_types.add(type(propagator))
propagators.append(propagator)

# Process structured composite list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the structured propagator list also support entrypoints?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structured composite list is intentionally limited to the four types defined in the config schema (tracecontext, baggage, b3, b3multi) — the schema enforces maxProperties: 1 with fixed property names, so there's no way to specify an unknown third-party propagator via that path. For anything outside those four, composite_list (the comma-separated string) is the right mechanism, and that does use entry points. So I don't think we need entry point support in the structured path.

…erge

- collapse _SCALAR_COERCIONS and _ARRAY_COERCIONS into a single _COERCIONS
  dict using an _array() factory, reducing _coerce_attribute_value to two lines
- process attributes_list before attributes so explicit attributes naturally
  overwrite list entries without needing an explicit guard

Assisted-by: Claude Sonnet 4.6
Copy link
Member

@pmcollins pmcollins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM once suggestions are addressed (I added a typing suggestion).

…config

Implements LoggerProvider instantiation from declarative config files,
following the same env-var-suppression pattern as create_meter_provider.
BatchLogRecordProcessor defaults use spec values (1000ms schedule_delay),
overriding the Python SDK's incorrect 5000ms env-var default.

Assisted-by: Claude Sonnet 4.6
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Mar 17, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@MikeGoldsmith MikeGoldsmith force-pushed the mike/config-resource-propagator branch from 9ca2ed3 to 85575ff Compare March 17, 2026 16:31
Assisted-by: Claude Sonnet 4.6
@MikeGoldsmith MikeGoldsmith force-pushed the mike/config-resource-propagator branch from 85575ff to 430ae83 Compare March 17, 2026 16:35
@MikeGoldsmith
Copy link
Member Author

I think the Misc/tracecontext failure is a flakey test. Please can someone, re-run it for me?

Comment on lines +93 to +103
def _map_compression(
value: Optional[str], compression_enum: type
) -> Optional[object]:
"""Map a compression string to the given Compression enum value."""
if value is None or value.lower() == "none":
return None
if value.lower() == "gzip":
return compression_enum.Gzip # type: ignore[attr-defined]
raise ConfigurationError(
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _map_compression(
value: Optional[str], compression_enum: type
) -> Optional[object]:
"""Map a compression string to the given Compression enum value."""
if value is None or value.lower() == "none":
return None
if value.lower() == "gzip":
return compression_enum.Gzip # type: ignore[attr-defined]
raise ConfigurationError(
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
)
class CompressionT(Protocol[T]):
@property
def Gzip(self) -> T: ...
def _map_compression(
value: Optional[str], compression_enum: CompressionT[T]
) -> Optional[T]:
"""Map a compression string to the given Compression enum value."""
if value is None or value.lower() == "none":
return None
if value.lower() == "gzip":
return compression_enum.Gzip
raise ConfigurationError(
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion @herin049.

I tried using Protocol[T] but ran into a Pyright limitation: we pass the enum class itself (e.g. grpc.Compression) as compression_enum, not an instance of it.

This means:

  • @Property def Gzip(self) -> T describes instance-level access, so Pyright rejects type[Compression] as incompatible
  • ClassVar[T] would describe class-level access correctly, but Pyright doesn't allow TypeVars inside ClassVar

I reverted back to compression_enum: type with # type: ignore[attr-defined].

If you know a way to express this that Pyright accepts, happy to revisit — but I couldn't find a clean path.

Thanks

MikeGoldsmith and others added 4 commits March 18, 2026 13:10
Co-authored-by: Lukas Hering <40302054+herin049@users.noreply.github.com>
…smith/opentelemetry-python into mike/config-resource-propagator
The Protocol[T] approach doesn't type-check cleanly for enum classes in
Pyright: ClassVar[T] disallows TypeVars, and @Property only describes
instances, not class-level enum attributes.

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

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants