betterproto2-compiler 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. betterproto2_compiler/__init__.py +0 -0
  2. betterproto2_compiler/_types.py +13 -0
  3. betterproto2_compiler/casing.py +140 -0
  4. betterproto2_compiler/compile/__init__.py +0 -0
  5. betterproto2_compiler/compile/importing.py +193 -0
  6. betterproto2_compiler/compile/naming.py +21 -0
  7. betterproto2_compiler/enum.py +180 -0
  8. betterproto2_compiler/grpc/__init__.py +0 -0
  9. betterproto2_compiler/grpc/grpclib_client.py +172 -0
  10. betterproto2_compiler/grpc/grpclib_server.py +32 -0
  11. betterproto2_compiler/grpc/util/__init__.py +0 -0
  12. betterproto2_compiler/grpc/util/async_channel.py +190 -0
  13. betterproto2_compiler/lib/__init__.py +0 -0
  14. betterproto2_compiler/lib/google/__init__.py +0 -0
  15. betterproto2_compiler/lib/google/protobuf/__init__.py +1 -0
  16. betterproto2_compiler/lib/google/protobuf/compiler/__init__.py +1 -0
  17. betterproto2_compiler/lib/pydantic/__init__.py +0 -0
  18. betterproto2_compiler/lib/pydantic/google/__init__.py +0 -0
  19. betterproto2_compiler/lib/pydantic/google/protobuf/__init__.py +2690 -0
  20. betterproto2_compiler/lib/pydantic/google/protobuf/compiler/__init__.py +209 -0
  21. betterproto2_compiler/lib/std/__init__.py +0 -0
  22. betterproto2_compiler/lib/std/google/__init__.py +0 -0
  23. betterproto2_compiler/lib/std/google/protobuf/__init__.py +2517 -0
  24. betterproto2_compiler/lib/std/google/protobuf/compiler/__init__.py +197 -0
  25. betterproto2_compiler/plugin/__init__.py +3 -0
  26. betterproto2_compiler/plugin/__main__.py +3 -0
  27. betterproto2_compiler/plugin/compiler.py +59 -0
  28. betterproto2_compiler/plugin/main.py +52 -0
  29. betterproto2_compiler/plugin/models.py +709 -0
  30. betterproto2_compiler/plugin/module_validation.py +161 -0
  31. betterproto2_compiler/plugin/parser.py +263 -0
  32. betterproto2_compiler/plugin/plugin.bat +2 -0
  33. betterproto2_compiler/plugin/typing_compiler.py +167 -0
  34. betterproto2_compiler/py.typed +0 -0
  35. betterproto2_compiler/templates/header.py.j2 +50 -0
  36. betterproto2_compiler/templates/template.py.j2 +243 -0
  37. betterproto2_compiler-0.0.1.dist-info/LICENSE.md +22 -0
  38. betterproto2_compiler-0.0.1.dist-info/METADATA +35 -0
  39. betterproto2_compiler-0.0.1.dist-info/RECORD +41 -0
  40. betterproto2_compiler-0.0.1.dist-info/WHEEL +4 -0
  41. betterproto2_compiler-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,709 @@
1
+ """Plugin model dataclasses.
2
+
3
+ These classes are meant to be an intermediate representation
4
+ of protobuf objects. They are used to organize the data collected during parsing.
5
+
6
+ The general intention is to create a doubly-linked tree-like structure
7
+ with the following types of references:
8
+ - Downwards references: from message -> fields, from output package -> messages
9
+ or from service -> service methods
10
+ - Upwards references: from field -> message, message -> package.
11
+ - Input/output message references: from a service method to it's corresponding
12
+ input/output messages, which may even be in another package.
13
+
14
+ There are convenience methods to allow climbing up and down this tree, for
15
+ example to retrieve the list of all messages that are in the same package as
16
+ the current message.
17
+
18
+ Most of these classes take as inputs:
19
+ - proto_obj: A reference to it's corresponding protobuf object as
20
+ presented by the protoc plugin.
21
+ - parent: a reference to the parent object in the tree.
22
+
23
+ With this information, the class is able to expose attributes,
24
+ such as a pythonized name, that will be calculated from proto_obj.
25
+
26
+ The instantiation should also attach a reference to the new object
27
+ into the corresponding place within it's parent object. For example,
28
+ instantiating field `A` with parent message `B` should add a
29
+ reference to `A` to `B`'s `fields` attribute.
30
+ """
31
+
32
+ import builtins
33
+ import re
34
+ from dataclasses import (
35
+ dataclass,
36
+ field,
37
+ )
38
+ from typing import (
39
+ Dict,
40
+ Iterable,
41
+ Iterator,
42
+ List,
43
+ Optional,
44
+ Set,
45
+ Type,
46
+ Union,
47
+ )
48
+
49
+ import betterproto2_compiler
50
+ from betterproto2_compiler.compile.naming import (
51
+ pythonize_class_name,
52
+ pythonize_field_name,
53
+ pythonize_method_name,
54
+ )
55
+ from betterproto2_compiler.lib.google.protobuf import (
56
+ DescriptorProto,
57
+ EnumDescriptorProto,
58
+ FieldDescriptorProto,
59
+ FieldDescriptorProtoLabel,
60
+ FieldDescriptorProtoType,
61
+ FileDescriptorProto,
62
+ MethodDescriptorProto,
63
+ )
64
+ from betterproto2_compiler.lib.google.protobuf.compiler import CodeGeneratorRequest
65
+
66
+ from ..compile.importing import get_type_reference, parse_source_type_name
67
+ from ..compile.naming import (
68
+ pythonize_class_name,
69
+ pythonize_enum_member_name,
70
+ pythonize_field_name,
71
+ pythonize_method_name,
72
+ )
73
+ from .typing_compiler import (
74
+ DirectImportTypingCompiler,
75
+ TypingCompiler,
76
+ )
77
+
78
+ # Create a unique placeholder to deal with
79
+ # https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses
80
+ PLACEHOLDER = object()
81
+
82
+ # Organize proto types into categories
83
+ PROTO_FLOAT_TYPES = (
84
+ FieldDescriptorProtoType.TYPE_DOUBLE, # 1
85
+ FieldDescriptorProtoType.TYPE_FLOAT, # 2
86
+ )
87
+ PROTO_INT_TYPES = (
88
+ FieldDescriptorProtoType.TYPE_INT64, # 3
89
+ FieldDescriptorProtoType.TYPE_UINT64, # 4
90
+ FieldDescriptorProtoType.TYPE_INT32, # 5
91
+ FieldDescriptorProtoType.TYPE_FIXED64, # 6
92
+ FieldDescriptorProtoType.TYPE_FIXED32, # 7
93
+ FieldDescriptorProtoType.TYPE_UINT32, # 13
94
+ FieldDescriptorProtoType.TYPE_SFIXED32, # 15
95
+ FieldDescriptorProtoType.TYPE_SFIXED64, # 16
96
+ FieldDescriptorProtoType.TYPE_SINT32, # 17
97
+ FieldDescriptorProtoType.TYPE_SINT64, # 18
98
+ )
99
+ PROTO_BOOL_TYPES = (FieldDescriptorProtoType.TYPE_BOOL,) # 8
100
+ PROTO_STR_TYPES = (FieldDescriptorProtoType.TYPE_STRING,) # 9
101
+ PROTO_BYTES_TYPES = (FieldDescriptorProtoType.TYPE_BYTES,) # 12
102
+ PROTO_MESSAGE_TYPES = (
103
+ FieldDescriptorProtoType.TYPE_MESSAGE, # 11
104
+ FieldDescriptorProtoType.TYPE_ENUM, # 14
105
+ )
106
+ PROTO_MAP_TYPES = (FieldDescriptorProtoType.TYPE_MESSAGE,) # 11
107
+ PROTO_PACKED_TYPES = (
108
+ FieldDescriptorProtoType.TYPE_DOUBLE, # 1
109
+ FieldDescriptorProtoType.TYPE_FLOAT, # 2
110
+ FieldDescriptorProtoType.TYPE_INT64, # 3
111
+ FieldDescriptorProtoType.TYPE_UINT64, # 4
112
+ FieldDescriptorProtoType.TYPE_INT32, # 5
113
+ FieldDescriptorProtoType.TYPE_FIXED64, # 6
114
+ FieldDescriptorProtoType.TYPE_FIXED32, # 7
115
+ FieldDescriptorProtoType.TYPE_BOOL, # 8
116
+ FieldDescriptorProtoType.TYPE_UINT32, # 13
117
+ FieldDescriptorProtoType.TYPE_SFIXED32, # 15
118
+ FieldDescriptorProtoType.TYPE_SFIXED64, # 16
119
+ FieldDescriptorProtoType.TYPE_SINT32, # 17
120
+ FieldDescriptorProtoType.TYPE_SINT64, # 18
121
+ )
122
+
123
+
124
+ # TODO patch again to make field optional
125
+ # def monkey_patch_oneof_index():
126
+ # """
127
+ # The compiler message types are written for proto2, but we read them as proto3.
128
+ # For this to work in the case of the oneof_index fields, which depend on being able
129
+ # to tell whether they were set, we have to treat them as oneof fields. This method
130
+ # monkey patches the generated classes after the fact to force this behaviour.
131
+ # """
132
+ # object.__setattr__(
133
+ # FieldDescriptorProto.__dataclass_fields__["oneof_index"].metadata[
134
+ # "betterproto"
135
+ # ],
136
+ # "group",
137
+ # "oneof_index",
138
+ # )
139
+ # object.__setattr__(
140
+ # Field.__dataclass_fields__["oneof_index"].metadata["betterproto"],
141
+ # "group",
142
+ # "oneof_index",
143
+ # )
144
+
145
+
146
+ def get_comment(
147
+ proto_file: "FileDescriptorProto",
148
+ path: List[int],
149
+ ) -> str:
150
+ for sci_loc in proto_file.source_code_info.location:
151
+ if list(sci_loc.path) == path:
152
+ all_comments = list(sci_loc.leading_detached_comments)
153
+ if sci_loc.leading_comments:
154
+ all_comments.append(sci_loc.leading_comments)
155
+ if sci_loc.trailing_comments:
156
+ all_comments.append(sci_loc.trailing_comments)
157
+
158
+ lines = []
159
+
160
+ for comment in all_comments:
161
+ lines += comment.split("\n")
162
+ lines.append("")
163
+
164
+ # Remove consecutive empty lines
165
+ lines = [line for i, line in enumerate(lines) if line or (i == 0 or lines[i - 1])]
166
+
167
+ if lines and not lines[-1]:
168
+ lines.pop() # Remove the last empty line
169
+
170
+ # It is common for one line comments to start with a space, for example: // comment
171
+ # We don't add this space to the generated file.
172
+ lines = [line[1:] if line and line[0] == " " else line for line in lines]
173
+
174
+ return "\n".join(lines)
175
+
176
+ return ""
177
+
178
+
179
+ class ProtoContentBase:
180
+ """Methods common to MessageCompiler, ServiceCompiler and ServiceMethodCompiler."""
181
+
182
+ source_file: FileDescriptorProto
183
+ typing_compiler: TypingCompiler
184
+ path: List[int]
185
+ parent: Union["betterproto2_compiler.Message", "OutputTemplate"]
186
+
187
+ __dataclass_fields__: Dict[str, object]
188
+
189
+ def __post_init__(self) -> None:
190
+ """Checks that no fake default fields were left as placeholders."""
191
+ for field_name, field_val in self.__dataclass_fields__.items():
192
+ if field_val is PLACEHOLDER:
193
+ raise ValueError(f"`{field_name}` is a required field.")
194
+
195
+ def ready(self) -> None:
196
+ """
197
+ This function is called after all the compilers are created, but before generating the output code.
198
+ """
199
+ pass
200
+
201
+ @property
202
+ def output_file(self) -> "OutputTemplate":
203
+ current = self
204
+ while not isinstance(current, OutputTemplate):
205
+ current = current.parent
206
+ return current
207
+
208
+ @property
209
+ def request(self) -> "PluginRequestCompiler":
210
+ return self.output_file.parent_request
211
+
212
+ @property
213
+ def comment(self) -> str:
214
+ """Crawl the proto source code and retrieve comments
215
+ for this object.
216
+ """
217
+ return get_comment(proto_file=self.source_file, path=self.path)
218
+
219
+ @property
220
+ def deprecated(self) -> bool:
221
+ return self.proto_obj.options and self.proto_obj.options.deprecated
222
+
223
+
224
+ @dataclass
225
+ class PluginRequestCompiler:
226
+ plugin_request_obj: CodeGeneratorRequest
227
+ output_packages: Dict[str, "OutputTemplate"] = field(default_factory=dict)
228
+
229
+ @property
230
+ def all_messages(self) -> List["MessageCompiler"]:
231
+ """All of the messages in this request.
232
+
233
+ Returns
234
+ -------
235
+ List[MessageCompiler]
236
+ List of all of the messages in this request.
237
+ """
238
+ return [msg for output in self.output_packages.values() for msg in output.messages.values()]
239
+
240
+
241
+ @dataclass
242
+ class OutputTemplate:
243
+ """Representation of an output .py file.
244
+
245
+ Each output file corresponds to a .proto input file,
246
+ but may need references to other .proto files to be
247
+ built.
248
+ """
249
+
250
+ parent_request: PluginRequestCompiler
251
+ package_proto_obj: FileDescriptorProto
252
+ input_files: List[str] = field(default_factory=list)
253
+ imports_end: Set[str] = field(default_factory=set)
254
+ messages: Dict[str, "MessageCompiler"] = field(default_factory=dict)
255
+ enums: Dict[str, "EnumDefinitionCompiler"] = field(default_factory=dict)
256
+ services: Dict[str, "ServiceCompiler"] = field(default_factory=dict)
257
+ pydantic_dataclasses: bool = False
258
+ output: bool = True
259
+ typing_compiler: TypingCompiler = field(default_factory=DirectImportTypingCompiler)
260
+
261
+ @property
262
+ def package(self) -> str:
263
+ """Name of input package.
264
+
265
+ Returns
266
+ -------
267
+ str
268
+ Name of input package.
269
+ """
270
+ return self.package_proto_obj.package
271
+
272
+ @property
273
+ def input_filenames(self) -> Iterable[str]:
274
+ """Names of the input files used to build this output.
275
+
276
+ Returns
277
+ -------
278
+ Iterable[str]
279
+ Names of the input files used to build this output.
280
+ """
281
+ return sorted(f.name for f in self.input_files)
282
+
283
+
284
+ @dataclass
285
+ class MessageCompiler(ProtoContentBase):
286
+ """Representation of a protobuf message."""
287
+
288
+ source_file: FileDescriptorProto
289
+ typing_compiler: TypingCompiler
290
+ parent: Union["MessageCompiler", OutputTemplate] = PLACEHOLDER
291
+ proto_obj: DescriptorProto = PLACEHOLDER
292
+ path: List[int] = PLACEHOLDER
293
+ fields: List[Union["FieldCompiler", "MessageCompiler"]] = field(default_factory=list)
294
+ builtins_types: Set[str] = field(default_factory=set)
295
+
296
+ def __post_init__(self) -> None:
297
+ # Add message to output file
298
+ if isinstance(self.parent, OutputTemplate):
299
+ if isinstance(self, EnumDefinitionCompiler):
300
+ self.output_file.enums[self.proto_name] = self
301
+ else:
302
+ self.output_file.messages[self.proto_name] = self
303
+ super().__post_init__()
304
+
305
+ @property
306
+ def proto_name(self) -> str:
307
+ return self.proto_obj.name
308
+
309
+ @property
310
+ def py_name(self) -> str:
311
+ return pythonize_class_name(self.proto_name)
312
+
313
+ @property
314
+ def deprecated_fields(self) -> Iterator[str]:
315
+ for f in self.fields:
316
+ if f.deprecated:
317
+ yield f.py_name
318
+
319
+ @property
320
+ def has_deprecated_fields(self) -> bool:
321
+ return any(self.deprecated_fields)
322
+
323
+ @property
324
+ def has_oneof_fields(self) -> bool:
325
+ return any(isinstance(field, OneOfFieldCompiler) for field in self.fields)
326
+
327
+ @property
328
+ def has_message_field(self) -> bool:
329
+ return any(
330
+ (
331
+ field.proto_obj.type in PROTO_MESSAGE_TYPES
332
+ for field in self.fields
333
+ if isinstance(field.proto_obj, FieldDescriptorProto)
334
+ )
335
+ )
336
+
337
+
338
+ def is_map(proto_field_obj: FieldDescriptorProto, parent_message: DescriptorProto) -> bool:
339
+ """True if proto_field_obj is a map, otherwise False."""
340
+ if proto_field_obj.type == FieldDescriptorProtoType.TYPE_MESSAGE:
341
+ if not hasattr(parent_message, "nested_type"):
342
+ return False
343
+
344
+ # This might be a map...
345
+ message_type = proto_field_obj.type_name.split(".").pop().lower()
346
+ map_entry = f"{proto_field_obj.name.replace('_', '').lower()}entry"
347
+ if message_type == map_entry:
348
+ for nested in parent_message.nested_type: # parent message
349
+ if nested.name.replace("_", "").lower() == map_entry and nested.options.map_entry:
350
+ return True
351
+ return False
352
+
353
+
354
+ def is_oneof(proto_field_obj: FieldDescriptorProto) -> bool:
355
+ """
356
+ True if proto_field_obj is a OneOf, otherwise False.
357
+
358
+ .. warning::
359
+ TODO update comment
360
+ Becuase the message from protoc is defined in proto2, and betterproto works with
361
+ proto3, and interpreting the FieldDescriptorProto.oneof_index field requires
362
+ distinguishing between default and unset values (which proto3 doesn't support),
363
+ we have to hack the generated FieldDescriptorProto class for this to work.
364
+ The hack consists of setting group="oneof_index" in the field metadata,
365
+ essentially making oneof_index the sole member of a one_of group, which allows
366
+ us to tell whether it was set, via the which_one_of interface.
367
+ """
368
+
369
+ return not proto_field_obj.proto3_optional and proto_field_obj.oneof_index is not None
370
+
371
+
372
+ @dataclass
373
+ class FieldCompiler(ProtoContentBase):
374
+ source_file: FileDescriptorProto
375
+ typing_compiler: TypingCompiler
376
+ path: List[int] = PLACEHOLDER
377
+ builtins_types: Set[str] = field(default_factory=set)
378
+
379
+ parent: MessageCompiler = PLACEHOLDER
380
+ proto_obj: FieldDescriptorProto = PLACEHOLDER
381
+
382
+ def __post_init__(self) -> None:
383
+ # Add field to message
384
+ if isinstance(self.parent, MessageCompiler):
385
+ self.parent.fields.append(self)
386
+ super().__post_init__()
387
+
388
+ def get_field_string(self) -> str:
389
+ """Construct string representation of this field as a field."""
390
+ name = f"{self.py_name}"
391
+ field_args = ", ".join(([""] + self.betterproto_field_args) if self.betterproto_field_args else [])
392
+ betterproto_field_type = f"betterproto.{self.field_type}_field({self.proto_obj.number}{field_args})"
393
+ if self.py_name in dir(builtins):
394
+ self.parent.builtins_types.add(self.py_name)
395
+ return f'{name}: "{self.annotation}" = {betterproto_field_type}'
396
+
397
+ @property
398
+ def betterproto_field_args(self) -> List[str]:
399
+ args = []
400
+ if self.field_wraps:
401
+ args.append(f"wraps={self.field_wraps}")
402
+ if self.optional:
403
+ args.append("optional=True")
404
+ if self.repeated:
405
+ args.append("repeated=True")
406
+ if self.field_type == "enum":
407
+ t = self.py_type
408
+ args.append(f"enum_default_value=lambda: {t}.try_value(0)")
409
+ return args
410
+
411
+ @property
412
+ def use_builtins(self) -> bool:
413
+ return self.py_type in self.parent.builtins_types or (
414
+ self.py_type == self.py_name and self.py_name in dir(builtins)
415
+ )
416
+
417
+ @property
418
+ def field_wraps(self) -> Optional[str]:
419
+ """Returns betterproto wrapped field type or None."""
420
+ match_wrapper = re.match(r"\.google\.protobuf\.(.+)Value$", self.proto_obj.type_name)
421
+ if match_wrapper:
422
+ wrapped_type = "TYPE_" + match_wrapper.group(1).upper()
423
+ if hasattr(betterproto2_compiler, wrapped_type):
424
+ return f"betterproto.{wrapped_type}"
425
+ return None
426
+
427
+ @property
428
+ def repeated(self) -> bool:
429
+ return self.proto_obj.label == FieldDescriptorProtoLabel.LABEL_REPEATED and not is_map(
430
+ self.proto_obj, self.parent
431
+ )
432
+
433
+ @property
434
+ def optional(self) -> bool:
435
+ return self.proto_obj.proto3_optional or (self.field_type == "message" and not self.repeated)
436
+
437
+ @property
438
+ def field_type(self) -> str:
439
+ """String representation of proto field type."""
440
+ return FieldDescriptorProtoType(self.proto_obj.type).name.lower().replace("type_", "")
441
+
442
+ @property
443
+ def packed(self) -> bool:
444
+ """True if the wire representation is a packed format."""
445
+ return self.repeated and self.proto_obj.type in PROTO_PACKED_TYPES
446
+
447
+ @property
448
+ def py_name(self) -> str:
449
+ """Pythonized name."""
450
+ return pythonize_field_name(self.proto_name)
451
+
452
+ @property
453
+ def proto_name(self) -> str:
454
+ """Original protobuf name."""
455
+ return self.proto_obj.name
456
+
457
+ @property
458
+ def py_type(self) -> str:
459
+ """String representation of Python type."""
460
+ if self.proto_obj.type in PROTO_FLOAT_TYPES:
461
+ return "float"
462
+ elif self.proto_obj.type in PROTO_INT_TYPES:
463
+ return "int"
464
+ elif self.proto_obj.type in PROTO_BOOL_TYPES:
465
+ return "bool"
466
+ elif self.proto_obj.type in PROTO_STR_TYPES:
467
+ return "str"
468
+ elif self.proto_obj.type in PROTO_BYTES_TYPES:
469
+ return "bytes"
470
+ elif self.proto_obj.type in PROTO_MESSAGE_TYPES:
471
+ # Type referencing another defined Message or a named enum
472
+ return get_type_reference(
473
+ package=self.output_file.package,
474
+ imports=self.output_file.imports_end,
475
+ source_type=self.proto_obj.type_name,
476
+ typing_compiler=self.typing_compiler,
477
+ request=self.request,
478
+ pydantic=self.output_file.pydantic_dataclasses,
479
+ )
480
+ else:
481
+ raise NotImplementedError(f"Unknown type {self.proto_obj.type}")
482
+
483
+ @property
484
+ def annotation(self) -> str:
485
+ py_type = self.py_type
486
+ if self.use_builtins:
487
+ py_type = f"builtins.{py_type}"
488
+ if self.repeated:
489
+ return self.typing_compiler.list(py_type)
490
+ if self.optional:
491
+ return self.typing_compiler.optional(py_type)
492
+ return py_type
493
+
494
+
495
+ @dataclass
496
+ class OneOfFieldCompiler(FieldCompiler):
497
+ @property
498
+ def optional(self) -> bool:
499
+ return True
500
+
501
+ @property
502
+ def betterproto_field_args(self) -> List[str]:
503
+ args = super().betterproto_field_args
504
+ group = self.parent.proto_obj.oneof_decl[self.proto_obj.oneof_index].name
505
+ args.append(f'group="{group}"')
506
+ return args
507
+
508
+
509
+ @dataclass
510
+ class MapEntryCompiler(FieldCompiler):
511
+ py_k_type: Optional[Type] = None
512
+ py_v_type: Optional[Type] = None
513
+ proto_k_type: str = ""
514
+ proto_v_type: str = ""
515
+
516
+ def __post_init__(self):
517
+ map_entry = f"{self.proto_obj.name.replace('_', '').lower()}entry"
518
+ for nested in self.parent.proto_obj.nested_type:
519
+ if nested.name.replace("_", "").lower() == map_entry and nested.options.map_entry:
520
+ pass
521
+ return super().__post_init__()
522
+
523
+ def ready(self) -> None:
524
+ """Explore nested types and set k_type and v_type if unset."""
525
+ map_entry = f"{self.proto_obj.name.replace('_', '').lower()}entry"
526
+ for nested in self.parent.proto_obj.nested_type:
527
+ if nested.name.replace("_", "").lower() == map_entry and nested.options.map_entry:
528
+ # Get Python types
529
+ self.py_k_type = FieldCompiler(
530
+ source_file=self.source_file,
531
+ parent=self,
532
+ proto_obj=nested.field[0], # key
533
+ typing_compiler=self.typing_compiler,
534
+ ).py_type
535
+ self.py_v_type = FieldCompiler(
536
+ source_file=self.source_file,
537
+ parent=self,
538
+ proto_obj=nested.field[1], # value
539
+ typing_compiler=self.typing_compiler,
540
+ ).py_type
541
+
542
+ # Get proto types
543
+ self.proto_k_type = FieldDescriptorProtoType(nested.field[0].type).name
544
+ self.proto_v_type = FieldDescriptorProtoType(nested.field[1].type).name
545
+ return
546
+
547
+ raise ValueError("can't find enum")
548
+
549
+ @property
550
+ def betterproto_field_args(self) -> List[str]:
551
+ return [f"betterproto.{self.proto_k_type}", f"betterproto.{self.proto_v_type}"]
552
+
553
+ @property
554
+ def field_type(self) -> str:
555
+ return "map"
556
+
557
+ @property
558
+ def annotation(self) -> str:
559
+ return self.typing_compiler.dict(self.py_k_type, self.py_v_type)
560
+
561
+ @property
562
+ def repeated(self) -> bool:
563
+ return False # maps cannot be repeated
564
+
565
+
566
+ @dataclass
567
+ class EnumDefinitionCompiler(MessageCompiler):
568
+ """Representation of a proto Enum definition."""
569
+
570
+ proto_obj: EnumDescriptorProto = PLACEHOLDER
571
+ entries: List["EnumDefinitionCompiler.EnumEntry"] = PLACEHOLDER
572
+
573
+ @dataclass(unsafe_hash=True)
574
+ class EnumEntry:
575
+ """Representation of an Enum entry."""
576
+
577
+ name: str
578
+ value: int
579
+ comment: str
580
+
581
+ def __post_init__(self) -> None:
582
+ # Get entries/allowed values for this Enum
583
+ self.entries = [
584
+ self.EnumEntry(
585
+ name=pythonize_enum_member_name(entry_proto_value.name, self.proto_obj.name),
586
+ value=entry_proto_value.number,
587
+ comment=get_comment(proto_file=self.source_file, path=self.path + [2, entry_number]),
588
+ )
589
+ for entry_number, entry_proto_value in enumerate(self.proto_obj.value)
590
+ ]
591
+ super().__post_init__() # call MessageCompiler __post_init__
592
+
593
+
594
+ @dataclass
595
+ class ServiceCompiler(ProtoContentBase):
596
+ source_file: FileDescriptorProto
597
+ parent: OutputTemplate = PLACEHOLDER
598
+ proto_obj: DescriptorProto = PLACEHOLDER
599
+ path: List[int] = PLACEHOLDER
600
+ methods: List["ServiceMethodCompiler"] = field(default_factory=list)
601
+
602
+ def __post_init__(self) -> None:
603
+ # Add service to output file
604
+ self.output_file.services[self.proto_name] = self
605
+ super().__post_init__() # check for unset fields
606
+
607
+ @property
608
+ def proto_name(self) -> str:
609
+ return self.proto_obj.name
610
+
611
+ @property
612
+ def py_name(self) -> str:
613
+ return pythonize_class_name(self.proto_name)
614
+
615
+
616
+ @dataclass
617
+ class ServiceMethodCompiler(ProtoContentBase):
618
+ source_file: FileDescriptorProto
619
+ parent: ServiceCompiler
620
+ proto_obj: MethodDescriptorProto
621
+ path: List[int] = PLACEHOLDER
622
+
623
+ def __post_init__(self) -> None:
624
+ # Add method to service
625
+ self.parent.methods.append(self)
626
+
627
+ super().__post_init__() # check for unset fields
628
+
629
+ @property
630
+ def py_name(self) -> str:
631
+ """Pythonized method name."""
632
+ return pythonize_method_name(self.proto_obj.name)
633
+
634
+ @property
635
+ def proto_name(self) -> str:
636
+ """Original protobuf name."""
637
+ return self.proto_obj.name
638
+
639
+ @property
640
+ def route(self) -> str:
641
+ package_part = f"{self.output_file.package}." if self.output_file.package else ""
642
+ return f"/{package_part}{self.parent.proto_name}/{self.proto_name}"
643
+
644
+ @property
645
+ def py_input_message_type(self) -> str:
646
+ """String representation of the Python type corresponding to the
647
+ input message.
648
+
649
+ Returns
650
+ -------
651
+ str
652
+ String representation of the Python type corresponding to the input message.
653
+ """
654
+ return get_type_reference(
655
+ package=self.output_file.package,
656
+ imports=self.output_file.imports_end,
657
+ source_type=self.proto_obj.input_type,
658
+ typing_compiler=self.output_file.typing_compiler,
659
+ request=self.request,
660
+ unwrap=False,
661
+ pydantic=self.output_file.pydantic_dataclasses,
662
+ )
663
+
664
+ @property
665
+ def is_input_msg_empty(self: "ServiceMethodCompiler") -> bool:
666
+ package, name = parse_source_type_name(self.proto_obj.input_type, self.request)
667
+
668
+ msg = self.request.output_packages[package].messages[name]
669
+
670
+ return not bool(msg.fields)
671
+
672
+ @property
673
+ def py_input_message_param(self) -> str:
674
+ """Param name corresponding to py_input_message_type.
675
+
676
+ Returns
677
+ -------
678
+ str
679
+ Param name corresponding to py_input_message_type.
680
+ """
681
+ return pythonize_field_name(self.py_input_message_type)
682
+
683
+ @property
684
+ def py_output_message_type(self) -> str:
685
+ """String representation of the Python type corresponding to the
686
+ output message.
687
+
688
+ Returns
689
+ -------
690
+ str
691
+ String representation of the Python type corresponding to the output message.
692
+ """
693
+ return get_type_reference(
694
+ package=self.output_file.package,
695
+ imports=self.output_file.imports_end,
696
+ source_type=self.proto_obj.output_type,
697
+ typing_compiler=self.output_file.typing_compiler,
698
+ request=self.request,
699
+ unwrap=False,
700
+ pydantic=self.output_file.pydantic_dataclasses,
701
+ )
702
+
703
+ @property
704
+ def client_streaming(self) -> bool:
705
+ return self.proto_obj.client_streaming
706
+
707
+ @property
708
+ def server_streaming(self) -> bool:
709
+ return self.proto_obj.server_streaming