Skip to content

Commit b6a42b6

Browse files
committed
support in the field name for declarative configuration
Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com>
1 parent 634cec5 commit b6a42b6

2 files changed

Lines changed: 32 additions & 5 deletions

File tree

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_conversion.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def _convert_value(value: Any, type_hint: Any) -> Any:
7777
def _dict_to_dataclass(data: Mapping[str, Any], cls: type[_T]) -> _T:
7878
"""Recursively convert a mapping to a dataclass instance.
7979
80-
For each key in ``data``:
80+
For each (normalized) key in ``data``:
8181
- If it matches a known dataclass field, the value is converted according
8282
to that field's type annotation (recursing for nested dataclasses).
8383
- Unknown keys are passed through as kwargs; classes decorated with
@@ -103,11 +103,15 @@ def _dict_to_dataclass(data: Mapping[str, Any], cls: type[_T]) -> _T:
103103
kwargs: dict[str, Any] = {}
104104

105105
for key, value in data.items():
106-
if key in known_fields:
107-
type_hint = hints.get(key)
108-
kwargs[key] = _convert_value(value, type_hint)
106+
# OpenTelemetry Configuration schema uses "/" to separate the feature from its
107+
# stability level (e.g. "detection/development"). Normalize to "_" so
108+
# the key matches valid Python dataclass field names.
109+
normalized_key = key.replace("/", "_")
110+
if normalized_key in known_fields:
111+
type_hint = hints.get(normalized_key)
112+
kwargs[normalized_key] = _convert_value(value, type_hint)
109113
else:
110114
# Unknown key — @_additional_properties decorator will capture it.
111-
kwargs[key] = value
115+
kwargs[normalized_key] = value
112116

113117
return cls(**kwargs)

opentelemetry-sdk/tests/_configuration/test_conversion.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class _WithEnum:
4242
filter: ExemplarFilter | None = None
4343

4444

45+
@dataclass
46+
class _WithSlashField:
47+
detection_development: str | None = None
48+
name: str | None = None
49+
50+
4551
class TestDictToDataclass(unittest.TestCase):
4652
def test_raises_on_non_dataclass(self):
4753
# _dict_to_dataclass is internal and assumes cls is a dataclass.
@@ -108,3 +114,20 @@ def test_enum_value_already_enum_passes_through(self):
108114
{"filter": ExemplarFilter.trace_based}, _WithEnum
109115
)
110116
self.assertIs(result.filter, ExemplarFilter.trace_based)
117+
118+
def test_slash_key_mapped_to_underscore_field(self):
119+
result = _dict_to_dataclass(
120+
{"name": "hello", "detection/development": "some_value"},
121+
_WithSlashField,
122+
)
123+
self.assertEqual(result.name, "hello")
124+
self.assertEqual(result.detection_development, "some_value")
125+
126+
def test_slash_key_normalized_in_additional_properties(self):
127+
result = _dict_to_dataclass(
128+
{"known": "yes", "detection/development": "value"}, _WithExtras
129+
)
130+
self.assertEqual(result.known, "yes")
131+
self.assertEqual(
132+
result.additional_properties, {"detection_development": "value"}
133+
)

0 commit comments

Comments
 (0)