Skip to content

Reflection

protobuf-py exposes the full Protobuf descriptor model at runtime. Descriptors are the single source of truth for every message and enum: they describe the schema, and all serialization, presence tracking, and field access are driven through them.

Descriptor types

Descriptors wrap the compiler's internal descriptor messages and give them a convenient Python API. Their names all start with Desc:

Type Wraps Purpose
DescFile FileDescriptorProto A single .proto source file. Root of the hierarchy.
DescMessage DescriptorProto A message declaration.
DescField FieldDescriptorProto A field declared in a message.
DescOneof OneofDescriptorProto A oneof group in a message.
DescEnum EnumDescriptorProto An enum declaration.
DescEnumValue EnumValueDescriptorProto A single enum value.
DescExtension FieldDescriptorProto An extension field defined outside its container message.
DescService ServiceDescriptorProto An RPC service.
DescMethod MethodDescriptorProto A single RPC method.

Descriptors form a hierarchy rooted at a file:

DescFile
 ├─ messages: DescMessage
 │   ├─ fields:            DescField
 │   ├─ oneofs:            DescOneof
 │   ├─ members:           DescField | DescOneof  (fields + oneofs in source order)
 │   ├─ nested_messages:   DescMessage
 │   ├─ nested_enums:      DescEnum
 │   └─ nested_extensions: DescExtension
 ├─ enums: DescEnum
 │   └─ values: DescEnumValue
 ├─ extensions: DescExtension
 └─ services: DescService
     └─ methods: DescMethod

Getting a descriptor

Every generated class carries its descriptor. Use Message.desc() to retrieve it:

from gen.example_pb import User

desc = User.desc()    # DescMessage
desc.name             # "User"
desc.type_name        # "example.User"

The file-level descriptor is exported as a getter function from the generated _pb.py:

from gen import example_pb

example_pb.desc().name        # "example.proto"
example_pb.desc().messages    # [DescMessage, ...]

Next steps