Skip to content

Reserved Names

Proto field names can clash with Python builtins, keywords, and Pydantic BaseModel attributes. protoc-gen-pydantic handles these automatically using a PEP 8 trailing underscore alias.

How it works

When a proto field name is a reserved word in Python, the generator:

  1. Appends _ to the Python attribute name (e.g. boolbool_)
  2. Sets alias="<original_name>" on the field so JSON / dict serialization still uses the original proto name
  3. Adds populate_by_name=True to model_config so you can pass either the alias or the Python name when constructing the model
message BuiltinNames {
  bool  bool  = 1;
  float float = 2;
  bytes bytes = 3;
  int32 int   = 4;
}
class BuiltinNames(_ProtoModel):
    model_config = _ConfigDict(populate_by_name=True, protected_namespaces=())

    bool_: bool = _Field(
        default=False,
        alias="bool",
    )
    float_: float = _Field(
        default=0.0,
        alias="float",
    )
    bytes_: bytes = _Field(
        default=b"",
        alias="bytes",
    )
    int_: int = _Field(
        default=0,
        alias="int",
    )

Reserved name categories

The following categories of names trigger the trailing-underscore rename:

Python builtins: bool, bytes, complex, dict, float, frozenset, int, list, map, object, set, str, tuple, type, …

Python keywords: and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield, False, None, True

Pydantic BaseModel attributes: model_config, model_fields, model_dump, model_validate, model_json_schema, and other model_* names that would shadow Pydantic internals

message ReservedFieldNames {
  string model_config = 1;
  string model_fields = 2;
  string model_dump   = 3;
}
class ReservedFieldNames(_ProtoModel):
    model_config = _ConfigDict(populate_by_name=True, protected_namespaces=())

    model_config_: str = _Field(
        default="",
        alias="model_config",
    )
    model_fields_: str = _Field(
        default="",
        alias="model_fields",
    )
    model_dump_: str = _Field(
        default="",
        alias="model_dump",
    )

Using the aliased fields

Because populate_by_name=True is set, you can use either the Python name or the proto alias:

# Using the Python name (trailing underscore)
b = BuiltinNames(bool_=True, float_=3.14)

# Using the original proto alias
b = BuiltinNames(**{"bool": True, "float": 3.14})

# Serialization always uses the proto name (no trailing underscore)
print(b.model_dump())
# {"bool": True, "float": 3.14}

buf.validate + reserved names

When a reserved field also carries buf.validate constraints, both the alias= and the constraint kwargs are emitted in a single _Field() call:

message ValidatedReserved {
  float float = 1 [(buf.validate.field).float.gt = 0.0];
}
class ValidatedReserved(_ProtoModel):
    model_config = _ConfigDict(populate_by_name=True, ...)

    float_: "float" = _Field(0.0, alias="float", gt=0.0)