Repository navigation
Conversation
c1d60de to
9989274
Compare
Pull request dashboard statusWaiting on the author · refreshed 2026-10-17 02:49 UTC Move out of draft to request review. Status above doesn't look right?
|
9989274 to
1da6e50
Compare
a6885cc to
6cd3ff2
Compare
|
WIP |
|
Hey @ocelotl, thanks for this -- I also like the idea of having a dependency-less protobuf/gRPC exporter. I have a few questions about the overall approach:
|
|
Another option would be to try |
https://protobuf.dev/support/cross-version-runtime-guarantee/ the range shouldn't be too narrow |
I think the solution we're trying to implement here is to have an installable exporter that can never introduce dependency conflicts with user provided packages. Switching to protobuf-py would still leave possibilities of dependency conflicts (e.g. if user packages depend on different versions of protobuf-py) unless we vendored it directly into the exporters. Having a Python native protobuf implementation is somewhat irrelevant here. |
|
This PR has been automatically marked as stale because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 days of this comment. |
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).
Restore the parts of the protobuf message API that the unchanged otlp-proto-common/http/grpc components rely on, so replacing the generated classes is transparent to them: - add the top-level SpanFlags enum and nested Span.SpanKind / Status.StatusCode enums, with values also lifted onto the parent message class as protobuf does - add a Message base with value equality (by serialized bytes, matching proto3 default omission), repr, SerializePartialToString and FromString - support the metrics encoder's mutable API: as_int/as_double oneof setters on NumberDataPoint and Exemplar (with sfixed64 range checking), and auto-vivified gauge/sum/histogram/exponential_histogram/summary fields on Metric - accept a mapping for the Span.status message field - add the gRPC service Servicer and add_*Servicer_to_server helpers alongside the existing client Stub opentelemetry-proto (668 tests, including the byte-for-byte differential against protobuf) and otlp-proto-common (20 tests) pass.
A protoc plugin that generates the pure-Python, encode-only protobuf-wire message classes from the .proto files, so the hand-written classes in opentelemetry._proto can be generated instead of maintained by hand. Mirrors the architecture of opentelemetry-codegen-json: a plugin entry point (protoc-gen-pyproto / --pyproto_out), a generic CodeWriter, a wire-specific type table, and a descriptor-walking generator. Output goes to opentelemetry/_proto/<signal>/v1/<name>_pb2.py and imports the existing _pyprotobuf runtime. Proven for common/v1 and resource/v1: generated classes serialize byte-identically to both the hand-written classes and real protobuf across 21 constructed messages. Other signals, the mutable API (Metric auto-vivification, as_int/as_double setters), packed fields, maps, and gRPC service stubs remain to be covered.
The pure-Python proto classes only serialize. Tests that decode OTLP bytes compared the public classes to themselves, so they proved nothing. Generate a real protobuf reference set under opentelemetry.proto._test (gated by the new opentelemetry-proto[test] extra) and point the equivalence tests at it, so they now compare the pure-Python implementation against real protobuf. The conftest guard asserts the two sides are distinct implementations. Add scripts/proto_codegen_test.sh to regenerate the reference set.
The http span, log, and metrics test suites decode the exported OTLP bytes to assert on them. The pure-Python classes only serialize, so decode via the protobuf-backed opentelemetry.proto._test classes and compare against the same representation. The metrics batch-splitting unit tests keep the pure-Python classes, since _split_metrics_data produces them; give the pure-Python KeyValue and ResourceMetrics the same dict coercion protobuf constructors do so those tests build messages the same way.
Treat a None scalar as the proto3 default so serialization omits it, matching protobuf: the numeric field encoders omitted only an exact zero, so a None timestamp (from a span with no start time) raised in struct.pack and the grpc client reported 'Exception serializing request!'. Decode the log request with the protobuf-backed reference class before MessageToDict, which needs a real DESCRIPTOR.
Extend the opentelemetry-codegen-pyproto plugin to emit every signal completely and replace the hand-written message modules with its output. The generator now emits IntEnum enum classes with a proper import, drops the builtins-qualified annotations, lifts top-level enum values to module scope, exposes the global___<Name> aliases, and generates the mutable oneof API: auto-vivifying message-oneof properties, scalar-oneof setters that clear siblings and range-check integer widths, with SerializeToString reading the backing fields. The generated classes serialize byte-for-byte identically to real protobuf (the equivalence suite) and are a drop-in for the exporters. Add scripts/proto_codegen_pyproto.sh to regenerate them. This also adds the message types and enums the hand-written set omitted (TracesData, MetricsData, EntityRef, AggregationTemporality, DataPointFlags).
Add the SPDX header to the hand-written pure-Python proto modules and their tests. Add the 5503 changelog fragment. Let codespell accept the 'Nd' in the struct format string in the field encoders.
The pure-Python vs protobuf benchmarks used the pytest-benchmark fixture but lived under tests/, so the normal test env collected them and errored without that plugin. Move them to opentelemetry-proto/benchmarks with a benchmark-requirements.txt and a dedicated tox env, matching the other packages.
6cd3ff2 to
abbba5e
Compare
What
Replace the
google.protobuf-generated message classes inopentelemetry-protowith a hand-written pure-Python protobuf implementation, and drop theprotobufdependency (protobuf>=5.0, <8.0) entirely.The public
opentelemetry.proto.*import namespace is preserved: real code lives under a privateopentelemetry._protopackage, and the existingopentelemetry.proto.*modules become thin re-export shims (from opentelemetry._proto... import *). Downstream code importingopentelemetry.proto.trace.v1.trace_pb2etc. keeps working unchanged.Why
protobufships a compiled C extension (upb) and enforces a narrow, moving version range. That causes real friction:protobufcan collide with the version the target app pins, producing hard-to-diagnose ABI/version errors. A pure-Python encoder is safe to inject.>=5.0,<8.0range regularly clashes with other libraries (gRPC stacks, ML tooling, cloud SDKs).The OTLP exporters only ever serialize protobuf; they never parse arbitrary messages. The only decode needed is the export-service response, which is empty. So a small, auditable encode-only implementation is sufficient — this is not a general-purpose protobuf runtime.
Scope of this PR
This is the base of a 4-PR stacked series that makes the whole OTLP export path dependency-light. Merge order:
opentelemetry-proto→ pure-Python (this PR)opentelemetry-exporter-otlp-proto-common→ pure-Python backendopentelemetry-exporter-otlp-proto-http→ pure-Python + stdliburllib(dropsrequests)opentelemetry-exporter-otlp-proto-grpc→ pure-Python gRPC (dropsgrpcio)Because this org's fork model can't host intermediate base branches upstream, each PR targets
mainand its diff is cumulative (this PR's changes appear in all four). Isolated per-package diffs are viewable via the fork compare links noted in PRs 2–4.Known gaps / discussion points (why this is a draft)
profilessignal not ported. The pure-Python impl currently coverscommon,resource,trace,metrics,logsand the collector service messages, but notopentelemetry.proto.profiles. That namespace is removed here and would need to be added before this could replace the current package for profiles users..protosources the project vendors, so schema changes stay mechanical. This PR carries a hand-written implementation; agreeing on a generator is part of the discussion._proto(real) +proto(shim) split is one option; collapsing intoprotodirectly is another. Happy to go either way.protobuf-generated serialization; a differential test against the current encoders would be worth adding.The reference implementation this is derived from has been running in a downstream distribution.
Stack (merge in order): #5503 (proto) → #5504 (common) → #5505 (http) → #5506 (grpc)