Library / SDK
nipunn1313/mypy-protobuf avatar
nipunn1313/mypy-protobuf

mypy-protobuf generates typed stubs that pyi_out does not

open source tools to generate mypy stubs from protobufs

709 stars81 forksPythonApache-2.0

At a glance

What is it?
mypy-protobuf is a protoc plugin that turns protobuf definitions into mypy stub files, and its selling point is what it adds over the compiler's own --pyi_out mode: typed HasField, WhichOneof and ClearField, correctly typed constructors, gRPC service stubs, and docstrings from comments. It is Apache-2.0 licensed and was built at Dropbox.
Who is it for?
mypy-protobuf suits a Python team that has protobuf definitions and wants a type checker to see them properly, since the stubs it generates describe field presence and gRPC services in a way the compiler's own pyi_out does not, and the plugin is installed as an ordinary protoc plugin. It is the wrong tool if you have no .proto files, and it is the wrong tool if you are still on Python 2.7, because 2.10 is the last version that supports it.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap it fills against protoc's own pyi_out

mypy-protobuf is a protoc plugin that generates mypy stub files from protobuf specs, and the README spends a long section on the difference between it and the protobuf compiler's own --pyi_out mode, marked as of 11/17/2025.

The differences listed are specific rather than cosmetic. mypy-protobuf generates stubs for GRPC services and clients, which pyi_out does not. It generates correctly typed HasField methods depending on field presence, where pyi_out does not type the HasField arguments. It generates correctly typed constructors depending on field presence. It generates correctly typed HasField, WhichOneof and ClearField methods, and it exports type aliases for the HasField, WhichOneof and ClearField arguments.

It also parses comments as docstrings, and it marks enums, enum values, messages, message fields, services and methods with @warnings.deprecated if the deprecation option is set to true.

The field presence point is the substantive one. In proto3 with explicit presence, HasField is how you test whether a scalar was actually set, and a stub that types its argument as a plain str gives you no checking on a call that can silently be wrong. A stub that narrows the argument to a Literal of the fields that actually have presence turns a runtime surprise into a type error.

There is one difference that goes the other way. There are differences in how mypy-protobuf and pyi_out generate enums, with a link to a protobuf issue for the details, so the two do not agree everywhere.

What the generated stub looks like

The README includes a full example of the generated output, and it is worth reading because it shows the mechanism rather than just the result.

The stub begins with a docstring saying it was generated by mypy-protobuf and should not be edited manually, followed by isort:skip_file, and then the imports of builtins, google.protobuf.descriptor, google.protobuf.message, sys and typing, with a conditional import of typing_extensions for Python below 3.10.

The messages are marked @typing.final, which is what you want for generated code, and each carries its DESCRIPTOR annotation. The field declarations are plain attributes, and the interesting part is the presence machinery. A _HasFieldArgType type alias is defined as a typing.Literal of the field names in both string and bytes form, and HasField takes that alias rather than a bare str:

python
    _HasFieldArgType: typing_extensions.TypeAlias = typing.Literal["thing", b"thing"]
    def HasField(self, field_name: _HasFieldArgType) -> builtins.bool: ...
    _ClearFieldArgType: typing_extensions.TypeAlias = typing.Literal["thing", b"thing"]
    def ClearField(self, field_name: _ClearFieldArgType) -> None: ...

The constructor is keyword-only, with each field typed as its own optional or default value, and the field name list varies by presence: the example shows that an implicit singular field gets a non-optional str with a default while the others get str | None, and the _HasFieldArgType list for the larger message includes the explicit and implicit singular fields but not the default one.

That asymmetry is the point. The stub encodes which fields have presence, so the type checker knows that HasField on a default field is meaningless and that passing a value for an implicit field is a different thing from setting an explicit one.

Installing it and running protoc

The plugin installs like any other Python package:

sh
pip3 install mypy-protobuf

To run mypy on the generated code you also need:

sh
pip3 install mypy>=0.910 types-protobuf>=0.1.14

And to install an unreleased version from git, the README gives a recipe with a REV variable:

sh
REV=main  # or whichever unreleased git rev you'd like
pip3 install git+https://github.com/nipunn1313/mypy-protobuf.git@$REV

On posix, protoc-gen-mypy is installed to python's executable bin, and assuming that is on your $PATH you run protoc with two output flags:

sh
protoc --python_out=output/location --mypy_out=output/location

If the plugin is not on the path, you can give protoc the location explicitly with the --plugin flag pointing at protoc-gen-mypy alongside the same two output flags.

The version check is a single command:

sh
> protoc-gen-mypy --version

The implementation of the plugin is in mypy_protobuf/main.py, which installs to an executable protoc-gen-mypy, and on Windows it installs to protoc-gen-mypy.exe. The pyproject.toml registers two console scripts, protoc-gen-mypy mapped to mypy_protobuf.main:main and protoc-gen-mypy_grpc mapped to mypy_protobuf.main:grpc, so the gRPC generator is a separate entry point rather than a mode of the first.

The version requirements, and the Python 2.7 boundary

The README separates the requirements to run the plugin from the requirements to run the type checkers on what it produces, and the split is worth keeping straight.

To run mypy-protobuf itself you need protoc 32.0 or newer, python-protobuf 6.32 or newer matching the protoc release, and Python 3.9 or later for running the plugin.

To run the type checkers on the generated stubs you need mypy 1.14.0 or newer or pyright 1.1.383 or newer, python-protobuf 6.32 matching the protoc release again, and types-protobuf 6.32 or newer for the stubs from the google.protobuf library.

The matching requirement is the one to pay attention to. The protoc version and the python-protobuf version are expected to be the same release, and the README repeats the note in both lists. A mismatch there is the kind of thing that produces confusing errors deep in a generated file rather than at the plugin boundary.

If you also generate code with the grpc plugin, there is a third list: grpcio 1.70 or newer, grpcio-tools 1.70 or newer, and types-grpcio 1.0.0.20251009 or newer. The README is careful to say that other configurations may work but are not continuously tested currently, and that the maintainers would be open to expanding the list if you file an issue on the issue tracker.

Then the boundary that ends the project's history: 2.10 is the last version of mypy-protobuf which supports targeting python 2.7. Anything after that drops it, so a project still on Python 2.7 is pinned to an old plugin as well as an old protobuf.

gRPC stubs, and the second plugin

The gRPC support is the feature that pyi_out simply does not have, and it explains why there are two console scripts rather than one.

The pyproject registers protoc-gen-mypy and protoc-gen-mypy_grpc as separate entry points, both pointing into mypy_protobuf.main, at the main and grpc functions respectively. The gRPC one produces the stubs for the service and client code that the plain protoc python_out does not cover.

That matters because the service half of a gRPC setup is exactly where a type checker has the least to work with otherwise. The protobuf message types are ordinary Python classes, but a service stub is a client class with a method per RPC, and each method's argument and return types come from the service definition rather than from a message. Without stubs for it, a typed codebase quietly becomes untyped at the network boundary, which is usually the boundary where mistakes are most expensive.

The repository layout is consistent with a project that takes stub correctness seriously. There is a proto/ directory holding the specifications the stubs are generated from, a test/ directory, a separate test_negative/ directory, and a stubtest_allowlist.txt. That last file is the interesting one: stubtest is the tool that checks whether stubs match the runtime behaviour of the library they describe, and an allowlist is the list of known, accepted exceptions. Keeping that list means the project runs stubtest and records the differences it has decided to tolerate, rather than skipping the check.

The build tooling is also strict by convention. The pyproject sets mypy strict = true with show_error_codes and the deprecated error code enabled, and it configures both black and isort with a black profile, extending the skip lists to exclude the generated _pb2.py and _pb2_grpc.py files, and giving generated code a line length of 10000 because the formatter is not supposed to reformat it.

Where mypy-protobuf fits, and where it stops

The alternatives are worth naming with their actual differences, because there is a genuine choice here.

The first alternative is protoc's own --pyi_out, with no extra plugin. It is already installed with protoc, it needs no matching version dance beyond protoc itself, and it produces stubs that a type checker will accept. You give up the typed HasField, the typed constructors, the gRPC service stubs, and the docstrings from comments. If your protos are simple message definitions with no field presence subtleties and no gRPC services, that is a reasonable place to be.

The second alternative is writing the stubs by hand or maintaining a separate hand-written type layer alongside the generated code. That is what some codebases do when the generated types do not match what they want, and it works until the proto changes and the hand-written layer has to be updated too. mypy-protobuf's position is that the presence typing and the gRPC coverage remove most of that reason to exist.

The limits are visible in the requirements. The plugin is tied to a protoc release and a matching python-protobuf release, and a project that has to support an unusual protobuf version may find the tested window narrow. The gRPC support additionally needs grpcio and grpcio-tools at 1.70 or newer.

The Python 2.7 line is closed at version 2.10, and the project was built originally with love at Dropbox, which dates it to the period when Python 2 was still a live target for many services. The current maintainers are Nipunn Koorapati, listed as the author, and Aidan Jensen, listed as a maintainer, and the licence is Apache 2.0, which is permissive enough for a build-time tool in a closed-source product.

The last push was on 2026-09-17 and the repository is not archived, and there are no GitHub releases, so the CHANGELOG.md at the root is where the version history actually lives.

Editorial conclusion

mypy-protobuf suits a Python team that has protobuf definitions and wants a type checker to see them properly, since the stubs it generates describe field presence and gRPC services in a way the compiler's own pyi_out does not, and the plugin is installed as an ordinary protoc plugin. It is the wrong tool if you have no .proto files, and it is the wrong tool if you are still on Python 2.7, because 2.10 is the last version that supports it. Before adopting it, check that your protoc and python-protobuf versions match, since the README requires the matching protoc release, and run protoc-gen-mypy --version to confirm which build you have.

Frequently asked questions

How do I install mypy-protobuf?

Run pip3 install mypy-protobuf, and then pip3 install mypy>=0.910 types-protobuf>=0.1.14 to be able to type check the generated code. Unreleased versions can be installed from git with pip3 install git+https://github.com/nipunn1313/mypy-protobuf.git@$REV, where REV is main or a specific git rev.

How do I run the mypy-protobuf plugin with protoc?

On posix, protoc-gen-mypy is installed to python's executable bin, so with that on your $PATH you run protoc --python_out=output/location --mypy_out=output/location. If it is not on the path, pass --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy alongside the same output flags.

What does mypy-protobuf generate that the pyi_out mode does not?

Stubs for gRPC services and clients, correctly typed HasField methods depending on field presence, correctly typed constructors depending on field presence, correctly typed HasField, WhichOneof and ClearField methods, type aliases exported for the HasField, WhichOneof and ClearField arguments, comments parsed as docstrings, and @warnings.deprecated markers when the deprecation option is set.

What are the version requirements for mypy-protobuf?

To run the plugin you need protoc 32.0 or newer, python-protobuf 6.32 or newer matching the protoc release, and Python 3.9 or later. To type check the output you need mypy 1.14.0 or newer or pyright 1.1.383 or newer, plus types-protobuf 6.32 or newer. Using the grpc plugin additionally needs grpcio and grpcio-tools 1.70 or newer and types-grpcio 1.0.0.20251009 or newer.

Does mypy-protobuf still support Python 2.7?

No. The README states that 2.10 is the last version of mypy-protobuf which supports targeting python 2.7, so a project on Python 2.7 is pinned to that older plugin as well as an older protobuf.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. nipunn1313/mypy-protobuf on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nipunn1313-mypy-protobuf.svg)](https://hysenlabs.com/projects/nipunn1313-mypy-protobuf)

Was this useful?