danielgtaylor / python-betterproto

Clean, modern, Python 3.6+ code generator & library for Protobuf 3 and async gRPC
MIT License
1.54k stars 216 forks source link

betterproto-2.0.0b5 generated methods do not contain message instance as positional argument if message type name is not in UpperPascal case #438

Closed hf-kklein closed 1 year ago

hf-kklein commented 2 years ago

Steps to reproduce

Install betterproto-2.0.0b5 + tooling:

pip install --pre betterproto[compiler]
pip install grpcio-tools

other versions:

grpcio==1.50.0 grpcio-tools==1.50.0 grpclib==0.4.3

Create an example.proto

syntax = "proto3";

package echo;

message EchoRequest {
  string value = 1;
  // Number of extra times to echo
  uint32 extra_times = 2;
}

message EchoResponse {
  repeated string values = 1;
}

message EchoStreamResponse  {
  string value = 1;
}

service Echo {
  rpc Echo(EchoRequest) returns (EchoResponse);
  rpc EchoStream(EchoRequest) returns (stream EchoStreamResponse);
}

Create the directory

mkdir lib

Generate Python Code:

python -m grpc_tools.protoc -I . --python_betterproto_out=lib example.proto

Check that lib/echo/__init__.py contains a class EchoStub with a method that has echo_request as a positional argument ✔

class EchoStub(betterproto.ServiceStub):
    async def echo(
        self,
        echo_request: "EchoRequest",
        *,
        timeout: Optional[float] = None,
        deadline: Optional["Deadline"] = None,
        metadata: Optional["MetadataLike"] = None
    ) -> "EchoResponse":
    ...

Up to here, this is just the MWE from README.md.

Change Casing of Request Message Type Name

Now modify the above example.proto and change the casing of the request message from EchoRequest to EChoRequest with an upper case ECH at all 3 occurrences. (Note that the bug is not reproducable if you only change to EChoRequest with upper case EC but lower case h.)

syntax = "proto3";

package echo;

message ECHoRequest {
  string value = 1;
  // Number of extra times to echo
  uint32 extra_times = 2;
}

message EchoResponse {
  repeated string values = 1;
}

message EchoStreamResponse  {
  string value = 1;
}

service Echo {
  rpc Echo(ECHoRequest) returns (EchoResponse);
  rpc EchoStream(ECHoRequest) returns (stream EchoStreamResponse);
}

Repeat the next steps up to the code generation (don't forget to clean up lib before re-generating).

The newly generated lib/echo/__init__.py now still contains a class EchoStub:

class EchoStub(betterproto.ServiceStub):
    async def echo(
        self,
        *,
        timeout: Optional[float] = None,
        deadline: Optional["Deadline"] = None,
        metadata: Optional["MetadataLike"] = None
    ) -> "EchoResponse":
        return await self._unary_unary(
            "/echo.Echo/Echo",
            ec_ho_request, # <--- Bug: ec_ho_request is not provided as an argument
            EchoResponse,
            timeout=timeout,
            deadline=deadline,
            metadata=metadata,
        )
    ...

The ec_ho_request is undefined (other than echo_request in the MWE above) ❌

This leads to the following error at runtime:

TypeError: EchoStub.echo() takes 1 positional argument but 2 were given

Expected Behaviour

The code generation shall work for different casing, too.

hf-kklein commented 2 years ago

For a similar casing issue in the package name see #437.

Blackclaws commented 9 months ago

I am facing a similar problem using the protobuf files from the NI repo: https://github.com/ni/grpc-device