0
# Code Generation
1
2
Protocol buffer compiler plugin that generates clean Python dataclasses from .proto files with proper type hints, async gRPC stubs, and modern Python conventions.
3
4
## Capabilities
5
6
### Plugin Entry Point
7
8
The main entry point function for the protoc plugin that processes protobuf definitions and generates Python code.
9
10
```python { .api }
11
def main() -> None:
12
"""
13
The plugin's main entry point.
14
15
Reads CodeGeneratorRequest from stdin, processes protobuf definitions,
16
and writes CodeGeneratorResponse to stdout.
17
"""
18
```
19
20
### Code Generation Core
21
22
The core function that transforms protobuf definitions into Python code.
23
24
```python { .api }
25
def generate_code(request, response) -> None:
26
"""
27
Generate Python code from protobuf definitions.
28
29
Args:
30
request: CodeGeneratorRequest from protoc
31
response: CodeGeneratorResponse to populate with generated files
32
"""
33
```
34
35
### Type Conversion Functions
36
37
Functions that convert protobuf types to appropriate Python types and handle type references.
38
39
```python { .api }
40
def get_ref_type(
41
package: str,
42
imports: set,
43
type_name: str,
44
unwrap: bool = True
45
) -> str:
46
"""
47
Return a Python type name for a proto type reference.
48
49
Args:
50
package: Current package name
51
imports: Set to add import statements to
52
type_name: Protobuf type name
53
unwrap: Whether to unwrap Google wrapper types
54
55
Returns:
56
Python type string
57
"""
58
59
def py_type(
60
package: str,
61
imports: set,
62
message: DescriptorProto,
63
descriptor: FieldDescriptorProto,
64
) -> str:
65
"""
66
Get Python type for a protobuf field descriptor.
67
68
Args:
69
package: Current package name
70
imports: Set to add import statements to
71
message: Message containing the field
72
descriptor: Field descriptor
73
74
Returns:
75
Python type string for the field
76
"""
77
78
def get_py_zero(type_num: int) -> str:
79
"""
80
Get Python zero/default value for a protobuf type number.
81
82
Args:
83
type_num: Protobuf type number
84
85
Returns:
86
String representation of the default value
87
"""
88
```
89
90
### Protobuf Structure Traversal
91
92
Functions for navigating and extracting information from protobuf definitions.
93
94
```python { .api }
95
def traverse(proto_file):
96
"""
97
Traverse protobuf file structure yielding items and their paths.
98
99
Args:
100
proto_file: Protobuf file descriptor
101
102
Yields:
103
Tuples of (item, path) for messages and enums
104
"""
105
106
def get_comment(proto_file, path: List[int], indent: int = 4) -> str:
107
"""
108
Extract comments from protobuf source code info.
109
110
Args:
111
proto_file: Protobuf file descriptor
112
path: Path to the element in the descriptor
113
indent: Indentation level for the comment
114
115
Returns:
116
Formatted comment string
117
"""
118
```
119
120
## Usage Examples
121
122
### Using the Plugin with protoc
123
124
```bash
125
# Install betterproto with compiler support
126
pip install "betterproto[compiler]"
127
128
# Generate Python code from .proto files
129
protoc -I . --python_betterproto_out=. example.proto
130
131
# Generate code for multiple files
132
protoc -I . --python_betterproto_out=. *.proto
133
134
# Specify include paths
135
protoc -I ./protos -I ./common --python_betterproto_out=./generated example.proto
136
```
137
138
### Example Proto File
139
140
```protobuf
141
syntax = "proto3";
142
143
package example;
144
145
// A simple greeting message
146
message HelloRequest {
147
string name = 1; // The name to greet
148
int32 count = 2; // Number of greetings
149
}
150
151
// Response with greeting
152
message HelloReply {
153
string message = 1;
154
repeated string additional_messages = 2;
155
}
156
157
// Greeting service
158
service Greeter {
159
// Sends a greeting
160
rpc SayHello (HelloRequest) returns (HelloReply);
161
162
// Sends multiple greetings
163
rpc SayManyHellos (HelloRequest) returns (stream HelloReply);
164
}
165
```
166
167
### Generated Python Code
168
169
The plugin generates clean, typed Python code:
170
171
```python
172
# Generated by the protocol buffer compiler. DO NOT EDIT!
173
# sources: example.proto
174
# plugin: python-betterproto
175
from dataclasses import dataclass
176
from typing import AsyncGenerator, List, Optional
177
178
import betterproto
179
from grpclib.client import Channel
180
181
182
@dataclass
183
class HelloRequest(betterproto.Message):
184
"""A simple greeting message"""
185
186
# The name to greet
187
name: str = betterproto.string_field(1)
188
# Number of greetings
189
count: int = betterproto.int32_field(2)
190
191
192
@dataclass
193
class HelloReply(betterproto.Message):
194
"""Response with greeting"""
195
196
message: str = betterproto.string_field(1)
197
additional_messages: List[str] = betterproto.string_field(2)
198
199
200
class GreeterStub(betterproto.ServiceStub):
201
"""Greeting service"""
202
203
async def say_hello(
204
self,
205
request: HelloRequest,
206
*,
207
timeout: Optional[float] = None,
208
deadline: Optional["betterproto.Deadline"] = None,
209
metadata: Optional["betterproto._MetadataLike"] = None,
210
) -> HelloReply:
211
"""Sends a greeting"""
212
return await self._unary_unary(
213
"/example.Greeter/SayHello",
214
request,
215
HelloReply,
216
timeout=timeout,
217
deadline=deadline,
218
metadata=metadata,
219
)
220
221
async def say_many_hellos(
222
self,
223
request: HelloRequest,
224
*,
225
timeout: Optional[float] = None,
226
deadline: Optional["betterproto.Deadline"] = None,
227
metadata: Optional["betterproto._MetadataLike"] = None,
228
) -> AsyncGenerator[HelloReply, None]:
229
"""Sends multiple greetings"""
230
async for response in self._unary_stream(
231
"/example.Greeter/SayManyHellos",
232
request,
233
HelloReply,
234
timeout=timeout,
235
deadline=deadline,
236
metadata=metadata,
237
):
238
yield response
239
```
240
241
### Complex Proto Features
242
243
The plugin handles advanced protobuf features:
244
245
```protobuf
246
syntax = "proto3";
247
package advanced;
248
249
import "google/protobuf/timestamp.proto";
250
import "google/protobuf/wrappers.proto";
251
252
enum Status {
253
UNKNOWN = 0;
254
ACTIVE = 1;
255
INACTIVE = 2;
256
}
257
258
message User {
259
string id = 1;
260
string name = 2;
261
Status status = 3;
262
263
// One-of fields
264
oneof contact {
265
string email = 4;
266
string phone = 5;
267
}
268
269
// Repeated and nested
270
repeated Address addresses = 6;
271
272
// Map fields
273
map<string, string> metadata = 7;
274
275
// Google types
276
google.protobuf.Timestamp created_at = 8;
277
google.protobuf.StringValue nickname = 9;
278
}
279
280
message Address {
281
string street = 1;
282
string city = 2;
283
string country = 3;
284
}
285
```
286
287
Generated code with proper typing:
288
289
```python
290
from dataclasses import dataclass
291
from datetime import datetime
292
from typing import Dict, List, Optional
293
import betterproto
294
295
class Status(betterproto.Enum):
296
UNKNOWN = 0
297
ACTIVE = 1
298
INACTIVE = 2
299
300
@dataclass
301
class Address(betterproto.Message):
302
street: str = betterproto.string_field(1)
303
city: str = betterproto.string_field(2)
304
country: str = betterproto.string_field(3)
305
306
@dataclass
307
class User(betterproto.Message):
308
id: str = betterproto.string_field(1)
309
name: str = betterproto.string_field(2)
310
status: Status = betterproto.enum_field(3)
311
312
# One-of fields
313
email: str = betterproto.string_field(4, group="contact")
314
phone: str = betterproto.string_field(5, group="contact")
315
316
# Repeated and nested
317
addresses: List[Address] = betterproto.message_field(6)
318
319
# Map fields
320
metadata: Dict[str, str] = betterproto.map_field(7, "string", "string")
321
322
# Google types (automatically unwrapped)
323
created_at: datetime = betterproto.message_field(8)
324
nickname: Optional[str] = betterproto.message_field(9, wraps="string")
325
```
326
327
### Plugin Configuration
328
329
The plugin can be configured through command-line options:
330
331
```bash
332
# Basic usage
333
protoc --python_betterproto_out=. example.proto
334
335
# With custom output directory
336
protoc --python_betterproto_out=./output example.proto
337
338
# Multiple include paths
339
protoc -I./protos -I./vendor --python_betterproto_out=. example.proto
340
```
341
342
### Integration with Build Systems
343
344
#### Using with Python setuptools
345
346
```python
347
# setup.py
348
from setuptools import setup
349
from betterproto.plugin import generate_proto_code
350
351
setup(
352
name="my-package",
353
# ... other setup parameters
354
)
355
356
# Custom command to generate proto code
357
if __name__ == "__main__":
358
# Generate proto code before building
359
import subprocess
360
subprocess.run([
361
"protoc",
362
"-I", "protos",
363
"--python_betterproto_out=src/generated",
364
"protos/*.proto"
365
])
366
```
367
368
#### Using with Make
369
370
```makefile
371
# Makefile
372
.PHONY: generate-proto
373
generate-proto:
374
protoc -I protos --python_betterproto_out=src/generated protos/*.proto
375
376
.PHONY: clean-proto
377
clean-proto:
378
rm -rf src/generated/*.py
379
380
build: generate-proto
381
python -m build
382
```
383
384
## Constants
385
386
```python { .api }
387
# Google wrapper type mappings
388
WRAPPER_TYPES: Dict[str, Optional[Type]] = {
389
"google.protobuf.DoubleValue": google_wrappers.DoubleValue,
390
"google.protobuf.FloatValue": google_wrappers.FloatValue,
391
"google.protobuf.Int64Value": google_wrappers.Int64Value,
392
"google.protobuf.UInt64Value": google_wrappers.UInt64Value,
393
"google.protobuf.Int32Value": google_wrappers.Int32Value,
394
"google.protobuf.UInt32Value": google_wrappers.UInt32Value,
395
"google.protobuf.BoolValue": google_wrappers.BoolValue,
396
"google.protobuf.StringValue": google_wrappers.StringValue,
397
"google.protobuf.BytesValue": google_wrappers.BytesValue,
398
}
399
```