Skip to content

Field Types

protoc-gen-pydantic supports all standard proto3 field types and generates correct Pydantic annotations with appropriate defaults.

Scalar fields

All proto3 scalar types map to native Python types:

Proto type Python type Default
string str ""
bool bool False
int32, sint32, sfixed32 int 0
uint32, fixed32 int 0
int64, sint64, sfixed64 ProtoInt64 0
uint64, fixed64 ProtoUInt64 0
float float 0.0
double float 0.0
bytes bytes b""

ProtoInt64 and ProtoUInt64 are type aliases for int that carry JSON serialization semantics (proto3 encodes 64-bit integers as strings in JSON).

message Person {
  string name   = 1;
  int32  age    = 2;
  bool   active = 3;
  double score  = 4;
  bytes  avatar = 5;
}
class Person(_ProtoModel):
    name: str = _Field(default="")
    age: int = _Field(default=0)
    active: bool = _Field(default=False)
    score: float = _Field(default=0.0)
    avatar: bytes = _Field(default=b"")

Optional fields

optional fields use T | None with a default of None, distinguishing "field not set" from the zero value:

message SearchRequest {
  optional string query           = 1;
  optional int32  page_size       = 2;
  optional bool   include_deleted = 3;
}
class SearchRequest(_ProtoModel):
    query: str | None = _Field(default=None)
    page_size: int | None = _Field(default=None)
    include_deleted: bool | None = _Field(default=None)

Repeated fields

repeated fields generate list[T] with default_factory=list:

message TaggedItem {
  string          name   = 1;
  repeated string tags   = 2;
  repeated int32  scores = 3;
}
class TaggedItem(_ProtoModel):
    name: str = _Field(default="")
    tags: list[str] = _Field(
        default_factory=list,
    )
    scores: list[int] = _Field(
        default_factory=list,
    )

Map fields

map<K, V> fields generate dict[K, V] with default_factory=dict:

message Config {
  map<string, string> labels   = 1;
  map<string, int32>  counters = 2;
}
class Config(_ProtoModel):
    labels: dict[str, str] = _Field(
        default_factory=dict,
    )
    counters: dict[str, int] = _Field(
        default_factory=dict,
    )

Oneof fields

oneof groups generate one field per variant, all typed as T | None = None. A @model_validator is generated for each group and raises ValidationError if more than one field is set, enforcing proto3's at-most-one semantics at runtime.

message Payment {
  oneof method {
    string credit_card = 1;
    string paypal      = 2;
    string bank_iban   = 3;
  }
}
class Payment(_ProtoModel):
    credit_card: str | None = _Field(
        default=None,
        description='Only one of the fields can be specified with: ["credit_card", "paypal", "bank_iban"] (oneof method)',
    )
    paypal: str | None = _Field(
        default=None,
        description='Only one of the fields can be specified with: ["credit_card", "paypal", "bank_iban"] (oneof method)',
    )
    bank_iban: str | None = _Field(
        default=None,
        description='Only one of the fields can be specified with: ["credit_card", "paypal", "bank_iban"] (oneof method)',
    )

    @_model_validator(mode="after")
    def _validate_oneof_method(self) -> "Payment":
        _set = [
            f
            for f in ("credit_card", "paypal", "bank_iban")
            if getattr(self, f) is not None
        ]
        if len(_set) > 1:
            raise ValueError(f"oneof 'method': only one field may be set, got {_set!r}")
        return self

Message fields

Message-typed fields default to None (not an empty sub-message):

message Address {
  string street = 1;
  string city   = 2;
}

message Order {
  string  order_id = 1;
  Address address  = 2;
}
class Address(_ProtoModel):
    street: str = _Field(default="")
    city: str = _Field(default="")


class Order(_ProtoModel):
    order_id: str = _Field(default="")
    address: "Address | None" = _Field(default=None)

Enum fields

Enum-typed fields also default to None. See the Enums page for full details.

message Task {
  string status_label = 1;
  Status status       = 2;

  enum Status {
    STATUS_UNSPECIFIED = 0;
    STATUS_OPEN        = 1;
    STATUS_DONE        = 2;
  }
}
class Task(_ProtoModel):
    class Status(str, _Enum):
        UNSPECIFIED = "UNSPECIFIED"  # 0
        OPEN = "OPEN"  # 1
        DONE = "DONE"  # 2

    status_label: str = _Field(default="")
    status: "Task.Status | None" = _Field(default=None)