betterproto2-compiler 0.2.0__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.
- betterproto2_compiler/__init__.py +0 -0
- betterproto2_compiler/casing.py +140 -0
- betterproto2_compiler/compile/__init__.py +0 -0
- betterproto2_compiler/compile/importing.py +180 -0
- betterproto2_compiler/compile/naming.py +21 -0
- betterproto2_compiler/known_types/__init__.py +14 -0
- betterproto2_compiler/known_types/any.py +36 -0
- betterproto2_compiler/known_types/duration.py +25 -0
- betterproto2_compiler/known_types/timestamp.py +45 -0
- betterproto2_compiler/lib/__init__.py +0 -0
- betterproto2_compiler/lib/google/__init__.py +0 -0
- betterproto2_compiler/lib/google/protobuf/__init__.py +3338 -0
- betterproto2_compiler/lib/google/protobuf/compiler/__init__.py +235 -0
- betterproto2_compiler/lib/message_pool.py +3 -0
- betterproto2_compiler/plugin/__init__.py +3 -0
- betterproto2_compiler/plugin/__main__.py +3 -0
- betterproto2_compiler/plugin/compiler.py +70 -0
- betterproto2_compiler/plugin/main.py +47 -0
- betterproto2_compiler/plugin/models.py +643 -0
- betterproto2_compiler/plugin/module_validation.py +156 -0
- betterproto2_compiler/plugin/parser.py +272 -0
- betterproto2_compiler/plugin/plugin.bat +2 -0
- betterproto2_compiler/plugin/typing_compiler.py +163 -0
- betterproto2_compiler/py.typed +0 -0
- betterproto2_compiler/settings.py +9 -0
- betterproto2_compiler/templates/header.py.j2 +59 -0
- betterproto2_compiler/templates/template.py.j2 +258 -0
- betterproto2_compiler-0.2.0.dist-info/LICENSE.md +22 -0
- betterproto2_compiler-0.2.0.dist-info/METADATA +35 -0
- betterproto2_compiler-0.2.0.dist-info/RECORD +32 -0
- betterproto2_compiler-0.2.0.dist-info/WHEEL +4 -0
- betterproto2_compiler-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,643 @@
|
|
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
|
+
|
27
|
+
import builtins
|
28
|
+
import inspect
|
29
|
+
import re
|
30
|
+
from collections.abc import Iterator
|
31
|
+
from dataclasses import (
|
32
|
+
dataclass,
|
33
|
+
field,
|
34
|
+
)
|
35
|
+
|
36
|
+
import betterproto2
|
37
|
+
from betterproto2 import unwrap
|
38
|
+
|
39
|
+
from betterproto2_compiler.compile.importing import get_type_reference, parse_source_type_name
|
40
|
+
from betterproto2_compiler.compile.naming import (
|
41
|
+
pythonize_class_name,
|
42
|
+
pythonize_enum_member_name,
|
43
|
+
pythonize_field_name,
|
44
|
+
pythonize_method_name,
|
45
|
+
)
|
46
|
+
from betterproto2_compiler.known_types import KNOWN_METHODS
|
47
|
+
from betterproto2_compiler.lib.google.protobuf import (
|
48
|
+
DescriptorProto,
|
49
|
+
EnumDescriptorProto,
|
50
|
+
FieldDescriptorProto,
|
51
|
+
FieldDescriptorProtoLabel,
|
52
|
+
FieldDescriptorProtoType,
|
53
|
+
FieldDescriptorProtoType as FieldType,
|
54
|
+
FileDescriptorProto,
|
55
|
+
MethodDescriptorProto,
|
56
|
+
OneofDescriptorProto,
|
57
|
+
ServiceDescriptorProto,
|
58
|
+
)
|
59
|
+
from betterproto2_compiler.lib.google.protobuf.compiler import CodeGeneratorRequest
|
60
|
+
from betterproto2_compiler.plugin.typing_compiler import TypingCompiler
|
61
|
+
from betterproto2_compiler.settings import Settings
|
62
|
+
|
63
|
+
# Organize proto types into categories
|
64
|
+
PROTO_FLOAT_TYPES = (
|
65
|
+
FieldDescriptorProtoType.TYPE_DOUBLE, # 1
|
66
|
+
FieldDescriptorProtoType.TYPE_FLOAT, # 2
|
67
|
+
)
|
68
|
+
PROTO_INT_TYPES = (
|
69
|
+
FieldDescriptorProtoType.TYPE_INT64, # 3
|
70
|
+
FieldDescriptorProtoType.TYPE_UINT64, # 4
|
71
|
+
FieldDescriptorProtoType.TYPE_INT32, # 5
|
72
|
+
FieldDescriptorProtoType.TYPE_FIXED64, # 6
|
73
|
+
FieldDescriptorProtoType.TYPE_FIXED32, # 7
|
74
|
+
FieldDescriptorProtoType.TYPE_UINT32, # 13
|
75
|
+
FieldDescriptorProtoType.TYPE_SFIXED32, # 15
|
76
|
+
FieldDescriptorProtoType.TYPE_SFIXED64, # 16
|
77
|
+
FieldDescriptorProtoType.TYPE_SINT32, # 17
|
78
|
+
FieldDescriptorProtoType.TYPE_SINT64, # 18
|
79
|
+
)
|
80
|
+
PROTO_BOOL_TYPES = (FieldDescriptorProtoType.TYPE_BOOL,) # 8
|
81
|
+
PROTO_STR_TYPES = (FieldDescriptorProtoType.TYPE_STRING,) # 9
|
82
|
+
PROTO_BYTES_TYPES = (FieldDescriptorProtoType.TYPE_BYTES,) # 12
|
83
|
+
PROTO_MESSAGE_TYPES = (
|
84
|
+
FieldDescriptorProtoType.TYPE_MESSAGE, # 11
|
85
|
+
FieldDescriptorProtoType.TYPE_ENUM, # 14
|
86
|
+
)
|
87
|
+
PROTO_MAP_TYPES = (FieldDescriptorProtoType.TYPE_MESSAGE,) # 11
|
88
|
+
PROTO_PACKED_TYPES = (
|
89
|
+
FieldDescriptorProtoType.TYPE_DOUBLE, # 1
|
90
|
+
FieldDescriptorProtoType.TYPE_FLOAT, # 2
|
91
|
+
FieldDescriptorProtoType.TYPE_INT64, # 3
|
92
|
+
FieldDescriptorProtoType.TYPE_UINT64, # 4
|
93
|
+
FieldDescriptorProtoType.TYPE_INT32, # 5
|
94
|
+
FieldDescriptorProtoType.TYPE_FIXED64, # 6
|
95
|
+
FieldDescriptorProtoType.TYPE_FIXED32, # 7
|
96
|
+
FieldDescriptorProtoType.TYPE_BOOL, # 8
|
97
|
+
FieldDescriptorProtoType.TYPE_UINT32, # 13
|
98
|
+
FieldDescriptorProtoType.TYPE_SFIXED32, # 15
|
99
|
+
FieldDescriptorProtoType.TYPE_SFIXED64, # 16
|
100
|
+
FieldDescriptorProtoType.TYPE_SINT32, # 17
|
101
|
+
FieldDescriptorProtoType.TYPE_SINT64, # 18
|
102
|
+
)
|
103
|
+
|
104
|
+
|
105
|
+
def get_comment(
|
106
|
+
proto_file: "FileDescriptorProto",
|
107
|
+
path: list[int],
|
108
|
+
) -> str:
|
109
|
+
for sci_loc in unwrap(proto_file.source_code_info).location:
|
110
|
+
if list(sci_loc.path) == path:
|
111
|
+
all_comments = list(sci_loc.leading_detached_comments)
|
112
|
+
if sci_loc.leading_comments:
|
113
|
+
all_comments.append(sci_loc.leading_comments)
|
114
|
+
if sci_loc.trailing_comments:
|
115
|
+
all_comments.append(sci_loc.trailing_comments)
|
116
|
+
|
117
|
+
lines = []
|
118
|
+
|
119
|
+
for comment in all_comments:
|
120
|
+
lines += comment.split("\n")
|
121
|
+
lines.append("")
|
122
|
+
|
123
|
+
# Remove consecutive empty lines
|
124
|
+
lines = [line for i, line in enumerate(lines) if line or (i == 0 or lines[i - 1])]
|
125
|
+
|
126
|
+
if lines and not lines[-1]:
|
127
|
+
lines.pop() # Remove the last empty line
|
128
|
+
|
129
|
+
# It is common for one line comments to start with a space, for example: // comment
|
130
|
+
# We don't add this space to the generated file.
|
131
|
+
lines = [line[1:] if line and line[0] == " " else line for line in lines]
|
132
|
+
|
133
|
+
return "\n".join(lines)
|
134
|
+
|
135
|
+
return ""
|
136
|
+
|
137
|
+
|
138
|
+
@dataclass(kw_only=True)
|
139
|
+
class ProtoContentBase:
|
140
|
+
"""Methods common to MessageCompiler, ServiceCompiler and ServiceMethodCompiler."""
|
141
|
+
|
142
|
+
source_file: FileDescriptorProto
|
143
|
+
path: list[int]
|
144
|
+
|
145
|
+
def ready(self) -> None:
|
146
|
+
"""
|
147
|
+
This function is called after all the compilers are created, but before generating the output code.
|
148
|
+
"""
|
149
|
+
pass
|
150
|
+
|
151
|
+
@property
|
152
|
+
def comment(self) -> str:
|
153
|
+
"""Crawl the proto source code and retrieve comments
|
154
|
+
for this object.
|
155
|
+
"""
|
156
|
+
return get_comment(proto_file=self.source_file, path=self.path)
|
157
|
+
|
158
|
+
|
159
|
+
@dataclass(kw_only=True)
|
160
|
+
class PluginRequestCompiler:
|
161
|
+
plugin_request_obj: CodeGeneratorRequest
|
162
|
+
output_packages: dict[str, "OutputTemplate"] = field(default_factory=dict)
|
163
|
+
|
164
|
+
@property
|
165
|
+
def all_messages(self) -> list["MessageCompiler"]:
|
166
|
+
"""All of the messages in this request.
|
167
|
+
|
168
|
+
Returns
|
169
|
+
-------
|
170
|
+
List[MessageCompiler]
|
171
|
+
List of all of the messages in this request.
|
172
|
+
"""
|
173
|
+
return [msg for output in self.output_packages.values() for msg in output.messages.values()]
|
174
|
+
|
175
|
+
|
176
|
+
@dataclass(kw_only=True)
|
177
|
+
class OutputTemplate:
|
178
|
+
"""Representation of an output .py file.
|
179
|
+
|
180
|
+
Each output file corresponds to a .proto input file,
|
181
|
+
but may need references to other .proto files to be
|
182
|
+
built.
|
183
|
+
"""
|
184
|
+
|
185
|
+
parent_request: PluginRequestCompiler
|
186
|
+
package_proto_obj: FileDescriptorProto
|
187
|
+
input_files: list[FileDescriptorProto] = field(default_factory=list)
|
188
|
+
imports_end: set[str] = field(default_factory=set)
|
189
|
+
messages: dict[str, "MessageCompiler"] = field(default_factory=dict)
|
190
|
+
enums: dict[str, "EnumDefinitionCompiler"] = field(default_factory=dict)
|
191
|
+
services: dict[str, "ServiceCompiler"] = field(default_factory=dict)
|
192
|
+
|
193
|
+
settings: Settings
|
194
|
+
|
195
|
+
@property
|
196
|
+
def package(self) -> str:
|
197
|
+
"""Name of input package.
|
198
|
+
|
199
|
+
Returns
|
200
|
+
-------
|
201
|
+
str
|
202
|
+
Name of input package.
|
203
|
+
"""
|
204
|
+
return self.package_proto_obj.package
|
205
|
+
|
206
|
+
@property
|
207
|
+
def input_filenames(self) -> list[str]:
|
208
|
+
"""Names of the input files used to build this output.
|
209
|
+
|
210
|
+
Returns
|
211
|
+
-------
|
212
|
+
list[str]
|
213
|
+
Names of the input files used to build this output.
|
214
|
+
"""
|
215
|
+
return sorted([f.name for f in self.input_files])
|
216
|
+
|
217
|
+
|
218
|
+
@dataclass(kw_only=True)
|
219
|
+
class MessageCompiler(ProtoContentBase):
|
220
|
+
"""Representation of a protobuf message."""
|
221
|
+
|
222
|
+
output_file: OutputTemplate
|
223
|
+
proto_obj: DescriptorProto
|
224
|
+
fields: list["FieldCompiler"] = field(default_factory=list)
|
225
|
+
oneofs: list["OneofCompiler"] = field(default_factory=list)
|
226
|
+
builtins_types: set[str] = field(default_factory=set)
|
227
|
+
|
228
|
+
@property
|
229
|
+
def proto_name(self) -> str:
|
230
|
+
return self.proto_obj.name
|
231
|
+
|
232
|
+
@property
|
233
|
+
def py_name(self) -> str:
|
234
|
+
return pythonize_class_name(self.proto_name)
|
235
|
+
|
236
|
+
@property
|
237
|
+
def deprecated(self) -> bool:
|
238
|
+
return bool(self.proto_obj.options and self.proto_obj.options.deprecated)
|
239
|
+
|
240
|
+
@property
|
241
|
+
def deprecated_fields(self) -> Iterator[str]:
|
242
|
+
for f in self.fields:
|
243
|
+
if f.deprecated:
|
244
|
+
yield f.py_name
|
245
|
+
|
246
|
+
@property
|
247
|
+
def has_deprecated_fields(self) -> bool:
|
248
|
+
return any(self.deprecated_fields)
|
249
|
+
|
250
|
+
@property
|
251
|
+
def has_oneof_fields(self) -> bool:
|
252
|
+
return any(isinstance(field, OneOfFieldCompiler) for field in self.fields)
|
253
|
+
|
254
|
+
@property
|
255
|
+
def has_message_field(self) -> bool:
|
256
|
+
return any(
|
257
|
+
field.proto_obj.type in PROTO_MESSAGE_TYPES
|
258
|
+
for field in self.fields
|
259
|
+
if isinstance(field.proto_obj, FieldDescriptorProto)
|
260
|
+
)
|
261
|
+
|
262
|
+
@property
|
263
|
+
def custom_methods(self) -> list[str]:
|
264
|
+
"""
|
265
|
+
Return a list of the custom methods.
|
266
|
+
"""
|
267
|
+
methods_source: list[str] = []
|
268
|
+
|
269
|
+
for method in KNOWN_METHODS.get((self.source_file.package, self.py_name), []):
|
270
|
+
source = inspect.getsource(method)
|
271
|
+
methods_source.append(source.strip())
|
272
|
+
|
273
|
+
return methods_source
|
274
|
+
|
275
|
+
|
276
|
+
def is_map(proto_field_obj: FieldDescriptorProto, parent_message: DescriptorProto) -> bool:
|
277
|
+
"""True if proto_field_obj is a map, otherwise False."""
|
278
|
+
if proto_field_obj.type == FieldDescriptorProtoType.TYPE_MESSAGE:
|
279
|
+
if not hasattr(parent_message, "nested_type"):
|
280
|
+
return False
|
281
|
+
|
282
|
+
# This might be a map...
|
283
|
+
message_type = proto_field_obj.type_name.split(".").pop().lower()
|
284
|
+
map_entry = f"{proto_field_obj.name.replace('_', '').lower()}entry"
|
285
|
+
if message_type == map_entry:
|
286
|
+
for nested in parent_message.nested_type: # parent message
|
287
|
+
if nested.name.replace("_", "").lower() == map_entry and nested.options and nested.options.map_entry:
|
288
|
+
return True
|
289
|
+
return False
|
290
|
+
|
291
|
+
|
292
|
+
def is_oneof(proto_field_obj: FieldDescriptorProto) -> bool:
|
293
|
+
"""
|
294
|
+
True if proto_field_obj is a OneOf, otherwise False.
|
295
|
+
"""
|
296
|
+
return not proto_field_obj.proto3_optional and proto_field_obj.oneof_index is not None
|
297
|
+
|
298
|
+
|
299
|
+
@dataclass(kw_only=True)
|
300
|
+
class FieldCompiler(ProtoContentBase):
|
301
|
+
typing_compiler: TypingCompiler
|
302
|
+
builtins_types: set[str] = field(default_factory=set)
|
303
|
+
|
304
|
+
message: MessageCompiler
|
305
|
+
proto_obj: FieldDescriptorProto
|
306
|
+
|
307
|
+
@property
|
308
|
+
def output_file(self) -> "OutputTemplate":
|
309
|
+
return self.message.output_file
|
310
|
+
|
311
|
+
def get_field_string(self) -> str:
|
312
|
+
"""Construct string representation of this field as a field."""
|
313
|
+
name = f"{self.py_name}"
|
314
|
+
field_args = ", ".join(([""] + self.betterproto_field_args) if self.betterproto_field_args else [])
|
315
|
+
|
316
|
+
betterproto_field_type = (
|
317
|
+
f"betterproto2.field({self.proto_obj.number}, betterproto2.{str(self.field_type)}{field_args})"
|
318
|
+
)
|
319
|
+
if self.py_name in dir(builtins):
|
320
|
+
self.message.builtins_types.add(self.py_name)
|
321
|
+
return f'{name}: "{self.annotation}" = {betterproto_field_type}'
|
322
|
+
|
323
|
+
@property
|
324
|
+
def betterproto_field_args(self) -> list[str]:
|
325
|
+
args = []
|
326
|
+
if self.field_wraps:
|
327
|
+
args.append(f"wraps={self.field_wraps}")
|
328
|
+
if self.optional:
|
329
|
+
args.append("optional=True")
|
330
|
+
elif self.repeated:
|
331
|
+
args.append("repeated=True")
|
332
|
+
elif self.field_type == FieldType.TYPE_ENUM:
|
333
|
+
args.append(f"default_factory=lambda: {self.py_type}.try_value(0)")
|
334
|
+
return args
|
335
|
+
|
336
|
+
@property
|
337
|
+
def deprecated(self) -> bool:
|
338
|
+
return bool(self.proto_obj.options and self.proto_obj.options.deprecated)
|
339
|
+
|
340
|
+
@property
|
341
|
+
def use_builtins(self) -> bool:
|
342
|
+
return self.py_type in self.message.builtins_types or (
|
343
|
+
self.py_type == self.py_name and self.py_name in dir(builtins)
|
344
|
+
)
|
345
|
+
|
346
|
+
@property
|
347
|
+
def field_wraps(self) -> str | None:
|
348
|
+
"""Returns betterproto wrapped field type or None."""
|
349
|
+
match_wrapper = re.match(r"\.google\.protobuf\.(.+)Value$", self.proto_obj.type_name)
|
350
|
+
if match_wrapper:
|
351
|
+
wrapped_type = "TYPE_" + match_wrapper.group(1).upper()
|
352
|
+
if hasattr(betterproto2, wrapped_type):
|
353
|
+
return f"betterproto2.{wrapped_type}"
|
354
|
+
return None
|
355
|
+
|
356
|
+
@property
|
357
|
+
def repeated(self) -> bool:
|
358
|
+
return self.proto_obj.label == FieldDescriptorProtoLabel.LABEL_REPEATED
|
359
|
+
|
360
|
+
@property
|
361
|
+
def optional(self) -> bool:
|
362
|
+
# TODO not for maps
|
363
|
+
return self.proto_obj.proto3_optional or (self.field_type == FieldType.TYPE_MESSAGE and not self.repeated)
|
364
|
+
|
365
|
+
@property
|
366
|
+
def field_type(self) -> FieldType:
|
367
|
+
# TODO it should be possible to remove constructor
|
368
|
+
return FieldType(self.proto_obj.type)
|
369
|
+
|
370
|
+
@property
|
371
|
+
def packed(self) -> bool:
|
372
|
+
"""True if the wire representation is a packed format."""
|
373
|
+
return self.repeated and self.proto_obj.type in PROTO_PACKED_TYPES
|
374
|
+
|
375
|
+
@property
|
376
|
+
def py_name(self) -> str:
|
377
|
+
"""Pythonized name."""
|
378
|
+
return pythonize_field_name(self.proto_name)
|
379
|
+
|
380
|
+
@property
|
381
|
+
def proto_name(self) -> str:
|
382
|
+
"""Original protobuf name."""
|
383
|
+
return self.proto_obj.name
|
384
|
+
|
385
|
+
@property
|
386
|
+
def py_type(self) -> str:
|
387
|
+
"""String representation of Python type."""
|
388
|
+
if self.proto_obj.type in PROTO_FLOAT_TYPES:
|
389
|
+
return "float"
|
390
|
+
elif self.proto_obj.type in PROTO_INT_TYPES:
|
391
|
+
return "int"
|
392
|
+
elif self.proto_obj.type in PROTO_BOOL_TYPES:
|
393
|
+
return "bool"
|
394
|
+
elif self.proto_obj.type in PROTO_STR_TYPES:
|
395
|
+
return "str"
|
396
|
+
elif self.proto_obj.type in PROTO_BYTES_TYPES:
|
397
|
+
return "bytes"
|
398
|
+
elif self.proto_obj.type in PROTO_MESSAGE_TYPES:
|
399
|
+
# Type referencing another defined Message or a named enum
|
400
|
+
return get_type_reference(
|
401
|
+
package=self.output_file.package,
|
402
|
+
imports=self.output_file.imports_end,
|
403
|
+
source_type=self.proto_obj.type_name,
|
404
|
+
request=self.output_file.parent_request,
|
405
|
+
settings=self.output_file.settings,
|
406
|
+
)
|
407
|
+
else:
|
408
|
+
raise NotImplementedError(f"Unknown type {self.proto_obj.type}")
|
409
|
+
|
410
|
+
@property
|
411
|
+
def annotation(self) -> str:
|
412
|
+
py_type = self.py_type
|
413
|
+
if self.use_builtins:
|
414
|
+
py_type = f"builtins.{py_type}"
|
415
|
+
if self.repeated:
|
416
|
+
return self.typing_compiler.list(py_type)
|
417
|
+
if self.optional:
|
418
|
+
return self.typing_compiler.optional(py_type)
|
419
|
+
return py_type
|
420
|
+
|
421
|
+
|
422
|
+
@dataclass(kw_only=True)
|
423
|
+
class OneOfFieldCompiler(FieldCompiler):
|
424
|
+
@property
|
425
|
+
def optional(self) -> bool:
|
426
|
+
return True
|
427
|
+
|
428
|
+
@property
|
429
|
+
def betterproto_field_args(self) -> list[str]:
|
430
|
+
args = super().betterproto_field_args
|
431
|
+
group = self.message.proto_obj.oneof_decl[unwrap(self.proto_obj.oneof_index)].name
|
432
|
+
args.append(f'group="{group}"')
|
433
|
+
return args
|
434
|
+
|
435
|
+
|
436
|
+
@dataclass(kw_only=True)
|
437
|
+
class MapEntryCompiler(FieldCompiler):
|
438
|
+
py_k_type: str = ""
|
439
|
+
py_v_type: str = ""
|
440
|
+
proto_k_type: str = ""
|
441
|
+
proto_v_type: str = ""
|
442
|
+
|
443
|
+
def ready(self) -> None:
|
444
|
+
"""Explore nested types and set k_type and v_type if unset."""
|
445
|
+
map_entry = f"{self.proto_obj.name.replace('_', '').lower()}entry"
|
446
|
+
for nested in self.message.proto_obj.nested_type:
|
447
|
+
if nested.name.replace("_", "").lower() == map_entry and unwrap(nested.options).map_entry:
|
448
|
+
# Get Python types
|
449
|
+
self.py_k_type = FieldCompiler(
|
450
|
+
source_file=self.source_file,
|
451
|
+
proto_obj=nested.field[0], # key
|
452
|
+
typing_compiler=self.typing_compiler,
|
453
|
+
path=[],
|
454
|
+
message=self.message,
|
455
|
+
).py_type
|
456
|
+
self.py_v_type = FieldCompiler(
|
457
|
+
source_file=self.source_file,
|
458
|
+
proto_obj=nested.field[1], # value
|
459
|
+
typing_compiler=self.typing_compiler,
|
460
|
+
path=[],
|
461
|
+
message=self.message,
|
462
|
+
).py_type
|
463
|
+
|
464
|
+
# Get proto types
|
465
|
+
self.proto_k_type = unwrap(FieldDescriptorProtoType(nested.field[0].type).name)
|
466
|
+
self.proto_v_type = unwrap(FieldDescriptorProtoType(nested.field[1].type).name)
|
467
|
+
return
|
468
|
+
|
469
|
+
raise ValueError("can't find enum")
|
470
|
+
|
471
|
+
def get_field_string(self) -> str:
|
472
|
+
"""Construct string representation of this field as a field."""
|
473
|
+
betterproto_field_type = (
|
474
|
+
f"betterproto2.field({self.proto_obj.number}, "
|
475
|
+
"betterproto2.TYPE_MAP, "
|
476
|
+
f"map_types=(betterproto2.{self.proto_k_type}, "
|
477
|
+
f"betterproto2.{self.proto_v_type}))"
|
478
|
+
)
|
479
|
+
if self.py_name in dir(builtins):
|
480
|
+
self.message.builtins_types.add(self.py_name)
|
481
|
+
return f'{self.py_name}: "{self.annotation}" = {betterproto_field_type}'
|
482
|
+
|
483
|
+
@property
|
484
|
+
def annotation(self) -> str:
|
485
|
+
return self.typing_compiler.dict(self.py_k_type, self.py_v_type)
|
486
|
+
|
487
|
+
@property
|
488
|
+
def repeated(self) -> bool:
|
489
|
+
return False # maps cannot be repeated
|
490
|
+
|
491
|
+
|
492
|
+
@dataclass(kw_only=True)
|
493
|
+
class OneofCompiler(ProtoContentBase):
|
494
|
+
proto_obj: OneofDescriptorProto
|
495
|
+
|
496
|
+
@property
|
497
|
+
def name(self) -> str:
|
498
|
+
return self.proto_obj.name
|
499
|
+
|
500
|
+
|
501
|
+
@dataclass(kw_only=True)
|
502
|
+
class EnumDefinitionCompiler(ProtoContentBase):
|
503
|
+
"""Representation of a proto Enum definition."""
|
504
|
+
|
505
|
+
output_file: OutputTemplate
|
506
|
+
proto_obj: EnumDescriptorProto
|
507
|
+
entries: list["EnumDefinitionCompiler.EnumEntry"] = field(default_factory=list)
|
508
|
+
|
509
|
+
@dataclass(unsafe_hash=True, kw_only=True)
|
510
|
+
class EnumEntry:
|
511
|
+
"""Representation of an Enum entry."""
|
512
|
+
|
513
|
+
name: str
|
514
|
+
value: int
|
515
|
+
comment: str
|
516
|
+
|
517
|
+
def __post_init__(self) -> None:
|
518
|
+
# Get entries/allowed values for this Enum
|
519
|
+
self.entries = [
|
520
|
+
self.EnumEntry(
|
521
|
+
name=pythonize_enum_member_name(entry_proto_value.name, self.proto_obj.name),
|
522
|
+
value=entry_proto_value.number,
|
523
|
+
comment=get_comment(proto_file=self.source_file, path=self.path + [2, entry_number]),
|
524
|
+
)
|
525
|
+
for entry_number, entry_proto_value in enumerate(self.proto_obj.value)
|
526
|
+
]
|
527
|
+
|
528
|
+
@property
|
529
|
+
def proto_name(self) -> str:
|
530
|
+
return self.proto_obj.name
|
531
|
+
|
532
|
+
@property
|
533
|
+
def py_name(self) -> str:
|
534
|
+
return pythonize_class_name(self.proto_name)
|
535
|
+
|
536
|
+
@property
|
537
|
+
def deprecated(self) -> bool:
|
538
|
+
return bool(self.proto_obj.options and self.proto_obj.options.deprecated)
|
539
|
+
|
540
|
+
|
541
|
+
@dataclass(kw_only=True)
|
542
|
+
class ServiceCompiler(ProtoContentBase):
|
543
|
+
output_file: OutputTemplate
|
544
|
+
proto_obj: ServiceDescriptorProto
|
545
|
+
methods: list["ServiceMethodCompiler"] = field(default_factory=list)
|
546
|
+
|
547
|
+
@property
|
548
|
+
def proto_name(self) -> str:
|
549
|
+
return self.proto_obj.name
|
550
|
+
|
551
|
+
@property
|
552
|
+
def py_name(self) -> str:
|
553
|
+
return pythonize_class_name(self.proto_name)
|
554
|
+
|
555
|
+
|
556
|
+
@dataclass(kw_only=True)
|
557
|
+
class ServiceMethodCompiler(ProtoContentBase):
|
558
|
+
parent: ServiceCompiler
|
559
|
+
proto_obj: MethodDescriptorProto
|
560
|
+
|
561
|
+
@property
|
562
|
+
def py_name(self) -> str:
|
563
|
+
"""Pythonized method name."""
|
564
|
+
return pythonize_method_name(self.proto_obj.name)
|
565
|
+
|
566
|
+
@property
|
567
|
+
def proto_name(self) -> str:
|
568
|
+
"""Original protobuf name."""
|
569
|
+
return self.proto_obj.name
|
570
|
+
|
571
|
+
@property
|
572
|
+
def route(self) -> str:
|
573
|
+
package_part = f"{self.parent.output_file.package}." if self.parent.output_file.package else ""
|
574
|
+
return f"/{package_part}{self.parent.proto_name}/{self.proto_name}"
|
575
|
+
|
576
|
+
@property
|
577
|
+
def py_input_message_type(self) -> str:
|
578
|
+
"""String representation of the Python type corresponding to the
|
579
|
+
input message.
|
580
|
+
|
581
|
+
Returns
|
582
|
+
-------
|
583
|
+
str
|
584
|
+
String representation of the Python type corresponding to the input message.
|
585
|
+
"""
|
586
|
+
return get_type_reference(
|
587
|
+
package=self.parent.output_file.package,
|
588
|
+
imports=self.parent.output_file.imports_end,
|
589
|
+
source_type=self.proto_obj.input_type,
|
590
|
+
request=self.parent.output_file.parent_request,
|
591
|
+
unwrap=False,
|
592
|
+
settings=self.parent.output_file.settings,
|
593
|
+
)
|
594
|
+
|
595
|
+
@property
|
596
|
+
def is_input_msg_empty(self: "ServiceMethodCompiler") -> bool:
|
597
|
+
package, name = parse_source_type_name(self.proto_obj.input_type, self.parent.output_file.parent_request)
|
598
|
+
|
599
|
+
msg = self.parent.output_file.parent_request.output_packages[package].messages[name]
|
600
|
+
|
601
|
+
return not bool(msg.fields)
|
602
|
+
|
603
|
+
@property
|
604
|
+
def py_input_message_param(self) -> str:
|
605
|
+
"""Param name corresponding to py_input_message_type.
|
606
|
+
|
607
|
+
Returns
|
608
|
+
-------
|
609
|
+
str
|
610
|
+
Param name corresponding to py_input_message_type.
|
611
|
+
"""
|
612
|
+
return pythonize_field_name(self.py_input_message_type)
|
613
|
+
|
614
|
+
@property
|
615
|
+
def py_output_message_type(self) -> str:
|
616
|
+
"""String representation of the Python type corresponding to the
|
617
|
+
output message.
|
618
|
+
|
619
|
+
Returns
|
620
|
+
-------
|
621
|
+
str
|
622
|
+
String representation of the Python type corresponding to the output message.
|
623
|
+
"""
|
624
|
+
return get_type_reference(
|
625
|
+
package=self.parent.output_file.package,
|
626
|
+
imports=self.parent.output_file.imports_end,
|
627
|
+
source_type=self.proto_obj.output_type,
|
628
|
+
request=self.parent.output_file.parent_request,
|
629
|
+
unwrap=False,
|
630
|
+
settings=self.parent.output_file.settings,
|
631
|
+
)
|
632
|
+
|
633
|
+
@property
|
634
|
+
def client_streaming(self) -> bool:
|
635
|
+
return self.proto_obj.client_streaming
|
636
|
+
|
637
|
+
@property
|
638
|
+
def server_streaming(self) -> bool:
|
639
|
+
return self.proto_obj.server_streaming
|
640
|
+
|
641
|
+
@property
|
642
|
+
def deprecated(self) -> bool:
|
643
|
+
return bool(self.proto_obj.options and self.proto_obj.options.deprecated)
|