feat(config): add resource and propagator creation from declarative config#4979
feat(config): add resource and propagator creation from declarative config#4979MikeGoldsmith wants to merge 18 commits intoopen-telemetry:mainfrom
Conversation
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
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
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py
Outdated
Show resolved
Hide resolved
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py
Outdated
Show resolved
Hide resolved
- 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
… into mike/config-resource-propagator
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py
Outdated
Show resolved
Hide resolved
| seen_types.add(type(propagator)) | ||
| propagators.append(propagator) | ||
|
|
||
| # Process structured composite list |
There was a problem hiding this comment.
Should the structured propagator list also support entrypoints?
There was a problem hiding this comment.
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
pmcollins
left a comment
There was a problem hiding this comment.
Thanks, LGTM once suggestions are addressed (I added a typing suggestion).
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py
Outdated
Show resolved
Hide resolved
…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
…metry#4990) Assisted-by: Claude Sonnet 4.6
9ca2ed3 to
85575ff
Compare
Assisted-by: Claude Sonnet 4.6
85575ff to
430ae83
Compare
|
I think the Misc/tracecontext failure is a flakey test. Please can someone, re-run it for me? |
opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py
Outdated
Show resolved
Hide resolved
| 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'." | ||
| ) |
There was a problem hiding this comment.
| 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'." | |
| ) |
There was a problem hiding this comment.
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
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
Simpler than a post-merge check; suggested by @herin049. Assisted-by: Claude Sonnet 4.6
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 SDKResourcefrom the declarative config model without readingOTEL_RESOURCE_ATTRIBUTESor running any resource detectors (matches Java/JS SDK behavior). Starts from SDK telemetry defaults (telemetry.sdk.*), mergesattributes(withAttributeTypecoercion) andattributes_list(URL-decoded, lower priority), and addsservice.name=unknown_serviceif not specified.create_propagator(config)— builds aCompositePropagatorfrom the config'scompositelist and/orcomposite_liststring. Deduplicates by propagator type. Returns an emptyCompositePropagator(noop) when no propagator is configured.configure_propagator(config)— callsset_global_textmapto override Python's default env-var-based propagator setup.ConfigurationErrormoved to_exceptions.pyto break a circular import introduced whenfile/__init__.pyre-exports the new functions.All functions are exported from
opentelemetry.sdk._configuration.file.Type of change
How Has This Been Tested?
43 new unit tests covering:
AttributeTypecoercions (string, bool, int, double, and array variants)attributes_listparsing: priority vs explicit attributes, URL decoding,=in values, invalid pairsschema_urlpassthroughOTEL_RESOURCE_ATTRIBUTESenv var is NOT readtracecontext,baggage,b3,b3multi)composite+composite_listmerging and deduplicationnoneand empty entries incomposite_listskippedConfigurationErrorconfigure_propagatorcallsset_global_textmapDoes This PR Require a Contrib Repo Change?
Checklist:
Assisted-by: Claude Sonnet 4.6