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.
@@ -1,5 +1,5 @@
1
1
  # Generated by the protocol buffer compiler. DO NOT EDIT!
2
- # sources: google/protobuf/compiler/plugin.proto
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.3.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: "List[str]" = betterproto2.field(1, betterproto2.TYPE_STRING, repeated=True)
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: "List[__protobuf__.FileDescriptorProto]" = betterproto2.field(
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: "List[__protobuf__.FileDescriptorProto]" = betterproto2.field(
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: "Optional[Version]" = betterproto2.field(3, betterproto2.TYPE_MESSAGE, optional=True)
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
- file: "List[CodeGeneratorResponseFile]" = betterproto2.field(15, betterproto2.TYPE_MESSAGE, repeated=True)
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: "Optional[__protobuf__.GeneratedCodeInfo]" = betterproto2.field(
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
- if self.field_wraps:
322
- args.append(f"wraps={self.field_wraps}")
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 py_type(self) -> str:
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
- self.py_v_type = FieldCompiler(
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
- ).py_type
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
- f"betterproto2.field({self.proto_obj.number}, "
498
+ "betterproto2.field("
499
+ f"{self.proto_obj.number}, "
468
500
  "betterproto2.TYPE_MAP, "
469
- f"map_types=(betterproto2.{self.proto_k_type}, "
470
- f"betterproto2.{self.proto_v_type}))"
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
- unwrap=False,
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
- unwrap=False,
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 %}