Skip to content

Commit 2397a22

Browse files
herin049MikeGoldsmithxrmx
authored
feat: update EnvironmentGetter and EnvironmentSetter to use normalized environment variable names (open-telemetry#5119)
* feat: update EnvironmentGetter and EnvironmentSetter to use normalized environment variable names * update CHANGELOG.md --------- Co-authored-by: Mike Goldsmith <goldsmith.mike@gmail.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 7eb3a61 commit 2397a22

3 files changed

Lines changed: 40 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4646
([#5135](https://github.com/open-telemetry/opentelemetry-python/pull/5135))
4747
- ci: wait for tracecontext server readiness instead of a fixed sleep in `scripts/tracecontext-integration-test.sh`
4848
([#5149](https://github.com/open-telemetry/opentelemetry-python/pull/5149))
49+
- `opentelemetry-api`: update `EnvironmentGetter` and `EnvironmentSetter` to use normalized environment variable names
50+
([#5119](https://github.com/open-telemetry/opentelemetry-python/pull/5119))
4951
- `opentelemetry-sdk`: only load entrypoints for resource detectors if they are configured via `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS`
5052
([#5145](https://github.com/open-telemetry/opentelemetry-python/pull/5145))
5153
- `opentelemetry-exporter-otlp-json-common`: add 'opentelemetry-exporter-otlp-json-common' package for OTLP JSON exporters

opentelemetry-api/src/opentelemetry/propagators/_envcarrier.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import os
5+
import re
56
import typing
67
from collections.abc import MutableMapping
78

89
from opentelemetry.propagators.textmap import Getter, Setter
910

1011

12+
def _normalize_key(key: str) -> str:
13+
result = re.sub(r"[^A-Za-z0-9_]", "_", key).upper()
14+
if result and result[0].isdigit():
15+
result = "_" + result
16+
return result
17+
18+
1119
class EnvironmentGetter(Getter[typing.Mapping[str, str]]):
1220
"""Getter implementation for extracting context and baggage from environment variables.
1321
14-
EnvironmentGetter creates a case-insensitive lookup from the current environment
22+
EnvironmentGetter creates a normalized lookup from the current environment
1523
variables at initialization time and provides simple data access without validation.
1624
1725
Per the OpenTelemetry specification, environment variables are treated as immutable
@@ -25,10 +33,10 @@ class EnvironmentGetter(Getter[typing.Mapping[str, str]]):
2533
"""
2634

2735
def __init__(self):
28-
# Create case-insensitive lookup from current environment
36+
# Create a normalized lookup from current environment
2937
# Per spec: "creates an in-memory copy of the current environment variables"
3038
self.carrier: dict[str, str] = {
31-
k.lower(): v for k, v in os.environ.items()
39+
_normalize_key(k): v for k, v in os.environ.items()
3240
}
3341

3442
def get(
@@ -43,11 +51,9 @@ def get(
4351
Returns:
4452
A list with a single string value if the key exists, None otherwise.
4553
"""
46-
val = self.carrier.get(key.lower())
54+
val = self.carrier.get(_normalize_key(key))
4755
if val is None:
4856
return None
49-
if isinstance(val, typing.Iterable) and not isinstance(val, str):
50-
return list(val)
5157
return [val]
5258

5359
def keys(self, carrier: typing.Mapping[str, str]) -> list[str]:
@@ -57,7 +63,7 @@ def keys(self, carrier: typing.Mapping[str, str]) -> list[str]:
5763
carrier: Not used; maintained for interface compatibility with Getter[CarrierT]
5864
5965
Returns:
60-
List of all environment variable keys (lowercase).
66+
List of all environment variable keys (normalized).
6167
"""
6268
return list(self.carrier.keys())
6369

@@ -85,4 +91,4 @@ def set(
8591
key: The key to set (will be converted to uppercase)
8692
value: The value to set
8793
"""
88-
carrier[key.upper()] = value
94+
carrier[_normalize_key(key)] = value

opentelemetry-api/tests/propagators/test__envcarrier.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,35 @@
1414
from opentelemetry.propagators._envcarrier import (
1515
EnvironmentGetter,
1616
EnvironmentSetter,
17+
_normalize_key,
1718
)
1819
from opentelemetry.trace.propagation.tracecontext import (
1920
TraceContextTextMapPropagator,
2021
)
2122

2223

24+
class TestNormalizeKey(unittest.TestCase):
25+
"""Unit tests for _normalize_key."""
26+
27+
def test_lowercase(self):
28+
self.assertEqual(_normalize_key("traceparent"), "TRACEPARENT")
29+
30+
def test_hyphen_replaced(self):
31+
self.assertEqual(_normalize_key("trace-parent"), "TRACE_PARENT")
32+
33+
def test_non_ascii_replaced(self):
34+
self.assertEqual(_normalize_key("héllo"), "H_LLO")
35+
36+
def test_leading_digit_prefixed(self):
37+
self.assertEqual(_normalize_key("1abc"), "_1ABC")
38+
39+
def test_already_valid(self):
40+
self.assertEqual(_normalize_key("ALREADY_VALID"), "ALREADY_VALID")
41+
42+
def test_empty_string(self):
43+
self.assertEqual(_normalize_key(""), "")
44+
45+
2346
class TestEnvironmentGetter(unittest.TestCase):
2447
"""Unit tests for EnvironmentGetter."""
2548

@@ -67,7 +90,7 @@ def test_keys(self):
6790
with patch.dict(os.environ, test_env, clear=True):
6891
getter = EnvironmentGetter()
6992
keys = getter.keys({})
70-
expected_keys = {"key1", "key2", "key3"}
93+
expected_keys = {"KEY1", "KEY2", "KEY3"}
7194
self.assertEqual(set(keys), expected_keys)
7295

7396
def test_keys_empty_environment(self):

0 commit comments

Comments
 (0)