or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

code-generation.mdenumerations.mdgrpc-services.mdindex.mdmessage-fields.mdserialization.mdutilities.md

code-generation.mddocs/

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

```