Skip to content

Commit 66ba2e1

Browse files
kbandesKenneth Bandes
andauthored
feat: Support alternative http bindings in the gapic schema. (#993)
Support alternative http bindings in the gapic schema and adds support for parsing multiple bindings for one method. Co-authored-by: Kenneth Bandes <[email protected]>
1 parent 13832b2 commit 66ba2e1

File tree

2 files changed

+116
-4
lines changed

2 files changed

+116
-4
lines changed

packages/gapic-generator/gapic/schema/wrappers.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from google.api import annotations_pb2 # type: ignore
3737
from google.api import client_pb2
3838
from google.api import field_behavior_pb2
39+
from google.api import http_pb2
3940
from google.api import resource_pb2
4041
from google.api_core import exceptions # type: ignore
4142
from google.protobuf import descriptor_pb2 # type: ignore
@@ -706,6 +707,27 @@ class RetryInfo:
706707
retryable_exceptions: FrozenSet[exceptions.GoogleAPICallError]
707708

708709

710+
@dataclasses.dataclass(frozen=True)
711+
class HttpRule:
712+
"""Representation of the method's http bindings."""
713+
method: str
714+
uri: str
715+
body: Optional[str]
716+
717+
@classmethod
718+
def try_parse_http_rule(cls, http_rule) -> Optional['HttpRule']:
719+
method = http_rule.WhichOneof("pattern")
720+
if method is None or method == "custom":
721+
return None
722+
723+
uri = getattr(http_rule, method)
724+
if not uri:
725+
return None
726+
727+
body = http_rule.body or None
728+
return cls(method, uri, body)
729+
730+
709731
@dataclasses.dataclass(frozen=True)
710732
class Method:
711733
"""Description of a method (defined with the ``rpc`` keyword)."""
@@ -821,13 +843,22 @@ def field_headers(self) -> Sequence[str]:
821843

822844
return next((tuple(pattern.findall(verb)) for verb in potential_verbs if verb), ())
823845

846+
@property
847+
def http_options(self) -> List[HttpRule]:
848+
"""Return a list of the http bindings for this method."""
849+
http = self.options.Extensions[annotations_pb2.http]
850+
http_options = [http] + list(http.additional_bindings)
851+
opt_gen = (HttpRule.try_parse_http_rule(http_rule)
852+
for http_rule in http_options)
853+
return [rule for rule in opt_gen if rule]
854+
824855
@property
825856
def http_opt(self) -> Optional[Dict[str, str]]:
826-
"""Return the http option for this method.
857+
"""Return the (main) http option for this method.
827858
828-
e.g. {'verb': 'post'
829-
'url': '/some/path'
830-
'body': '*'}
859+
e.g. {'verb': 'post'
860+
'url': '/some/path'
861+
'body': '*'}
831862
832863
"""
833864
http: List[Tuple[descriptor_pb2.FieldDescriptorProto, str]]

packages/gapic-generator/tests/unit/schema/wrappers/test_method.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import collections
16+
import dataclasses
1617
from typing import Sequence
1718

1819
from google.api import field_behavior_pb2
@@ -328,6 +329,86 @@ def test_method_path_params_no_http_rule():
328329
assert method.path_params == []
329330

330331

332+
def test_method_http_options():
333+
verbs = [
334+
'get',
335+
'put',
336+
'post',
337+
'delete',
338+
'patch'
339+
]
340+
for v in verbs:
341+
http_rule = http_pb2.HttpRule(**{v: '/v1/{parent=projects/*}/topics'})
342+
method = make_method('DoSomething', http_rule=http_rule)
343+
assert [dataclasses.asdict(http) for http in method.http_options] == [{
344+
'method': v,
345+
'uri': '/v1/{parent=projects/*}/topics',
346+
'body': None
347+
}]
348+
349+
350+
def test_method_http_options_empty_http_rule():
351+
http_rule = http_pb2.HttpRule()
352+
method = make_method('DoSomething', http_rule=http_rule)
353+
assert method.http_options == []
354+
355+
http_rule = http_pb2.HttpRule(get='')
356+
method = make_method('DoSomething', http_rule=http_rule)
357+
assert method.http_options == []
358+
359+
360+
def test_method_http_options_no_http_rule():
361+
method = make_method('DoSomething')
362+
assert method.path_params == []
363+
364+
365+
def test_method_http_options_body():
366+
http_rule = http_pb2.HttpRule(
367+
post='/v1/{parent=projects/*}/topics',
368+
body='*'
369+
)
370+
method = make_method('DoSomething', http_rule=http_rule)
371+
assert [dataclasses.asdict(http) for http in method.http_options] == [{
372+
'method': 'post',
373+
'uri': '/v1/{parent=projects/*}/topics',
374+
'body': '*'
375+
}]
376+
377+
378+
def test_method_http_options_additional_bindings():
379+
http_rule = http_pb2.HttpRule(
380+
post='/v1/{parent=projects/*}/topics',
381+
body='*',
382+
additional_bindings=[
383+
http_pb2.HttpRule(
384+
post='/v1/{parent=projects/*/regions/*}/topics',
385+
body='*',
386+
),
387+
http_pb2.HttpRule(
388+
post='/v1/projects/p1/topics',
389+
body='body_field',
390+
),
391+
]
392+
)
393+
method = make_method('DoSomething', http_rule=http_rule)
394+
assert [dataclasses.asdict(http) for http in method.http_options] == [
395+
{
396+
'method': 'post',
397+
'uri': '/v1/{parent=projects/*}/topics',
398+
'body': '*'
399+
},
400+
{
401+
'method': 'post',
402+
'uri': '/v1/{parent=projects/*/regions/*}/topics',
403+
'body': '*'
404+
},
405+
{
406+
'method': 'post',
407+
'uri': '/v1/projects/p1/topics',
408+
'body': 'body_field'
409+
}]
410+
411+
331412
def test_method_query_params():
332413
# tests only the basic case of grpc transcoding
333414
http_rule = http_pb2.HttpRule(

0 commit comments

Comments
 (0)