Search code examples
pythonprotocol-buffersmypypython-typinggrpc-python

Error with stubgen and generic used by mypy-protobuf


When I use the protoc-gen-mypy plugin from mypy-protobuf with protoc to generate mypy stubs for my gRPC Greeter service, I get an error about generics.

Here is the command I used to generate the greeter_pb2_grpc.pyi file:

python -m grpc_tools.protoc --plugin=protoc-gen-mypy=`which protoc-gen-mypy` -Iprotos --mypy_grpc_out=grpc protos/greeter.proto

Here is the content of greeter_pb2_grpc.pyi:

"""
@generated by mypy-protobuf.  Do not edit manually!
isort:skip_file
"""
import abc
import greeter_pb2
import grpc

class GreeterStub:
    """The greeting service definition."""
    def __init__(self, channel: grpc.Channel) -> None: ...
    SayHello: grpc.UnaryUnaryMultiCallable[
        greeter_pb2.HelloRequest,
        greeter_pb2.HelloReply]
    """Sends a greeting"""


class GreeterServicer(metaclass=abc.ABCMeta):
    """The greeting service definition."""
    @abc.abstractmethod
    def SayHello(self,
        request: greeter_pb2.HelloRequest,
        context: grpc.ServicerContext,
    ) -> greeter_pb2.HelloReply:
        """Sends a greeting"""
        pass


def add_GreeterServicer_to_server(servicer: GreeterServicer, server: grpc.Server) -> None: ...

When I run mypy, I get this error in the greeter_pb2_grpc.pyi file:

"UnaryUnaryMultiCallable" expects no type arguments, but 2 given

The actual definition in the grpc library is:

class UnaryUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)): ...

So, stubgen generates a stub like this:

class UnaryUnaryMultiCallable(metaclass=abc.ABCMeta):

I don't want to edit what mypy-protobuf generates because what I usually edit is what stubgen generates.

How should I edit the grpc/__init__.pyi file that stubgen generates to make mypy not complain?


Solution

  • You don't need to generate your own stubs for grpc because grpc-stubs already does that.

    And this library uses this for the class definition (see here):

    class UnaryUnaryClientInterceptor(typing.Generic[TRequest, TResponse]):
    

    So, just doing pip install grpc-stubs and removing your grpc/**/*.pyi stubs for grpc should do the trick.


    The above is a good solution if you are not using grpc.aio.

    However, if you use grpc.aio and have, for instance, a class inheriting from a grpc.aio.ServerInterceptor like this:

    class LoggingInterceptor(grpc.aio.ServerInterceptor):
    

    Because of this grpc-stubs issue, you'll need to do something more manual.

    I personally had to create my own stubs from scratch (with the help of stubgen) because I couldn't find a way to merge custom stubs with stubs coming from grpc-stubs (like how Declaration Merging would work in TypeScript).

    Don't uninstall grpc-stubs because you'll still need it to have types for grpc_health and grpc_reflection.

    I'm not including what I did in this StackOverflow answer because it is only has a partial coverage of grpc and grpc.aio and it depends on my server code.