Skip to content

Pure-Python otlp-proto-http over stdlib urllib (drop protobuf + requests) [3/4 RFC] - #5505

Draft
ocelotl wants to merge 4 commits into
open-telemetry:mainfrom
ocelotl:pure-python-otlp-3-http
Draft

Pure-Python otlp-proto-http over stdlib urllib (drop protobuf + requests) [3/4 RFC]#5505
ocelotl wants to merge 4 commits into
open-telemetry:mainfrom
ocelotl:pure-python-otlp-3-http

Conversation

@ocelotl

@ocelotl ocelotl commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

Replace the opentelemetry-exporter-otlp-proto-http transport with a pure-Python implementation that:

Also drops the googleapis-common-protos dependency (pulls protobuf) and the gcp-auth optional extra.

Why drop requests

requests is the last third-party runtime dependency on the OTLP/HTTP export path. Everything the exporter needs — gzip/deflate compression, timeouts, TLS with custom CA / client cert (OTEL_EXPORTER_OTLP_CERTIFICATE, ..._CLIENT_KEY, ..._CLIENT_CERTIFICATE), and the retry/backoff loop — is covered by urllib.request + a stdlib ssl.SSLContext. The exporter already implements its own retry loop, so little is lost.

Pluggable HTTP client — preserved, without requests

The original exporter let callers inject a configured requests.Session (custom auth, proxies, TLS, retry adapters) as its HTTP client, both via the session= constructor argument and via the OTEL_PYTHON_EXPORTER_OTLP_HTTP*_CREDENTIAL_PROVIDER entry-point env vars. This PR keeps that capability using the stdlib:

  • New supported injectable: urllib.request.OpenerDirector — the stdlib structural analog of a Session. Handlers cover the same ground: HTTPSHandler(context=…) (TLS), ProxyHandler (proxies), auth handlers, and a custom BaseHandler.https_request hook for dynamic per-request auth (e.g. refreshing bearer tokens).
  • Same env vars, same entry-point group. OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER and the per-signal …_{TRACES,METRICS,LOGS}_CREDENTIAL_PROVIDER and the opentelemetry_otlp_credential_provider group are unchanged; the provider now returns an OpenerDirector. (These were previously loaded but never wired into the send path — this PR actually wires them in.)
  • requests.Session still works, deprecated. Passing one (via session= or a provider) is detected structurally, without importing requests, routed through its .post() exactly as before, and now emits a DeprecationWarning. Its transport errors are normalized to URLError so the retry loop is uniform.
  • These OTEL_PYTHON_-prefixed vars are non-spec extensions, so this changes no spec-required behavior; all spec-defined TLS/auth (file-path based) still works.

SDK docstrings updated (in this PR): the four HTTP credential-provider env-var docstrings in opentelemetry-sdk now document the OpenerDirector contract (with an example) and the requests.Session deprecation. The gRPC credential-provider docstrings (which return grpc.ChannelCredentials) are untouched.

Implementation: a small client seam in _common (_resolve_client + _opener_client/_session_client); the three exporters wire self._client from _resolve_client(session, <SIGNAL_ENV>, ssl_context) and send through it. Unit tests cover the default, OpenerDirector, deprecated-session (warns + routes), transport-error, and invalid-return paths.

Stacked series (merge in order)

  1. opentelemetry-proto → pure-Python
  2. opentelemetry-exporter-otlp-proto-common → pure-Python backend
  3. opentelemetry-exporter-otlp-proto-http → pure-Python + urllib (this PR)
  4. opentelemetry-exporter-otlp-proto-grpc → pure-Python gRPC

Depends on #5503 and #5504; diff is cumulative. Isolated per-package diff:
https://github.com/ocelotl/opentelemetry-python/compare/pure-python-otlp-2-common...pure-python-otlp-3-http

Entry-point names (otlp_proto_http for traces/metrics/logs) are unchanged. CI not expected green while the stack is in flight; see #5503 for full rationale.


Stack (merge in order): #5503 (proto) → #5504 (common) → #5505 (http) → #5506 (grpc)

ocelotl added 3 commits August 2, 2026 21:33
Swap the google.protobuf-generated message classes for hand-written pure-Python
encoders under opentelemetry._proto, keeping the public opentelemetry.proto.*
import namespace via thin re-export shims. Removes the protobuf (and native upb)
dependency entirely; only the serialize path used by the OTLP exporters is
implemented (plus the empty export-service response decode).
Point the shared OTLP encoders at the pure-Python opentelemetry-proto package.
No google.protobuf dependency; public opentelemetry.exporter.otlp.proto.common.*
API preserved via re-export shims over the private _proto implementation.
Drop the requests dependency in favour of stdlib urllib.request + ssl for the
OTLP/HTTP export, and use the pure-Python protobuf encoders. Removes both the
protobuf and requests native/third-party dependencies. The requests.Session
credential-provider feature (session= arg and *_CREDENTIAL_PROVIDER env vars,
plus the gcp-auth extra) is not carried over, as it is typed to requests.Session.
…no requests)

Reinstates the original exporter's pluggable-client capability without a
`requests` dependency:

- A `urllib.request.OpenerDirector` is now the supported injectable, via the
  `session` constructor argument or an `opentelemetry_otlp_credential_provider`
  entry point named by the existing
  OTEL_PYTHON_EXPORTER_OTLP_HTTP[_TRACES|_METRICS|_LOGS]_CREDENTIAL_PROVIDER
  environment variables (previously loaded but never wired in).
- A `requests.Session` is still accepted for backwards compatibility, detected
  structurally (never importing `requests`) and routed through its `.post()`;
  passing one now emits a DeprecationWarning. Its transport errors are
  normalized to URLError so the retry loop is unchanged.
- Default (no injection) builds a stdlib opener from the SSL context, matching
  prior behavior.

`_common` gains `_resolve_client` (the client seam) and `_opener_client` /
`_session_client` senders; `_load_session_from_envvar` becomes
`_load_provider_from_envvar`. The three HTTP credential-provider docstrings in
opentelemetry-sdk are updated to document the OpenerDirector contract and the
requests.Session deprecation. Drops the moot pyproto-vs-requests equivalence
test and covers the new client-resolution paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant