Oven logo

Oven

Published

Idiomatic Protocol Buffers for Python.

pip install protobuf-py

Package Downloads

Weekly DownloadsMonthly Downloads

Authors

Requires Python

>=3.10

Dependencies

  • protobuf-py-ext==0.6.0; platform_machine == 'arm64' and platform_python_implementation == 'CPython' and sys_platform == 'darwin' or platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux' or platform_machine == 'x86_64' and platform_python_implementation == 'CPython' and sys_platform == 'linux' or platform_machine == 'AMD64' and platform_python_implementation == 'CPython' and sys_platform == 'win32' or platform_machine == 'ARM64' and platform_python_implementation == 'CPython' and sys_platform == 'win32'
  • typing-extensions>=4.13.0

The Buf logo

protobuf-py

PyPI version License Slack

protobuf-py is an ergonomic and modern Protobuf library for Python.

Well-typed, generated code you can read. High-performance encoder/decoder written in Rust. 100% conformance.
Zero third-party dependencies or tooling required.

Getting started • Examples • New to Protobuf?

from gen.user_pb import User

# Messages are plain Python objects: pass fields as keyword args, or set them later.
user = User(id="123", first_name="Alice")
user.last_name = "Smith"

# Serialize to the Protobuf wire format, then parse it back.
data = user.to_binary()
user = User.from_binary(data)

print(user.first_name)  # Alice
print(user.to_json())  # {"id": "123", "firstName": "Alice", "lastName": "Smith"}

Protobuf is the easiest way to build APIs. We recommend using it with Connect for Python, which generates server stubs for you:

from connectrpc.request import RequestContext

from gen.user_connect import UserService, UserServiceASGIApplication
from gen.user_pb import GetUserRequest, GetUserResponse, User


class UserHandler(UserService):
    async def get_user(
        self, request: GetUserRequest, ctx: RequestContext
    ) -> GetUserResponse:
        user = User(id=request.id, first_name="Alice", last_name="Smith")
        return GetUserResponse(user=user)


# Serve it with any ASGI server, e.g. `uvicorn server:app`.
app = UserServiceASGIApplication(UserHandler())

Connect can also generate client libraries for every major language, including your frontend. Here's what the generated Python client looks like:

from gen.user_connect import UserServiceClient
from gen.user_pb import GetUserRequest

client = UserServiceClient("http://localhost:8080")
response = client.get_user(GetUserRequest(id="123"))

print(response.user.first_name)  # Alice

Quickstart

// proto/user.proto
syntax = "proto3";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}

message GetUserRequest {
  string id = 1;
}

message GetUserResponse {
  User user = 1;
}

message User {
  string id = 1;
  string first_name = 2;
  string last_name = 3;
}
# buf.gen.yaml
version: v2
inputs:
  - directory: proto
plugins:
  - local: protoc-gen-py
    out: src/gen
  - local: protoc-gen-connectrpc
    out: src/gen
$ uv add protobuf-py connectrpc
$ uv add --dev protoc-gen-py protoc-gen-connectrpc buf-bin
$ uv run -- buf generate

That's all - typed messages and Connect stubs now live in src/gen.

Feature highlights

Generated code you can read

class User(Message[_UserFields]):
    __slots__ = ("id", "first_name", "last_name")

    def __init__(
        self, *, id: str = "", first_name: str = "", last_name: str = ""
    ) -> None: ...

    id: str
    first_name: str
    last_name: str

Typed oneofs with pattern matching

from protobuf import Oneof

match msg.result:
    case Oneof(field="value", value=v):
        handle_value(v)
    case Oneof(field="error", value=e):
        handle_error(e)

Well-known types with friendly helpers

from datetime import UTC, datetime, timedelta
from protobuf.wkt import Any, Duration, Timestamp

ts = Timestamp.from_datetime(datetime.now(UTC))
td = Duration.from_timedelta(timedelta(minutes=5))
packed = Any.pack(user)

Seamless reflection

# Iterate over message fields
for field in user:
    # Access field name / value
    value = user[field]
    print(field.name, value)

    # Check for presence
    print(field in user)

    # Assign / delete fields
    user[field] = value
    del user[field]

How it compares

  • google-protobuf is complete, but generates code with unreadable binary blobs, broken imports, and a hostile API.
  • betterproto improves ergonomics, but it is proto3-only. The original project now points to betterproto2, which calls out active development, incomplete docs, and breaking changes.
Click here for a full comparison.
protobuf-pygoogle-protobufbetterproto
Spec coverage✅ 100%⚠️ Known conformance failures❌ proto3-only
Type annotations✅ Built-in❌ Third-party tooling needed✅ Built-in
Readable generated code✅❌ Classes are built only at runtime and cannot be inspected✅
Imports✅ Relative imports❌ Broken without third-party tooling✅ Relative imports
Oneofs✅ Ergonomic, match-compatible❌ String-returning WhichOneof()⚠️ Tuple-returning helpers
Enums✅ Python-native IntEnum❌ int + EnumTypeWrapper⚠️ Custom int subclass
Field presence✅ msg.has_field("x") with IDE completions⚠️ HasField("x") raises for proto3 scalars⚠️ Helper-based
Field assignment (foo.x = 123)✅ Direct assignment❌ CopyFrom() required✅ Direct assignment
copy.copy() / copy.replace()✅❌❌
Global mutable registry✅ Explicit Registry❌ Process-wide singleton behavior✅ N/A
Zero dependencies✅✅❌ Includes grpclib, python-dateutil

Migration guide

google-protobufprotobuf-py
msg.SerializeToString()msg.to_binary()
MessageType.FromString(data)MessageType.from_binary(data)
msg.HasField("nickname")msg.has_field("nickname")
msg.WhichOneof("result") + string checksmatch msg.result with typed Oneof
msg.Extensions[ext]msg[ext]
msg.child.CopyFrom(other)msg.child = other