Skip to content

Prevent generated RPC methods from being shadowed by ServiceStub parameters#225

Open
gaoflow wants to merge 1 commit into
betterproto:mainfrom
gaoflow:fix-224-rpc-name-collision
Open

Prevent generated RPC methods from being shadowed by ServiceStub parameters#225
gaoflow wants to merge 1 commit into
betterproto:mainfrom
gaoflow:fix-224-rpc-name-collision

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 4, 2026

Copy link
Copy Markdown

Fixes #224.

Problem

Generated RPC methods are class-level methods on the ServiceStub subclass. ServiceStub.__init__ stored its channel/timeout/deadline/metadata parameters as plain instance attributes. When a generated RPC's snake_cased name collides with one of those names — e.g. an RPC literally named Metadata becomes metadata — the instance attribute shadows the class method, since instance attributes take precedence over class methods during attribute lookup. Calling the RPC then fails with TypeError: 'dict' object is not callable (or 'NoneType' object is not callable when no metadata was passed).

Fix

Store the four constructor parameters under private names (self._channel, etc.) and expose them through read-only @property accessors. A subclass method now overrides the base property in the MRO, so a colliding RPC method is reachable again. Non-colliding access (stub.channel, stub.metadata, …) still returns the parameter value exactly as before, so this is backwards compatible for normal reads. The properties are read-only; the parameters were never reassigned after __init__, so nothing in the codebase relied on writing to them.

Tests

Added tests/grpc/test_service_stub_attributes.py:

  • test_rpc_method_not_shadowed_by_constructor_param — a ServiceStub subclass defining an async metadata method (the colliding RPC); asserts the method is still callable and returns its own result. Fails before the change (metadata resolves to the dict), passes after.
  • test_constructor_params_exposed_as_properties — asserts channel/timeout/deadline/metadata properties return the values passed to __init__.

A generated RPC method is a class-level method on the ServiceStub subclass.
When its snake_cased name collides with a base __init__ parameter (e.g. an
RPC named Metadata -> metadata), the base class stored that parameter as an
instance attribute, which takes precedence over the class method on lookup.
Calling the RPC then raised TypeError ('dict'/'NoneType' object is not
callable).

Store channel/timeout/deadline/metadata under private names and expose them
through read-only properties. A subclass RPC method now overrides the base
property in the MRO, while non-colliding access still returns the parameter.

Fixes betterproto#224.
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.

Generated ServiceStub with rcp method "Metadata" gets overwriten by metadata parameter in parent class

1 participant