Skip to content

fix(instrumentation-confluent-kafka): add __getattr__ delegation to ProxiedProducer and ProxiedConsumer - #4648

Open
stark256-spec wants to merge 5 commits into
open-telemetry:mainfrom
stark256-spec:fix/confluent-kafka-proxy-getattr
Open

fix(instrumentation-confluent-kafka): add __getattr__ delegation to ProxiedProducer and ProxiedConsumer#4648
stark256-spec wants to merge 5 commits into
open-telemetry:mainfrom
stark256-spec:fix/confluent-kafka-proxy-getattr

Conversation

@stark256-spec

Copy link
Copy Markdown

Description

Fixes #4278

ProxiedProducer and ProxiedConsumer only forward an explicit set of methods to the underlying confluent-kafka objects. Calling any method outside that list — such as list_topics(), assignment(), memberid(), or set_sasl_credentials() — raises AttributeError on pure-Python mocks or causes a segmentation fault on real confluent-kafka C-extension objects, because the C-extension base classes don't implement standard Python attribute lookup.

Add __getattr__ to both proxy classes so that any attribute or method lookup not satisfied by an explicitly defined proxy method is forwarded to the underlying _producer / _consumer via getattr(). Methods already explicitly proxied are unaffected.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

Two new unit tests added to tests/test_instrumentation.py:

  • test_proxied_producer_delegates_unproxied_methods — verifies ProxiedProducer forwards a method not in its explicit proxy list via __getattr__
  • test_proxied_consumer_delegates_unproxied_methods — same for ProxiedConsumer

Does This PR Require a Core Repo Change?

  • No.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 2, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: stark256-spec / name: stark256-spec (3e3d45b)

@stark256-spec
stark256-spec force-pushed the fix/confluent-kafka-proxy-getattr branch from 3e3d45b to 2440029 Compare June 3, 2026 16:28
…roxiedProducer and ProxiedConsumer

ProxiedProducer and ProxiedConsumer only forward an explicit list of
methods to the underlying confluent-kafka objects. Calling any method
outside that list (e.g. list_topics(), assignment(), memberid(),
set_sasl_credentials()) raises AttributeError on pure-Python mocks or
causes a segmentation fault on real confluent-kafka C-extension objects,
because the C-extension Producer/Consumer base classes don't implement
standard Python attribute lookup.

Add __getattr__ to both proxy classes so that any attribute or method
lookup not satisfied by an explicitly defined proxy method is forwarded
to the underlying _producer/_consumer via getattr(). This is the
standard Python delegation pattern and has no impact on methods that are
already explicitly proxied.

Fixes open-telemetry#4278

Assisted-by: Claude Sonnet 4.5
@stark256-spec
stark256-spec force-pushed the fix/confluent-kafka-proxy-getattr branch from 2440029 to 474a586 Compare June 3, 2026 16:31
@github-actions

Copy link
Copy Markdown

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.
If you're still working on this, please add a comment or push new commits.

@github-actions github-actions Bot added the Stale label Jun 18, 2026
@tammy-baylis-swi tammy-baylis-swi moved this to Ready for review in Python PR digest Jun 25, 2026

@xrmx xrmx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the proper fix would be to start using wrapt like the other instrumentations are doing, see #4270

@github-project-automation github-project-automation Bot moved this from Ready for review to Reviewed PRs that need fixes in Python PR digest Jul 1, 2026
@github-actions github-actions Bot removed the Stale label Jul 2, 2026
@stark256-spec

Copy link
Copy Markdown
Author

Makes sense — I'll switch this to wrapt.ObjectProxy instead of getattr.

… classes

ProxiedProducer and ProxiedConsumer forwarded only an explicit list of
methods to the underlying confluent-kafka objects, so any method outside
that list (list_topics(), assignment(), memberid(), set_sasl_credentials())
raised AttributeError on pure-Python mocks or segfaulted on the real
C-extension objects.

Base both proxies on wrapt.ObjectProxy, which transparently forwards every
attribute and method that is not explicitly overridden. This removes the
hand-maintained pass-through list (flush, poll, purge, committed, commit,
get_watermark_offsets, offsets_for_times, subscribe) along with the need for
manual __getattr__ delegation, leaving only the instrumented methods
(produce, poll, consume, close).

Proxy-local state uses ObjectProxy's _self_ storage. The two attributes the
consume-tracking helpers in utils.py read and write on the instance
(_current_consume_span, _current_context_token) are exposed as properties
backed by that storage, so the state stays on the proxy instead of being
forwarded onto the wrapped C-extension client.

Tests that asserted the exact proxy class now use assertIsInstance, since
ObjectProxy intentionally reports the wrapped object's __class__.
@stark256-spec
stark256-spec requested a review from a team as a code owner July 17, 2026 03:46
@stark256-spec

Copy link
Copy Markdown
Author

Switched to wrapt.ObjectProxy — thanks for the pointer, it's cleaner than the getattr approach.

Since ObjectProxy forwards anything not explicitly overridden, I was able to drop the whole hand-maintained pass-through list (flush, poll, purge, committed, commit, get_watermark_offsets, offsets_for_times, subscribe) — the proxies now only define the instrumented methods. Net -75/+72.

Two things worth flagging though ( assuming while youre doing diff might be useful):

utils.py reads and writes _current_consume_span / _current_context_token on the instance, so those are properties backed by ObjectProxy's self storage. Otherwise they'd get forwarded onto the wrapped consumer, which has no dict.
I had to change assertEqual(x.class, ProxiedProducer) to assertIsInstance in the existing tests, since ObjectProxy intentionally reports the wrapped object's class. The uninstrument assertions are unchanged and still pass.
Also rebased onto main and added a .changelog/4648.fixed fragment.
Let me know if you see a concern,, will try to figure out a another approach...
Tests pass, ruff and pylint clean.

@xrmx xrmx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks better!



class ProxiedProducer(Producer):
class ProxiedProducer(wrapt.ObjectProxy):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use BaseObjectProxy if available becase ObjectProxy always implementes __iter__ even if the proxied object doesn't, we do this in other instrumentations:

try:
    # wrapt 2.0.0+
    from wrapt import BaseObjectProxy
except ImportError:
    from wrapt import ObjectProxy as BaseObjectProxy

Comment on lines +201 to +220
# The consume-tracking helpers in utils.py read and write these two
# attributes on the instance. Back them with ObjectProxy's `_self_`
# storage via properties so the state lives on the proxy rather than being
# forwarded onto the wrapped consumer, whose C-extension type does not
# accept arbitrary attributes.
@property
def _current_consume_span(self):
return self._self_current_consume_span

@_current_consume_span.setter
def _current_consume_span(self, value) -> None:
self._self_current_consume_span = value

@property
def _current_context_token(self):
return self._self_current_context_token

@_current_context_token.setter
def _current_context_token(self, value) -> None:
self._self_current_context_token = value

@xrmx xrmx Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much value on these wrappers, I think we can live with the caller referencing the ._self_ version directly

self.assertEqual(consumer.__class__, ProxiedConsumer)
# See test_instrument_api: ObjectProxy reports the wrapped __class__.
self.assertIsInstance(consumer, ProxiedConsumer)
# commit() is no longer declared on the proxy; ObjectProxy forwards it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# commit() is no longer declared on the proxy; ObjectProxy forwards it.

Looks more like a changelog

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

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.
If you're still working on this, please add a comment or push new commits.

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 14, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-08-29 16:22 UTC

Respond to 3 review items (e.g. link a commit, explain why not, ask a follow-up):

  • Inline threads: 1, 2, 3
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

@github-actions github-actions Bot removed the Stale label Aug 15, 2026
@opentelemetry-pr-dashboard

Copy link
Copy Markdown

Hi @stark256-spec — just a friendly reminder that this pull request is waiting on you. The dashboard status comment has the open items and is kept current.

  • Replying is enough to hand it off — answer, explain why no change is needed, or ask a follow-up. The dashboard routes it onward once nothing on the list is waiting on you.
  • To hand it back for any other reason, including the dashboard getting this wrong, comment /dashboard route:reviewers.

…-proxy-getattr

# Conflicts:
#	instrumentation/opentelemetry-instrumentation-confluent-kafka/src/opentelemetry/instrumentation/confluent_kafka/__init__.py
@stark256-spec

Copy link
Copy Markdown
Author

Merged latest main — the conflicts are resolved and the PR is mergeable again.

The only conflict was in the confluent-kafka proxy classes: main recently added two pass-through methods to ProxiedConsumer (get_watermark_offsets, offsets_for_times). Since this PR switches the proxies to wrapt.ObjectProxy (per the earlier review — dropping __getattr__ and the manual pass-throughs), those methods are now forwarded transparently, so I kept the ObjectProxy implementation and let it delegate them rather than re-adding the manual overrides.

Verified locally: all 18 package tests pass — including test_proxied_consumer_delegates_unproxied_methods / test_proxied_producer_delegates_unproxied_methods, which cover exactly that delegation — and ruff check is clean.

@xrmx this should be ready for another look whenever you have a chance — thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Reviewed PRs that need fixes

Development

Successfully merging this pull request may close these issues.

[ConfluentKafkaInstrumentor] Not all methods are supported by returned proxy objects

3 participants