betterproto2-compiler 0.3.2__py3-none-any.whl → 0.5.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/compile/importing.py +5 -25
- betterproto2_compiler/known_types/__init__.py +98 -2
- betterproto2_compiler/known_types/duration.py +45 -0
- betterproto2_compiler/known_types/google_values.py +231 -0
- betterproto2_compiler/known_types/timestamp.py +39 -0
- betterproto2_compiler/lib/google/protobuf/__init__.py +418 -603
- betterproto2_compiler/lib/google/protobuf/compiler/__init__.py +25 -14
- betterproto2_compiler/plugin/models.py +55 -23
- betterproto2_compiler/plugin/parser.py +22 -1
- betterproto2_compiler/settings.py +67 -0
- betterproto2_compiler/templates/header.py.j2 +4 -3
- betterproto2_compiler/templates/service_stub.py.j2 +32 -0
- betterproto2_compiler/templates/service_stub_async.py.j2 +86 -0
- betterproto2_compiler/templates/service_stub_sync.py.j2 +72 -0
- betterproto2_compiler/templates/template.py.j2 +9 -93
- {betterproto2_compiler-0.3.2.dist-info → betterproto2_compiler-0.5.0.dist-info}/METADATA +3 -3
- betterproto2_compiler-0.5.0.dist-info/RECORD +35 -0
- {betterproto2_compiler-0.3.2.dist-info → betterproto2_compiler-0.5.0.dist-info}/WHEEL +1 -1
- betterproto2_compiler-0.3.2.dist-info/RECORD +0 -31
- {betterproto2_compiler-0.3.2.dist-info → betterproto2_compiler-0.5.0.dist-info}/LICENSE.md +0 -0
- {betterproto2_compiler-0.3.2.dist-info → betterproto2_compiler-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
-
# sources:
|
2
|
+
# sources: plugin.proto
|
3
3
|
# plugin: python-betterproto2
|
4
4
|
# This file has been @generated
|
5
5
|
|
@@ -11,18 +11,13 @@ __all__ = (
|
|
11
11
|
"Version",
|
12
12
|
)
|
13
13
|
|
14
|
-
|
15
14
|
from dataclasses import dataclass
|
16
|
-
from typing import (
|
17
|
-
List,
|
18
|
-
Optional,
|
19
|
-
)
|
20
15
|
|
21
16
|
import betterproto2
|
22
17
|
|
23
18
|
from ....message_pool import default_message_pool
|
24
19
|
|
25
|
-
betterproto2.check_compiler_version("0.
|
20
|
+
betterproto2.check_compiler_version("0.5.0")
|
26
21
|
|
27
22
|
|
28
23
|
class CodeGeneratorResponseFeature(betterproto2.Enum):
|
@@ -43,7 +38,7 @@ class CodeGeneratorRequest(betterproto2.Message):
|
|
43
38
|
An encoded CodeGeneratorRequest is written to the plugin's stdin.
|
44
39
|
"""
|
45
40
|
|
46
|
-
file_to_generate: "
|
41
|
+
file_to_generate: "list[str]" = betterproto2.field(1, betterproto2.TYPE_STRING, repeated=True)
|
47
42
|
"""
|
48
43
|
The .proto files that were explicitly listed on the command-line. The
|
49
44
|
code generator should generate code only for these files. Each file's
|
@@ -55,7 +50,7 @@ class CodeGeneratorRequest(betterproto2.Message):
|
|
55
50
|
The generator parameter passed on the command-line.
|
56
51
|
"""
|
57
52
|
|
58
|
-
proto_file: "
|
53
|
+
proto_file: "list[__protobuf__.FileDescriptorProto]" = betterproto2.field(
|
59
54
|
15, betterproto2.TYPE_MESSAGE, repeated=True
|
60
55
|
)
|
61
56
|
"""
|
@@ -80,7 +75,7 @@ class CodeGeneratorRequest(betterproto2.Message):
|
|
80
75
|
fully qualified.
|
81
76
|
"""
|
82
77
|
|
83
|
-
source_file_descriptors: "
|
78
|
+
source_file_descriptors: "list[__protobuf__.FileDescriptorProto]" = betterproto2.field(
|
84
79
|
17, betterproto2.TYPE_MESSAGE, repeated=True
|
85
80
|
)
|
86
81
|
"""
|
@@ -89,7 +84,7 @@ class CodeGeneratorRequest(betterproto2.Message):
|
|
89
84
|
files_to_generate.
|
90
85
|
"""
|
91
86
|
|
92
|
-
compiler_version: "
|
87
|
+
compiler_version: "Version | None" = betterproto2.field(3, betterproto2.TYPE_MESSAGE, optional=True)
|
93
88
|
"""
|
94
89
|
The version number of protocol compiler.
|
95
90
|
"""
|
@@ -122,7 +117,23 @@ class CodeGeneratorResponse(betterproto2.Message):
|
|
122
117
|
This is a bitwise "or" of values from the Feature enum.
|
123
118
|
"""
|
124
119
|
|
125
|
-
|
120
|
+
minimum_edition: "int" = betterproto2.field(3, betterproto2.TYPE_INT32)
|
121
|
+
"""
|
122
|
+
The minimum edition this plugin supports. This will be treated as an
|
123
|
+
Edition enum, but we want to allow unknown values. It should be specified
|
124
|
+
according the edition enum value, *not* the edition number. Only takes
|
125
|
+
effect for plugins that have FEATURE_SUPPORTS_EDITIONS set.
|
126
|
+
"""
|
127
|
+
|
128
|
+
maximum_edition: "int" = betterproto2.field(4, betterproto2.TYPE_INT32)
|
129
|
+
"""
|
130
|
+
The maximum edition this plugin supports. This will be treated as an
|
131
|
+
Edition enum, but we want to allow unknown values. It should be specified
|
132
|
+
according the edition enum value, *not* the edition number. Only takes
|
133
|
+
effect for plugins that have FEATURE_SUPPORTS_EDITIONS set.
|
134
|
+
"""
|
135
|
+
|
136
|
+
file: "list[CodeGeneratorResponseFile]" = betterproto2.field(15, betterproto2.TYPE_MESSAGE, repeated=True)
|
126
137
|
|
127
138
|
|
128
139
|
default_message_pool.register_message("google.protobuf.compiler", "CodeGeneratorResponse", CodeGeneratorResponse)
|
@@ -139,7 +150,7 @@ class CodeGeneratorResponseFile(betterproto2.Message):
|
|
139
150
|
The file name, relative to the output directory. The name must not
|
140
151
|
contain "." or ".." components and must be relative, not be absolute (so,
|
141
152
|
the file cannot lie outside the output directory). "/" must be used as
|
142
|
-
the path separator, not "
|
153
|
+
the path separator, not "\\".
|
143
154
|
|
144
155
|
If the name is omitted, the content will be appended to the previous
|
145
156
|
file. This allows the generator to break large files into small chunks,
|
@@ -195,7 +206,7 @@ class CodeGeneratorResponseFile(betterproto2.Message):
|
|
195
206
|
The file contents.
|
196
207
|
"""
|
197
208
|
|
198
|
-
generated_code_info: "
|
209
|
+
generated_code_info: "__protobuf__.GeneratedCodeInfo | None" = betterproto2.field(
|
199
210
|
16, betterproto2.TYPE_MESSAGE, optional=True
|
200
211
|
)
|
201
212
|
"""
|
@@ -26,14 +26,12 @@ such as a pythonized name, that will be calculated from proto_obj.
|
|
26
26
|
|
27
27
|
import builtins
|
28
28
|
import inspect
|
29
|
-
import re
|
30
29
|
from collections.abc import Iterator
|
31
30
|
from dataclasses import (
|
32
31
|
dataclass,
|
33
32
|
field,
|
34
33
|
)
|
35
34
|
|
36
|
-
import betterproto2
|
37
35
|
from betterproto2 import unwrap
|
38
36
|
|
39
37
|
from betterproto2_compiler.compile.importing import get_type_reference, parse_source_type_name
|
@@ -43,7 +41,7 @@ from betterproto2_compiler.compile.naming import (
|
|
43
41
|
pythonize_field_name,
|
44
42
|
pythonize_method_name,
|
45
43
|
)
|
46
|
-
from betterproto2_compiler.known_types import KNOWN_METHODS
|
44
|
+
from betterproto2_compiler.known_types import KNOWN_METHODS, WRAPPED_TYPES
|
47
45
|
from betterproto2_compiler.lib.google.protobuf import (
|
48
46
|
DescriptorProto,
|
49
47
|
EnumDescriptorProto,
|
@@ -318,8 +316,14 @@ class FieldCompiler(ProtoContentBase):
|
|
318
316
|
@property
|
319
317
|
def betterproto_field_args(self) -> list[str]:
|
320
318
|
args = []
|
321
|
-
|
322
|
-
|
319
|
+
|
320
|
+
if self.field_type == FieldDescriptorProtoType.TYPE_MESSAGE and self.is_wrapped:
|
321
|
+
unwrap_type = self.unwrapped_py_type
|
322
|
+
|
323
|
+
# Without the lambda function, the type is evaluated right away, which fails since the corresponding
|
324
|
+
# import is placed at the end of the file to avoid circular imports.
|
325
|
+
args.append(f"unwrap=lambda: {unwrap_type}")
|
326
|
+
|
323
327
|
if self.optional:
|
324
328
|
args.append("optional=True")
|
325
329
|
elif self.repeated:
|
@@ -338,16 +342,6 @@ class FieldCompiler(ProtoContentBase):
|
|
338
342
|
self.py_type == self.py_name and self.py_name in dir(builtins)
|
339
343
|
)
|
340
344
|
|
341
|
-
@property
|
342
|
-
def field_wraps(self) -> str | None:
|
343
|
-
"""Returns betterproto wrapped field type or None."""
|
344
|
-
match_wrapper = re.match(r"\.google\.protobuf\.(.+)Value$", self.proto_obj.type_name)
|
345
|
-
if match_wrapper:
|
346
|
-
wrapped_type = "TYPE_" + match_wrapper.group(1).upper()
|
347
|
-
if hasattr(betterproto2, wrapped_type):
|
348
|
-
return f"betterproto2.{wrapped_type}"
|
349
|
-
return None
|
350
|
-
|
351
345
|
@property
|
352
346
|
def repeated(self) -> bool:
|
353
347
|
return self.proto_obj.label == FieldDescriptorProtoLabel.LABEL_REPEATED
|
@@ -378,7 +372,13 @@ class FieldCompiler(ProtoContentBase):
|
|
378
372
|
return self.proto_obj.name
|
379
373
|
|
380
374
|
@property
|
381
|
-
def
|
375
|
+
def is_wrapped(self) -> bool:
|
376
|
+
assert self.field_type == FieldDescriptorProtoType.TYPE_MESSAGE
|
377
|
+
type_package, type_name = parse_source_type_name(self.proto_obj.type_name, self.output_file.parent_request)
|
378
|
+
|
379
|
+
return (type_package, type_name) in WRAPPED_TYPES
|
380
|
+
|
381
|
+
def _py_type(self, wrap: bool) -> str:
|
382
382
|
"""String representation of Python type."""
|
383
383
|
if self.proto_obj.type in PROTO_FLOAT_TYPES:
|
384
384
|
return "float"
|
@@ -397,14 +397,24 @@ class FieldCompiler(ProtoContentBase):
|
|
397
397
|
imports=self.output_file.imports_end,
|
398
398
|
source_type=self.proto_obj.type_name,
|
399
399
|
request=self.output_file.parent_request,
|
400
|
+
wrap=wrap,
|
400
401
|
settings=self.output_file.settings,
|
401
402
|
)
|
402
403
|
else:
|
403
404
|
raise NotImplementedError(f"Unknown type {self.proto_obj.type}")
|
404
405
|
|
406
|
+
@property
|
407
|
+
def py_type(self) -> str:
|
408
|
+
return self._py_type(wrap=True)
|
409
|
+
|
410
|
+
@property
|
411
|
+
def unwrapped_py_type(self) -> str:
|
412
|
+
return self._py_type(wrap=False)
|
413
|
+
|
405
414
|
@property
|
406
415
|
def annotation(self) -> str:
|
407
416
|
py_type = self.py_type
|
417
|
+
|
408
418
|
if self.use_builtins:
|
409
419
|
py_type = f"builtins.{py_type}"
|
410
420
|
if self.repeated:
|
@@ -432,6 +442,8 @@ class OneOfFieldCompiler(FieldCompiler):
|
|
432
442
|
class MapEntryCompiler(FieldCompiler):
|
433
443
|
py_k_type: str = ""
|
434
444
|
py_v_type: str = ""
|
445
|
+
unwrap_v: str | None = None
|
446
|
+
|
435
447
|
proto_k_type: str = ""
|
436
448
|
proto_v_type: str = ""
|
437
449
|
|
@@ -441,18 +453,30 @@ class MapEntryCompiler(FieldCompiler):
|
|
441
453
|
for nested in self.message.proto_obj.nested_type:
|
442
454
|
if nested.name.replace("_", "").lower() == map_entry and unwrap(nested.options).map_entry:
|
443
455
|
# Get Python types
|
456
|
+
assert nested.field[0].name == "key"
|
444
457
|
self.py_k_type = FieldCompiler(
|
445
458
|
source_file=self.source_file,
|
446
459
|
proto_obj=nested.field[0], # key
|
447
460
|
path=[],
|
448
461
|
message=self.message,
|
449
462
|
).py_type
|
450
|
-
|
463
|
+
|
464
|
+
assert nested.field[1].name == "value"
|
465
|
+
value_field_compiler = FieldCompiler(
|
451
466
|
source_file=self.source_file,
|
452
467
|
proto_obj=nested.field[1], # value
|
453
468
|
path=[],
|
454
469
|
message=self.message,
|
455
|
-
)
|
470
|
+
)
|
471
|
+
|
472
|
+
self.py_v_type = value_field_compiler.py_type
|
473
|
+
if (
|
474
|
+
value_field_compiler.field_type == FieldDescriptorProtoType.TYPE_MESSAGE
|
475
|
+
and value_field_compiler.is_wrapped
|
476
|
+
):
|
477
|
+
self.unwrap_v = value_field_compiler.unwrapped_py_type
|
478
|
+
else:
|
479
|
+
self.unwrap_v = None
|
456
480
|
|
457
481
|
# Get proto types
|
458
482
|
self.proto_k_type = unwrap(FieldDescriptorProtoType(nested.field[0].type).name)
|
@@ -463,11 +487,19 @@ class MapEntryCompiler(FieldCompiler):
|
|
463
487
|
|
464
488
|
def get_field_string(self) -> str:
|
465
489
|
"""Construct string representation of this field as a field."""
|
490
|
+
proto_type_1 = f"betterproto2.{self.proto_k_type}"
|
491
|
+
proto_type_2 = f"betterproto2.{self.proto_v_type}"
|
492
|
+
|
493
|
+
unwrap_2 = ""
|
494
|
+
if self.unwrap_v:
|
495
|
+
unwrap_2 = f", unwrap_2=lambda: {self.unwrap_v}"
|
496
|
+
|
466
497
|
betterproto_field_type = (
|
467
|
-
|
498
|
+
"betterproto2.field("
|
499
|
+
f"{self.proto_obj.number}, "
|
468
500
|
"betterproto2.TYPE_MAP, "
|
469
|
-
f"
|
470
|
-
|
501
|
+
f"map_meta=betterproto2.map_meta({proto_type_1}, {proto_type_2}{unwrap_2})"
|
502
|
+
")"
|
471
503
|
)
|
472
504
|
if self.py_name in dir(builtins):
|
473
505
|
self.message.builtins_types.add(self.py_name)
|
@@ -581,7 +613,7 @@ class ServiceMethodCompiler(ProtoContentBase):
|
|
581
613
|
imports=self.parent.output_file.imports_end,
|
582
614
|
source_type=self.proto_obj.input_type,
|
583
615
|
request=self.parent.output_file.parent_request,
|
584
|
-
|
616
|
+
wrap=False,
|
585
617
|
settings=self.parent.output_file.settings,
|
586
618
|
)
|
587
619
|
|
@@ -608,7 +640,7 @@ class ServiceMethodCompiler(ProtoContentBase):
|
|
608
640
|
imports=self.parent.output_file.imports_end,
|
609
641
|
source_type=self.proto_obj.output_type,
|
610
642
|
request=self.parent.output_file.parent_request,
|
611
|
-
|
643
|
+
wrap=False,
|
612
644
|
settings=self.parent.output_file.settings,
|
613
645
|
)
|
614
646
|
|
@@ -14,7 +14,7 @@ from betterproto2_compiler.lib.google.protobuf.compiler import (
|
|
14
14
|
CodeGeneratorResponseFeature,
|
15
15
|
CodeGeneratorResponseFile,
|
16
16
|
)
|
17
|
-
from betterproto2_compiler.settings import Settings
|
17
|
+
from betterproto2_compiler.settings import ClientGeneration, ServerGeneration, Settings
|
18
18
|
|
19
19
|
from .compiler import outputfile_compiler
|
20
20
|
from .models import (
|
@@ -60,8 +60,29 @@ def traverse(
|
|
60
60
|
|
61
61
|
|
62
62
|
def get_settings(plugin_options: list[str]) -> Settings:
|
63
|
+
# Synchronous clients are suitable for most users
|
64
|
+
client_generation = ClientGeneration.SYNC
|
65
|
+
server_generation = ServerGeneration.NONE
|
66
|
+
|
67
|
+
for opt in plugin_options:
|
68
|
+
if opt.startswith("client_generation="):
|
69
|
+
name = opt.split("=")[1]
|
70
|
+
try:
|
71
|
+
client_generation = ClientGeneration(name)
|
72
|
+
except ValueError:
|
73
|
+
raise ValueError(f"Invalid client_generation option: {name}")
|
74
|
+
|
75
|
+
if opt.startswith("server_generation="):
|
76
|
+
name = opt.split("=")[1]
|
77
|
+
try:
|
78
|
+
server_generation = ServerGeneration(name)
|
79
|
+
except ValueError:
|
80
|
+
raise ValueError(f"Invalid server_generation option: {name}")
|
81
|
+
|
63
82
|
return Settings(
|
64
83
|
pydantic_dataclasses="pydantic_dataclasses" in plugin_options,
|
84
|
+
client_generation=client_generation,
|
85
|
+
server_generation=server_generation,
|
65
86
|
)
|
66
87
|
|
67
88
|
|
@@ -1,6 +1,73 @@
|
|
1
|
+
import sys
|
1
2
|
from dataclasses import dataclass
|
2
3
|
|
4
|
+
if sys.version_info >= (3, 11):
|
5
|
+
from enum import StrEnum
|
6
|
+
else:
|
7
|
+
from strenum import StrEnum
|
8
|
+
|
9
|
+
|
10
|
+
class ClientGeneration(StrEnum):
|
11
|
+
NONE = "none"
|
12
|
+
"""Clients are not generated."""
|
13
|
+
|
14
|
+
SYNC = "sync"
|
15
|
+
"""Only synchronous clients are generated."""
|
16
|
+
|
17
|
+
ASYNC = "async"
|
18
|
+
"""Only asynchronous clients are generated."""
|
19
|
+
|
20
|
+
SYNC_ASYNC = "sync_async"
|
21
|
+
"""Both synchronous and asynchronous clients are generated.
|
22
|
+
|
23
|
+
Asynchronous clients are generated with the Async suffix."""
|
24
|
+
|
25
|
+
ASYNC_SYNC = "async_sync"
|
26
|
+
"""Both synchronous and asynchronous clients are generated.
|
27
|
+
|
28
|
+
Synchronous clients are generated with the Sync suffix."""
|
29
|
+
|
30
|
+
SYNC_ASYNC_NO_DEFAULT = "sync_async_no_default"
|
31
|
+
"""Both synchronous and asynchronous clients are generated.
|
32
|
+
|
33
|
+
Synchronous clients are generated with the Sync suffix, and asynchronous clients are generated with the Async
|
34
|
+
suffix."""
|
35
|
+
|
36
|
+
@property
|
37
|
+
def is_sync_generated(self) -> bool:
|
38
|
+
return self in {
|
39
|
+
ClientGeneration.SYNC,
|
40
|
+
ClientGeneration.SYNC_ASYNC,
|
41
|
+
ClientGeneration.ASYNC_SYNC,
|
42
|
+
ClientGeneration.SYNC_ASYNC_NO_DEFAULT,
|
43
|
+
}
|
44
|
+
|
45
|
+
@property
|
46
|
+
def is_async_generated(self) -> bool:
|
47
|
+
return self in {
|
48
|
+
ClientGeneration.ASYNC,
|
49
|
+
ClientGeneration.SYNC_ASYNC,
|
50
|
+
ClientGeneration.ASYNC_SYNC,
|
51
|
+
ClientGeneration.SYNC_ASYNC_NO_DEFAULT,
|
52
|
+
}
|
53
|
+
|
54
|
+
@property
|
55
|
+
def is_sync_prefixed(self) -> bool:
|
56
|
+
return self in {ClientGeneration.ASYNC_SYNC, ClientGeneration.SYNC_ASYNC_NO_DEFAULT}
|
57
|
+
|
58
|
+
@property
|
59
|
+
def is_async_prefixed(self) -> bool:
|
60
|
+
return self in {ClientGeneration.SYNC_ASYNC, ClientGeneration.SYNC_ASYNC_NO_DEFAULT}
|
61
|
+
|
62
|
+
|
63
|
+
class ServerGeneration(StrEnum):
|
64
|
+
NONE = "none"
|
65
|
+
ASYNC = "async"
|
66
|
+
|
3
67
|
|
4
68
|
@dataclass
|
5
69
|
class Settings:
|
6
70
|
pydantic_dataclasses: bool
|
71
|
+
|
72
|
+
client_generation: ClientGeneration
|
73
|
+
server_generation: ServerGeneration
|
@@ -18,10 +18,12 @@ __all__ = (
|
|
18
18
|
{%- endfor -%}
|
19
19
|
)
|
20
20
|
|
21
|
+
import re
|
21
22
|
import builtins
|
22
23
|
import datetime
|
24
|
+
import dateutil.parser
|
23
25
|
import warnings
|
24
|
-
from collections.abc import AsyncIterable, AsyncIterator, Iterable
|
26
|
+
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
25
27
|
import typing
|
26
28
|
from typing import TYPE_CHECKING
|
27
29
|
|
@@ -33,10 +35,9 @@ from dataclasses import dataclass
|
|
33
35
|
{% endif %}
|
34
36
|
|
35
37
|
import betterproto2
|
36
|
-
{% if output_file.services %}
|
37
38
|
from betterproto2.grpc.grpclib_server import ServiceBase
|
39
|
+
import grpc
|
38
40
|
import grpclib
|
39
|
-
{% endif %}
|
40
41
|
|
41
42
|
{# Import the message pool of the generated code. #}
|
42
43
|
{% if output_file.package %}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class {% block class_name %}{% endblock %}({% block inherit_from %}{% endblock %}):
|
2
|
+
{% block service_docstring scoped %}
|
3
|
+
{% if service.comment %}
|
4
|
+
"""
|
5
|
+
{{ service.comment | indent(4) }}
|
6
|
+
"""
|
7
|
+
{% elif not service.methods %}
|
8
|
+
pass
|
9
|
+
{% endif %}
|
10
|
+
{% endblock %}
|
11
|
+
|
12
|
+
{% block class_content %}{% endblock %}
|
13
|
+
|
14
|
+
{% for method in service.methods %}
|
15
|
+
{% block method_definition scoped required %}{% endblock %}
|
16
|
+
{% block method_docstring scoped %}
|
17
|
+
{% if method.comment %}
|
18
|
+
"""
|
19
|
+
{{ method.comment | indent(8) }}
|
20
|
+
"""
|
21
|
+
{% endif %}
|
22
|
+
{% endblock %}
|
23
|
+
|
24
|
+
{% block deprecation_warning scoped %}
|
25
|
+
{% if method.deprecated %}
|
26
|
+
warnings.warn("{{ service.py_name }}.{{ method.py_name }} is deprecated", DeprecationWarning)
|
27
|
+
{% endif %}
|
28
|
+
{% endblock %}
|
29
|
+
|
30
|
+
{% block method_body scoped required %}{% endblock %}
|
31
|
+
|
32
|
+
{% endfor %}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
{% extends "service_stub.py.j2" %}
|
2
|
+
|
3
|
+
{# Class definition #}
|
4
|
+
{% block class_name %}{{ service.py_name }}{% if output_file.settings.client_generation.is_async_prefixed %}Async{% endif %}Stub{% endblock %}
|
5
|
+
{% block inherit_from %}betterproto2.ServiceStub{% endblock %}
|
6
|
+
|
7
|
+
{# Methods definition #}
|
8
|
+
{% block method_definition %}
|
9
|
+
async def {{ method.py_name }}(self
|
10
|
+
{%- if not method.client_streaming -%}
|
11
|
+
, message:
|
12
|
+
{%- if method.is_input_msg_empty -%}
|
13
|
+
"{{ method.py_input_message_type }} | None" = None
|
14
|
+
{%- else -%}
|
15
|
+
"{{ method.py_input_message_type }}"
|
16
|
+
{%- endif -%}
|
17
|
+
{%- else -%}
|
18
|
+
{# Client streaming: need a request iterator instead #}
|
19
|
+
, messages: "AsyncIterable[{{ method.py_input_message_type }}] | Iterable[{{ method.py_input_message_type }}]"
|
20
|
+
{%- endif -%}
|
21
|
+
,
|
22
|
+
*
|
23
|
+
, timeout: "float | None" = None
|
24
|
+
, deadline: "Deadline | None" = None
|
25
|
+
, metadata: "MetadataLike | None" = None
|
26
|
+
) -> "{% if method.server_streaming %}AsyncIterator[{{ method.py_output_message_type }}]{% else %}{{ method.py_output_message_type }}{% endif %}":
|
27
|
+
{% endblock %}
|
28
|
+
|
29
|
+
{% block method_body %}
|
30
|
+
{% if method.server_streaming %}
|
31
|
+
{% if method.client_streaming %}
|
32
|
+
async for response in self._stream_stream(
|
33
|
+
"{{ method.route }}",
|
34
|
+
messages,
|
35
|
+
{{ method.py_input_message_type }},
|
36
|
+
{{ method.py_output_message_type }},
|
37
|
+
timeout=timeout,
|
38
|
+
deadline=deadline,
|
39
|
+
metadata=metadata,
|
40
|
+
):
|
41
|
+
yield response
|
42
|
+
{% else %}
|
43
|
+
{% if method.is_input_msg_empty %}
|
44
|
+
if message is None:
|
45
|
+
message = {{ method.py_input_message_type }}()
|
46
|
+
|
47
|
+
{% endif %}
|
48
|
+
async for response in self._unary_stream(
|
49
|
+
"{{ method.route }}",
|
50
|
+
message,
|
51
|
+
{{ method.py_output_message_type }},
|
52
|
+
timeout=timeout,
|
53
|
+
deadline=deadline,
|
54
|
+
metadata=metadata,
|
55
|
+
):
|
56
|
+
yield response
|
57
|
+
|
58
|
+
{% endif %}
|
59
|
+
{% else %}
|
60
|
+
{% if method.client_streaming %}
|
61
|
+
return await self._stream_unary(
|
62
|
+
"{{ method.route }}",
|
63
|
+
messages,
|
64
|
+
{{ method.py_input_message_type }},
|
65
|
+
{{ method.py_output_message_type }},
|
66
|
+
timeout=timeout,
|
67
|
+
deadline=deadline,
|
68
|
+
metadata=metadata,
|
69
|
+
)
|
70
|
+
{% else %}
|
71
|
+
{% if method.is_input_msg_empty %}
|
72
|
+
if message is None:
|
73
|
+
message = {{ method.py_input_message_type }}()
|
74
|
+
|
75
|
+
{% endif %}
|
76
|
+
return await self._unary_unary(
|
77
|
+
"{{ method.route }}",
|
78
|
+
message,
|
79
|
+
{{ method.py_output_message_type }},
|
80
|
+
timeout=timeout,
|
81
|
+
deadline=deadline,
|
82
|
+
metadata=metadata,
|
83
|
+
)
|
84
|
+
{% endif %}
|
85
|
+
{% endif %}
|
86
|
+
{% endblock %}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
{% extends "service_stub.py.j2" %}
|
2
|
+
|
3
|
+
{# Class definition #}
|
4
|
+
{% block class_name %}{{ service.py_name }}{% if output_file.settings.client_generation.is_sync_prefixed %}Sync{% endif %}Stub{% endblock %}
|
5
|
+
|
6
|
+
{% block class_content %}
|
7
|
+
{# TODO move to parent class #}
|
8
|
+
def __init__(self, channel: grpc.Channel):
|
9
|
+
self._channel = channel
|
10
|
+
{% endblock %}
|
11
|
+
|
12
|
+
{# Methods definition #}
|
13
|
+
{% block method_definition %}
|
14
|
+
def {{ method.py_name }}(self
|
15
|
+
{%- if not method.client_streaming -%}
|
16
|
+
, message:
|
17
|
+
{%- if method.is_input_msg_empty -%}
|
18
|
+
"{{ method.py_input_message_type }} | None" = None
|
19
|
+
{%- else -%}
|
20
|
+
"{{ method.py_input_message_type }}"
|
21
|
+
{%- endif -%}
|
22
|
+
{%- else -%}
|
23
|
+
{# Client streaming: need a request iterator instead #}
|
24
|
+
, messages: "Iterable[{{ method.py_input_message_type }}]"
|
25
|
+
{%- endif -%}
|
26
|
+
) -> "{% if method.server_streaming %}Iterator[{{ method.py_output_message_type }}]{% else %}{{ method.py_output_message_type }}{% endif %}":
|
27
|
+
{% endblock %}
|
28
|
+
|
29
|
+
{% block method_body %}
|
30
|
+
{% if method.server_streaming %}
|
31
|
+
{% if method.client_streaming %}
|
32
|
+
for response in self._channel.stream_stream(
|
33
|
+
"{{ method.route }}",
|
34
|
+
{{ method.py_input_message_type }}.SerializeToString,
|
35
|
+
{{ method.py_output_message_type }}.FromString,
|
36
|
+
)(iter(messages)):
|
37
|
+
yield response
|
38
|
+
{% else %}
|
39
|
+
{% if method.is_input_msg_empty %}
|
40
|
+
if message is None:
|
41
|
+
message = {{ method.py_input_message_type }}()
|
42
|
+
|
43
|
+
{% endif %}
|
44
|
+
for response in self._channel.unary_stream(
|
45
|
+
"{{ method.route }}",
|
46
|
+
{{ method.py_input_message_type }}.SerializeToString,
|
47
|
+
{{ method.py_output_message_type }}.FromString,
|
48
|
+
)(message):
|
49
|
+
yield response
|
50
|
+
|
51
|
+
{% endif %}
|
52
|
+
{% else %}
|
53
|
+
{% if method.client_streaming %}
|
54
|
+
return self._channel.stream_unary(
|
55
|
+
"{{ method.route }}",
|
56
|
+
{{ method.py_input_message_type }}.SerializeToString,
|
57
|
+
{{ method.py_output_message_type }}.FromString,
|
58
|
+
)(iter(messages))
|
59
|
+
{% else %}
|
60
|
+
{% if method.is_input_msg_empty %}
|
61
|
+
if message is None:
|
62
|
+
message = {{ method.py_input_message_type }}()
|
63
|
+
|
64
|
+
{% endif %}
|
65
|
+
return self._channel.unary_unary(
|
66
|
+
"{{ method.route }}",
|
67
|
+
{{ method.py_input_message_type }}.SerializeToString,
|
68
|
+
{{ method.py_output_message_type }}.FromString,
|
69
|
+
)(message)
|
70
|
+
{% endif %}
|
71
|
+
{% endif %}
|
72
|
+
{% endblock %}
|