Nested Types¶
Proto3 allows messages and enums to be defined inside other messages. protoc-gen-pydantic
generates these as true Python nested classes, so they are accessible via dotted attribute
access — exactly as you would expect from idiomatic Python.
Nested messages¶
class Shipment(_ProtoModel):
class Status(str, _Enum):
UNSPECIFIED = "UNSPECIFIED" # 0
PENDING = "PENDING" # 1
SHIPPED = "SHIPPED" # 2
DELIVERED = "DELIVERED" # 3
class Item(_ProtoModel):
sku: str = _Field(default="")
quantity: int = _Field(default=0)
price: float = _Field(default=0.0)
order_id: str = _Field(default="")
items: "list[Shipment.Item]" = _Field(
default_factory=list,
)
status_note: str = _Field(default="")
status: "Shipment.Status | None" = _Field(default=None)
# Usage
shipment = Shipment(
order_id="shp-1",
items=[
Shipment.Item(sku="ABC", quantity=2, price=9.99),
Shipment.Item(sku="XYZ", quantity=1, price=24.99),
],
)
print(shipment.items[0].sku) # ABC
Nested enums¶
Enums nested inside a message become nested classes of that message:
class Shipment(_ProtoModel):
class Status(str, _Enum):
UNSPECIFIED = "UNSPECIFIED" # 0
PENDING = "PENDING" # 1
SHIPPED = "SHIPPED" # 2
DELIVERED = "DELIVERED" # 3
class Item(_ProtoModel):
sku: str = _Field(default="")
quantity: int = _Field(default=0)
price: float = _Field(default=0.0)
order_id: str = _Field(default="")
items: "list[Shipment.Item]" = _Field(
default_factory=list,
)
status_note: str = _Field(default="")
status: "Shipment.Status | None" = _Field(default=None)
Deeply nested types¶
Nesting can go arbitrarily deep:
class Outer(_ProtoModel):
"""
Outer message comment.
"""
class OuterEnum(str, _Enum):
"""
Outer enum comment.
"""
UNSPECIFIED = "UNSPECIFIED" # 0
X = "X" # 1
class Inner(_ProtoModel):
"""
Inner message comment.
"""
class InnerEnum(str, _Enum):
"""
Inner enum comment.
"""
UNSPECIFIED = "UNSPECIFIED" # 0
A = "A" # 1
class Deepest(_ProtoModel):
"""
Deepest message comment.
"""
# Deepest field comment.
deepest_field: str = _Field(
default="",
description="Deepest field comment.",
)
# Inner field comment.
inner_field: str = _Field(
default="",
description="Inner field comment.",
)
# Outer field comment.
outer_field: str = _Field(
default="",
description="Outer field comment.",
)
Cross-file references¶
When a message in one file references a nested type from another file, the import uses only the top-level class name. The nested path is resolved via dotted attribute access at runtime:
# gen/collections_pydantic.py
from .scalars_pydantic import Scalars
class Collections(_ProtoModel):
nested_enum_repeated: "list[Scalars.NestedEnum]" = _Field(default_factory=list)
This means you only import Scalars, not Scalars.NestedEnum directly — Python resolves the
dotted access automatically.