Skip to content

Commit 2007531

Browse files
fix(config): allow deflate for OTLP HTTP exporters
Map declarative OTLP compression values through a shared helper that recognizes Deflate when the exporter enum supports it, while leaving gRPC validation unchanged. Add regression coverage for the shared mapping helper plus tracer and meter provider HTTP exporter construction. Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
1 parent 44d8911 commit 2007531

7 files changed

Lines changed: 140 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
([#4907](https://github.com/open-telemetry/opentelemetry-python/issues/4907))
2727
- Drop Python 3.9 support
2828
([#5076](https://github.com/open-telemetry/opentelemetry-python/pull/5076))
29+
- `opentelemetry-sdk`: Allow declarative OTLP HTTP exporters to map `compression: deflate` instead of rejecting it as unsupported
30+
([#5075](https://github.com/open-telemetry/opentelemetry-python/pull/5075))
2931
- `opentelemetry-semantic-conventions`: use `X | Y` union annotation
3032
([#5096](https://github.com/open-telemetry/opentelemetry-python/pull/5096))
3133
- `opentelemetry-sdk`: Fix `ProcessResourceDetector` to use `sys.orig_argv` so that `process.command`, `process.command_line`, and `process.command_args` reflect the original invocation for `python -m <module>` runs (where `sys.argv[0]` is rewritten to the module path)

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
2121
from opentelemetry.util._importlib_metadata import entry_points
2222

23+
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
24+
2325
_logger = logging.getLogger(__name__)
2426

2527

@@ -75,3 +77,24 @@ def _parse_headers(
7577
for pair in headers:
7678
result[pair.name] = pair.value or ""
7779
return result
80+
81+
82+
def _map_compression(
83+
value: Optional[str], compression_enum: type
84+
) -> Optional[object]:
85+
"""Map a compression string to the given Compression enum value."""
86+
if value is None or value.lower() == "none":
87+
return None
88+
if value.lower() == "gzip":
89+
return compression_enum.Gzip # type: ignore[attr-defined]
90+
if value.lower() == "deflate" and hasattr(compression_enum, "Deflate"):
91+
return compression_enum.Deflate # type: ignore[attr-defined]
92+
93+
supported_values = ["'gzip'", "'none'"]
94+
if hasattr(compression_enum, "Deflate"):
95+
supported_values.insert(1, "'deflate'")
96+
97+
raise ConfigurationError(
98+
f"Unsupported compression value '{value}'. Supported values: "
99+
f"{', '.join(supported_values)}."
100+
)

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
from typing import Optional, Set, Type
1919

2020
from opentelemetry import metrics
21-
from opentelemetry.sdk._configuration._common import _parse_headers
21+
from opentelemetry.sdk._configuration._common import (
22+
_map_compression,
23+
_parse_headers,
24+
)
2225
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
2326
from opentelemetry.sdk._configuration.models import (
2427
Aggregation as AggregationConfig,
@@ -265,19 +268,6 @@ def _create_console_metric_exporter(
265268
)
266269

267270

268-
def _map_compression_metric(
269-
value: Optional[str], compression_enum: type
270-
) -> Optional[object]:
271-
"""Map a compression string to the given Compression enum value."""
272-
if value is None or value.lower() == "none":
273-
return None
274-
if value.lower() == "gzip":
275-
return compression_enum.Gzip # type: ignore[attr-defined]
276-
raise ConfigurationError(
277-
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
278-
)
279-
280-
281271
def _create_otlp_http_metric_exporter(
282272
config: OtlpHttpMetricExporterConfig,
283273
) -> MetricExporter:
@@ -296,7 +286,7 @@ def _create_otlp_http_metric_exporter(
296286
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
297287
) from exc
298288

299-
compression = _map_compression_metric(config.compression, Compression)
289+
compression = _map_compression(config.compression, Compression)
300290
headers = _parse_headers(config.headers, config.headers_list)
301291
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
302292
preferred_temporality = _map_temporality(config.temporality_preference)
@@ -331,7 +321,7 @@ def _create_otlp_grpc_metric_exporter(
331321
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
332322
) from exc
333323

334-
compression = _map_compression_metric(config.compression, grpc.Compression)
324+
compression = _map_compression(config.compression, grpc.Compression)
335325
headers = _parse_headers(config.headers, config.headers_list)
336326
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
337327
preferred_temporality = _map_temporality(config.temporality_preference)

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
from typing import Optional
1919

2020
from opentelemetry import trace
21-
from opentelemetry.sdk._configuration._common import _parse_headers
21+
from opentelemetry.sdk._configuration._common import (
22+
_map_compression,
23+
_parse_headers,
24+
)
2225
from opentelemetry.sdk._configuration._exceptions import ConfigurationError
2326
from opentelemetry.sdk._configuration.models import (
2427
OtlpGrpcExporter as OtlpGrpcExporterConfig,
@@ -103,20 +106,6 @@ def _create_otlp_http_span_exporter(
103106
compression=compression, # type: ignore[arg-type]
104107
)
105108

106-
107-
def _map_compression(
108-
value: Optional[str], compression_enum: type
109-
) -> Optional[object]:
110-
"""Map a compression string to the given Compression enum value."""
111-
if value is None or value.lower() == "none":
112-
return None
113-
if value.lower() == "gzip":
114-
return compression_enum.Gzip # type: ignore[attr-defined]
115-
raise ConfigurationError(
116-
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
117-
)
118-
119-
120109
def _create_otlp_grpc_span_exporter(
121110
config: OtlpGrpcExporterConfig,
122111
) -> SpanExporter:

opentelemetry-sdk/tests/_configuration/test_common.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from unittest.mock import MagicMock, patch
1818

1919
from opentelemetry.sdk._configuration._common import (
20+
_map_compression,
2021
_parse_headers,
2122
load_entry_point,
2223
)
@@ -85,7 +86,6 @@ def test_struct_headers_override_headers_list(self):
8586
def test_both_empty_struct_and_none_list_returns_empty_dict(self):
8687
self.assertEqual(_parse_headers([], None), {})
8788

88-
8989
class TestLoadEntryPoint(unittest.TestCase):
9090
def test_returns_loaded_class(self):
9191
mock_class = MagicMock()
@@ -137,3 +137,53 @@ def test_instantiation_error_not_wrapped(self):
137137
# ConfigurationError
138138
with self.assertRaises(TypeError, msg="bad init"):
139139
cls()
140+
141+
142+
class _CompressionWithDeflate:
143+
Gzip = "gzip"
144+
Deflate = "deflate"
145+
146+
147+
class _CompressionWithoutDeflate:
148+
Gzip = "gzip"
149+
150+
151+
class TestMapCompression(unittest.TestCase):
152+
def test_none_returns_none(self):
153+
self.assertIsNone(_map_compression(None, _CompressionWithDeflate))
154+
155+
def test_none_string_returns_none(self):
156+
self.assertIsNone(
157+
_map_compression("none", _CompressionWithDeflate)
158+
)
159+
160+
def test_gzip_maps_to_gzip(self):
161+
self.assertEqual(
162+
_map_compression("gzip", _CompressionWithDeflate), "gzip"
163+
)
164+
165+
def test_deflate_maps_when_supported(self):
166+
self.assertEqual(
167+
_map_compression("deflate", _CompressionWithDeflate),
168+
"deflate",
169+
)
170+
171+
def test_deflate_raises_when_unsupported(self):
172+
with self.assertRaises(ConfigurationError) as ctx:
173+
_map_compression("deflate", _CompressionWithoutDeflate)
174+
175+
self.assertEqual(
176+
str(ctx.exception),
177+
"Unsupported compression value 'deflate'. Supported values: "
178+
"'gzip', 'none'.",
179+
)
180+
181+
def test_http_error_message_includes_deflate(self):
182+
with self.assertRaises(ConfigurationError) as ctx:
183+
_map_compression("brotli", _CompressionWithDeflate)
184+
185+
self.assertEqual(
186+
str(ctx.exception),
187+
"Unsupported compression value 'brotli'. Supported values: "
188+
"'gzip', 'deflate', 'none'.",
189+
)

opentelemetry-sdk/tests/_configuration/test_meter_provider.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,34 @@ def test_otlp_http_created_with_endpoint(self):
274274
self.assertIsNone(kwargs["timeout"])
275275
self.assertIsNone(kwargs["compression"])
276276

277+
def test_otlp_http_created_with_deflate_compression(self):
278+
mock_exporter_cls = MagicMock()
279+
mock_compression_cls = MagicMock()
280+
mock_compression_cls.Deflate = "deflate_val"
281+
mock_http_module = MagicMock()
282+
mock_http_module.Compression = mock_compression_cls
283+
mock_module = MagicMock()
284+
mock_module.OTLPMetricExporter = mock_exporter_cls
285+
286+
with patch.dict(
287+
sys.modules,
288+
{
289+
"opentelemetry.exporter.otlp.proto.http.metric_exporter": mock_module,
290+
"opentelemetry.exporter.otlp.proto.http": mock_http_module,
291+
},
292+
):
293+
config = self._make_periodic_config(
294+
PushMetricExporterConfig(
295+
otlp_http=OtlpHttpMetricExporterConfig(
296+
compression="deflate"
297+
)
298+
)
299+
)
300+
create_meter_provider(config)
301+
302+
_, kwargs = mock_exporter_cls.call_args
303+
self.assertEqual(kwargs["compression"], "deflate_val")
304+
277305
def test_otlp_grpc_missing_package_raises(self):
278306
config = self._make_periodic_config(
279307
PushMetricExporterConfig(otlp_grpc=OtlpGrpcMetricExporterConfig())

opentelemetry-sdk/tests/_configuration/test_tracer_provider.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,32 @@ def test_otlp_http_created_with_endpoint(self):
309309
compression=None,
310310
)
311311

312+
def test_otlp_http_created_with_deflate_compression(self):
313+
mock_exporter_cls = MagicMock()
314+
mock_compression_cls = MagicMock()
315+
mock_compression_cls.Deflate = "deflate_val"
316+
mock_module = MagicMock()
317+
mock_module.OTLPSpanExporter = mock_exporter_cls
318+
mock_http_module = MagicMock()
319+
mock_http_module.Compression = mock_compression_cls
320+
321+
with patch.dict(
322+
sys.modules,
323+
{
324+
"opentelemetry.exporter.otlp.proto.http.trace_exporter": mock_module,
325+
"opentelemetry.exporter.otlp.proto.http": mock_http_module,
326+
},
327+
):
328+
config = self._make_batch_config(
329+
SpanExporterConfig(
330+
otlp_http=OtlpHttpExporterConfig(compression="deflate")
331+
)
332+
)
333+
create_tracer_provider(config)
334+
335+
_, kwargs = mock_exporter_cls.call_args
336+
self.assertEqual(kwargs["compression"], "deflate_val")
337+
312338
def test_otlp_http_headers_list(self):
313339
mock_exporter_cls = MagicMock()
314340
mock_http_module = MagicMock()

0 commit comments

Comments
 (0)