protoc-gen-pydantic
Define your data schema once in Protobuf. Get validated, type-safe Python models automatically.
If you work with Protobuf APIs in Python, you face a familiar tradeoff: use the raw _pb2
classes — no validation, no editor support — or hand-write parallel Pydantic models and keep
them in sync forever. protoc-gen-pydantic generates Pydantic v2 models directly from your
.proto files, so your schema stays the single source of truth.
How it works¶
protoc-gen-pydantic is a protoc plugin written in Go. You run buf generate (or protoc)
once, and the plugin reads your .proto files and writes ready-to-use Python files alongside
them. After that, code generation is the only step — no runtime dependency on the plugin itself.
flowchart LR
A["proto/user.proto"] -->|buf generate| B["gen/user_pydantic.py<br/>gen/_proto_types.py"]
B --> C["from user_pydantic import User"]
Every generated message class inherits from _ProtoModel, a thin base class that overrides
model_dump and model_dump_json with ProtoJSON defaults on top of standard Pydantic. See
Generated Model API for the full interface.
Basic usage¶
A single buf generate command turns any .proto file into a ready-to-use Pydantic model:
The generated model validates inputs immediately — no extra setup, no runtime surprises.
With validation constraints¶
Add buf.validate constraints to your proto fields, and the generator translates them
directly into Pydantic validation:
syntax = "proto3";
package example;
import "buf/validate/validate.proto";
// A user account.
message ValidatedUser {
// Display name (1–50 characters).
string name = 1 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 50
];
// Age in years.
int32 age = 2 [(buf.validate.field).int32.gte = 0];
// Contact email address.
string email = 3 [(buf.validate.field).string.email = true];
enum Role {
ROLE_UNSPECIFIED = 0;
ROLE_VIEWER = 1;
ROLE_EDITOR = 2;
ROLE_ADMIN = 3;
}
Role role = 4;
}
class ValidatedUser(_ProtoModel):
"""
A user account.
"""
class Role(str, _Enum):
UNSPECIFIED = "UNSPECIFIED" # 0
VIEWER = "VIEWER" # 1
EDITOR = "EDITOR" # 2
ADMIN = "ADMIN" # 3
# Display name (1–50 characters).
name: str = _Field(
description="Display name (1–50 characters).",
min_length=1,
max_length=50,
)
# Age in years.
age: int = _Field(
default=0,
description="Age in years.",
ge=0,
)
# Contact email address.
email: _Annotated[str, _AfterValidator(_validate_email)] = _Field(
description="Contact email address.",
)
role: "ValidatedUser.Role | None" = _Field(
default=None,
examples=[1],
)
The generated model is immediately usable — construct, serialize, and validate with standard Pydantic:
from user_pydantic import ValidatedUser
# Construct and validate
user = ValidatedUser(name="Alice", age=30, email="alice@example.com", role=ValidatedUser.Role.EDITOR)
# Serialize (ProtoJSON — omits zero values, uses original proto field names)
print(user.model_dump_json())
# {"name":"Alice","age":30,"email":"alice@example.com","role":"EDITOR"}
# Validation errors are raised immediately
ValidatedUser(name="", age=-1) # raises ValidationError (3 validation errors)
Acknowledgements¶
This project is a fork of ornew/protoc-gen-pydantic by Arata Furukawa, which provided the initial plugin structure and plugin options. This fork adds well-known type mappings, Python builtin/keyword alias handling, cross-package references, enum value options, ProtoJSON-compatible output, buf.validate constraint translation, CEL expression transpilation, conditional imports, and a full test suite.