Skip to content

Python Language Support

Status: ✅ Full Support
Example: examples/python-example/

Python support provides comprehensive Protocol Buffer integration with multiple output options including gRPC, type stubs, and modern dataclass alternatives.

PluginDescriptionGenerated Files
protoc-gen-pythonBase message generation*_pb2.py
protoc-gen-grpc_pythongRPC services*_pb2_grpc.py
protoc-gen-pyiType stubs for IDE support*_pb2.pyi
protoc-gen-mypyMypy type checking stubs*_pb2.pyi, *_pb2_grpc.pyi
protoc-gen-python_betterprotoModern dataclass approach*.py (with @dataclass)
protoc-gen-python_betterproto (with pydantic)Validated dataclasses*.py (with Pydantic validation)
languages.python = {
enable = true;
outputPath = "proto/gen/python";
};
languages.python = {
enable = true;
outputPath = "proto/gen/python";
options = [];
# gRPC service generation
grpc = {
enable = true;
options = [];
};
# Type stubs for better IDE support
pyi = {
enable = true;
options = [];
};
# Mypy type checking support
mypy = {
enable = true;
options = [];
};
# Modern Python dataclasses (alternative to standard protobuf)
betterproto = {
enable = false; # Opt-in due to different API
pydantic = false; # Enable Pydantic dataclasses for validation
options = [];
};
};
proto/example/v1/example.proto
syntax = "proto3";
package example.v1;
message Greeting {
string id = 1;
string content = 2;
int64 created_at = 3;
repeated string tags = 4;
map<string, string> metadata = 5;
oneof optional_field {
string text_value = 6;
int32 numeric_value = 7;
}
}
enum GreetingType {
GREETING_TYPE_UNSPECIFIED = 0;
GREETING_TYPE_HELLO = 1;
GREETING_TYPE_GOODBYE = 2;
GREETING_TYPE_WELCOME = 3;
}
message CreateGreetingRequest {
string content = 1;
repeated string tags = 2;
GreetingType type = 3;
}
message CreateGreetingResponse {
Greeting greeting = 1;
}
message ListGreetingsRequest {
int32 page_size = 1;
string page_token = 2;
GreetingType type_filter = 3;
}
message ListGreetingsResponse {
repeated Greeting greetings = 1;
string next_page_token = 2;
}
service GreetingService {
rpc CreateGreeting(CreateGreetingRequest) returns (CreateGreetingResponse);
rpc ListGreetings(ListGreetingsRequest) returns (ListGreetingsResponse);
rpc StreamGreetings(ListGreetingsRequest) returns (stream Greeting);
rpc BidirectionalStream(stream CreateGreetingRequest) returns (stream Greeting);
}
import grpc
from proto.gen.python.example.v1 import example_pb2
from proto.gen.python.example.v1 import example_pb2_grpc
# Client usage
async def main():
# Create channel and stub
channel = grpc.insecure_channel('localhost:50051')
stub = example_pb2_grpc.GreetingServiceStub(channel)
# Create a greeting
request = example_pb2.CreateGreetingRequest(
content="Hello from Python!",
tags=["python", "grpc", "example"],
type=example_pb2.GREETING_TYPE_HELLO
)
response = stub.CreateGreeting(request)
print(f"Created greeting: {response.greeting.id}")
# Work with the message
greeting = response.greeting
print(f"Content: {greeting.content}")
print(f"Tags: {list(greeting.tags)}")
# Access map fields
greeting.metadata["author"] = "Python Client"
greeting.metadata["version"] = "1.0"
# Serialize to bytes
data = greeting.SerializeToString()
print(f"Serialized size: {len(data)} bytes")
# Deserialize from bytes
new_greeting = example_pb2.Greeting()
new_greeting.ParseFromString(data)
# JSON support
from google.protobuf.json_format import MessageToJson, Parse
json_str = MessageToJson(greeting)
print(f"JSON: {json_str}")
# Parse from JSON
from_json = Parse(json_str, example_pb2.Greeting())
import pytest
from proto.gen.python.example.v1 import example_pb2
def test_greeting_creation():
"""Test creating and serializing a greeting"""
greeting = example_pb2.Greeting(
id="test-1",
content="Test greeting",
created_at=1234567890
)
greeting.tags.extend(["test", "unit"])
greeting.metadata["test"] = "true"
# Test serialization
data = greeting.SerializeToString()
assert len(data) > 0
# Test deserialization
new_greeting = example_pb2.Greeting()
new_greeting.ParseFromString(data)
assert new_greeting.id == "test-1"
assert new_greeting.content == "Test greeting"
assert list(new_greeting.tags) == ["test", "unit"]
assert dict(new_greeting.metadata) == {"test": "true"}
@pytest.mark.asyncio
async def test_grpc_service():
"""Test gRPC service calls"""
# Mock or integration test your service
pass
  1. Use Type Stubs: Enable pyi or mypy for better IDE support
  2. Async Support: Use grpc.aio for async/await support
  3. Error Handling: Always handle grpc.RpcError exceptions
  4. Streaming: Use generators efficiently for streaming RPCs
  5. Betterproto: Consider for new projects wanting modern Python APIs
  6. Pydantic Validation: Enable betterproto.pydantic = true for data validation
  7. Package Structure: Follow Python package conventions
  8. Validation Strategy: Use proto field constraints for comprehensive validation
  • Directoryproto/ - gen/ - python/ - example/ - v1/ - __init__.py - example_pb2.py
  • example_pb2.pyi If pyi/mypy enabled - example_pb2_grpc.py - example_pb2_grpc.pyi If pyi/mypy enabled
  • Standard protobuf: Best performance, C++ implementation
  • Betterproto: Pure Python, slower but cleaner API
  • Betterproto + Pydantic: Additional validation overhead, enhanced safety
  • Serialization: Binary format is much smaller than JSON
  • Streaming: More efficient for large datasets
  • Validation: Pydantic validation has runtime cost but prevents data errors
Terminal window
# Standard Python example
cd examples/python-example
nix develop
bufrnix_init
bufrnix
python main.py
pytest -v
# Betterproto with Pydantic validation
cd examples/python-betterproto-pydantic
nix develop
nix run # Generate code
python test_pydantic.py # Test validation features

Ensure the generated code directory is in your Python path:

import sys
sys.path.append('./proto/gen/python')

For mypy to work correctly, use:

Terminal window
mypy --follow-imports=skip proto/gen/python/

Betterproto generates different code than standard protobuf. Don’t mix them in the same project.

For comprehensive validation with Pydantic:

  1. Define field constraints in your .proto files
  2. Use proto validation annotations (e.g., buf validate)
  3. Handle ValidationError exceptions in your code
  4. Consider validation performance impact for high-throughput scenarios
{
  description = "Python with betterproto example";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    bufrnix = {
      url = "github:conneroisu/bufrnix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    self,
    nixpkgs,
    flake-utils,
    bufrnix,
  }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = nixpkgs.legacyPackages.${system};

      bufrnixConfig = bufrnix.lib.mkBufrnix {
        inherit pkgs;
        config = {
          root = "./proto";

          # Python with betterproto for modern dataclasses
          languages.python = {
            enable = true;
            outputPath = "proto/gen/python";

            # Use betterproto instead of standard protobuf
            betterproto = {
              enable = true;
            };
          };
        };
      };
    in {
      devShells.default = pkgs.mkShell {
        buildInputs = with pkgs; [
          python3
          python3Packages.betterproto
          python3Packages.grpclib # betterproto uses grpclib instead of grpcio
          protobuf
        ];

        shellHook = ''
          echo "Python Betterproto Example"
          echo "========================="
          echo "Commands:"
          echo "  bufrnix_init - Initialize project"
          echo "  bufrnix - Generate betterproto code"
          echo "  python test_betterproto.py - Run test"
          echo ""
          echo "Note: Betterproto generates modern Python dataclasses"
          echo "      with async support and cleaner API"
          echo ""
          ${bufrnixConfig.shellHook}
        '';
      };
    });
}