Skip to content

Commit 412cc66

Browse files
authored
feat: add generator option proto-plus-dep (#1596)
* feat: add generator option proto-plus-dep * fix mypy * fix style * update goldens * fix tests * clean up * clean up * add support for dependency google-cloud-kms * Address review comments * add missing file * run formatting tool * style * address review comments * formatting * address review feedback * style * add pypi package google-geo-type * bump google-cloud-documentai dependency to 2.0.0 * address review comment * add docstring * address review comment * fix docs
1 parent 3e5f87c commit 412cc66

File tree

16 files changed

+432
-94
lines changed

16 files changed

+432
-94
lines changed

packages/gapic-generator/gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/rest.py.j2

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,10 @@ class {{service.name}}RestTransport({{service.name}}Transport):
322322
{% endfor %}{# rule in method.http_options #}
323323
]
324324
request, metadata = self._interceptor.pre_{{ method.name|snake_case }}(request, metadata)
325-
{% if method.input.ident.is_external_type %}
326-
pb_request = request
327-
{% else %}
325+
{% if method.input.ident.is_proto_plus_type %}
328326
pb_request = {{method.input.ident}}.pb(request)
327+
{% else %}
328+
pb_request = request
329329
{% endif %}
330330
transcoded_request = path_template.transcode(http_options, pb_request)
331331

@@ -384,10 +384,10 @@ class {{service.name}}RestTransport({{service.name}}Transport):
384384
resp = rest_streaming.ResponseIterator(response, {{method.output.ident}})
385385
{% else %}
386386
resp = {{method.output.ident}}()
387-
{% if method.output.ident.is_external_type %}
388-
pb_resp = resp
389-
{% else %}
387+
{% if method.output.ident.is_proto_plus_type %}
390388
pb_resp = {{method.output.ident}}.pb(resp)
389+
{% else %}
390+
pb_resp = resp
391391
{% endif %}
392392

393393
json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)

packages/gapic-generator/gapic/ads-templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,10 +1118,10 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide
11181118
{% endif %}{# default is str #}
11191119
{% endfor %}
11201120
request = request_type(**request_init)
1121-
{% if method.input.ident.is_external_type %}
1122-
pb_request = request
1123-
{% else %}
1121+
{% if method.input.ident.is_proto_plus_type %}
11241122
pb_request = request_type.pb(request)
1123+
{% else %}
1124+
pb_request = request
11251125
{% endif %}
11261126
jsonified_request = json.loads(json_format.MessageToJson(
11271127
pb_request,
@@ -1189,10 +1189,10 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide
11891189
with mock.patch.object(path_template, 'transcode') as transcode:
11901190
# A uri without fields and an empty body will force all the
11911191
# request fields to show up in the query_params.
1192-
{% if method.input.ident.is_external_type %}
1193-
pb_request = request
1194-
{% else %}
1192+
{% if method.input.ident.is_proto_plus_type %}
11951193
pb_request = request_type.pb(request)
1194+
{% else %}
1195+
pb_request = request
11961196
{% endif %}
11971197
transcode_result = {
11981198
'uri': 'v1/sample_method',
@@ -1212,10 +1212,10 @@ def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ide
12121212
json_return_value = json_format.MessageToJson(return_value)
12131213
{% else %}
12141214

1215-
{% if method.output.ident.is_external_type %}
1216-
pb_return_value = return_value
1217-
{% else %}
1215+
{% if method.output.ident.is_proto_plus_type %}
12181216
pb_return_value = {{ method.output.ident }}.pb(return_value)
1217+
{% else %}
1218+
pb_return_value = return_value
12191219
{% endif %}
12201220
json_return_value = json_format.MessageToJson(pb_return_value)
12211221
{% endif %}
@@ -1287,10 +1287,10 @@ def test_{{ method_name }}_rest_interceptors(null_interceptor):
12871287
{% if not method.void %}
12881288
post.assert_not_called()
12891289
{% endif %}
1290-
{% if method.input.ident.is_external_type %}
1291-
pb_message = {{ method.input.ident }}()
1292-
{% else %}
1290+
{% if method.input.ident.is_proto_plus_type %}
12931291
pb_message = {{ method.input.ident }}.pb({{ method.input.ident }}())
1292+
{% else %}
1293+
pb_message = {{ method.input.ident }}()
12941294
{% endif %}
12951295
transcode.return_value = {
12961296
"method": "post",

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

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"""
2828

2929
import dataclasses
30+
import re
3031
from typing import FrozenSet, Tuple, Optional
3132

3233
from google.protobuf import descriptor_pb2
@@ -89,10 +90,8 @@ def __str__(self) -> str:
8990
if self.module_alias:
9091
module_name = self.module_alias
9192

92-
# This module is from a different proto package
93-
# Most commonly happens for a common proto
94-
# https://pypi.org/project/googleapis-common-protos/
95-
if self.is_external_type:
93+
# Add _pb2 suffix except when it is a proto-plus type
94+
if not self.is_proto_plus_type:
9695
module_name = f'{self.module}_pb2'
9796

9897
# Return the dot-separated Python identifier.
@@ -103,8 +102,23 @@ def __str__(self) -> str:
103102
return '.'.join(self.parent + (self.name,))
104103

105104
@property
106-
def is_external_type(self):
107-
return not self.proto_package.startswith(self.api_naming.proto_package)
105+
def is_proto_plus_type(self) -> bool:
106+
"""This function is used to determine whether a given package `self.proto_package`
107+
is using proto-plus types or protobuf types. There are 2 scenarios where the package
108+
is expected to use proto-plus types:
109+
1) When `self.proto_package` starts with `self.api_naming.proto_package`, then
110+
the given package has the same namespace as the one that is being generated. It is assumed
111+
that the gapic generator always generates packages with proto-plus types.
112+
2) When `self.proto_package` is explicitly in `self.api_naming.proto_plus_deps` which is
113+
populated via the generator option `proto-plus-deps`.
114+
115+
Returns:
116+
bool: Whether the given package uses proto-plus types or not.
117+
"""
118+
return self.proto_package.startswith(self.api_naming.proto_package) or (
119+
hasattr(self.api_naming, "proto_plus_deps")
120+
and self.proto_package in self.api_naming.proto_plus_deps
121+
)
108122

109123
@cached_property
110124
def __cached_string_repr(self):
@@ -188,6 +202,29 @@ def python_import(self) -> imp.Import:
188202
alias=self.module_alias,
189203
)
190204

205+
if self.is_proto_plus_type:
206+
# We need to change the import statement to use an
207+
# underscore between the module and the version. For example,
208+
# change google.cloud.documentai.v1 to google.cloud.documentai_v1.
209+
# Check if the package name contains a version.
210+
version_regex = "^v\d[^/]*$"
211+
regex_match = re.match(version_regex, self.package[-1])
212+
213+
if regex_match and len(self.package) > 1:
214+
versioned_module = f"{self.package[-2]}_{regex_match[0]}"
215+
return imp.Import(
216+
package=self.package[:-2] +
217+
(versioned_module, 'types'),
218+
module=self.module,
219+
alias=self.module_alias,
220+
)
221+
else:
222+
return imp.Import(
223+
package=self.package + ('types',),
224+
module=self.module,
225+
alias=self.module_alias,
226+
)
227+
191228
# Return the standard import.
192229
return imp.Import(
193230
package=self.package,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Naming(abc.ABC):
4343
product_name: str = ''
4444
proto_package: str = ''
4545
_warehouse_package_name: str = ''
46+
proto_plus_deps: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
4647

4748
def __post_init__(self):
4849
if not self.product_name:
@@ -146,6 +147,11 @@ def build(
146147
package_info = dataclasses.replace(package_info,
147148
_warehouse_package_name=opts.warehouse_package_name
148149
)
150+
if opts.proto_plus_deps:
151+
package_info = dataclasses.replace(
152+
package_info,
153+
proto_plus_deps=opts.proto_plus_deps,
154+
)
149155

150156
# Done; return the naming information.
151157
return package_info

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __hash__(self):
7777
def name(self) -> str:
7878
"""Used to prevent collisions with python keywords"""
7979
name = self.field_pb.name
80-
return name + "_" if name in utils.RESERVED_NAMES and not self.meta.address.is_external_type else name
80+
return name + "_" if name in utils.RESERVED_NAMES and self.meta.address.is_proto_plus_type else name
8181

8282
@utils.cached_property
8383
def ident(self) -> metadata.FieldIdentifier:

packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,10 @@ class {{service.name}}RestTransport({{service.name}}Transport):
356356
{% endfor %}{# rule in method.http_options #}
357357
]
358358
request, metadata = self._interceptor.pre_{{ method.name|snake_case }}(request, metadata)
359-
{% if method.input.ident.is_external_type %}
360-
pb_request = request
361-
{% else %}
359+
{% if method.input.ident.is_proto_plus_type %}
362360
pb_request = {{method.input.ident}}.pb(request)
361+
{% else %}
362+
pb_request = request
363363
{% endif %}
364364
transcoded_request = path_template.transcode(http_options, pb_request)
365365

@@ -418,10 +418,10 @@ class {{service.name}}RestTransport({{service.name}}Transport):
418418
resp = rest_streaming.ResponseIterator(response, {{method.output.ident}})
419419
{% else %}
420420
resp = {{method.output.ident}}()
421-
{% if method.output.ident.is_external_type %}
422-
pb_resp = resp
423-
{% else %}
421+
{% if method.output.ident.is_proto_plus_type %}
424422
pb_resp = {{method.output.ident}}.pb(resp)
423+
{% else %}
424+
pb_resp = resp
425425
{% endif %}
426426

427427
json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!--
2+
Dict of PyPI packages available by API in the form <tuple>, <tuple>
3+
Where the first <tuple> contains the API and the second tuple contains the
4+
PyPI package name, the minimum allowed version and the maximum allowed version.
5+
6+
Note: Set the minimum version for google-cloud-documentai to 2.0.0 which has support for `barcode` in `google.cloud.documentai.types`
7+
-->
8+
{% set pypi_packages = {
9+
("google", "apps", "script", "type"): {"package_name": "google-apps-script-type", "lower_bound": "0.2.0", "upper_bound": "1.0.0dev"},
10+
("google", "geo", "type"): {"package_name": "google-geo-type", "lower_bound": "0.1.0", "upper_bound": "1.0.0dev"},
11+
("google", "cloud", "documentai", "v1"): {"package_name": "google-cloud-documentai", "lower_bound": "2.0.0", "upper_bound": "3.0.0dev"},
12+
("google", "cloud", "kms", "v1"): {"package_name": "google-cloud-kms", "lower_bound": "2.3.0", "upper_bound": "3.0.0dev"},
13+
("google", "iam", "v1"): {"package_name": "grpc-google-iam-v1", "lower_bound": "0.12.4", "upper_bound": "1.0.0dev"}
14+
}
15+
%}

packages/gapic-generator/gapic/templates/setup.py.j2

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends '_base.py.j2' %}
2-
2+
{% from '_pypi_packages.j2' import pypi_packages %}
33
{% block content %}
44

55
import io
@@ -31,13 +31,14 @@ dependencies = [
3131
"proto-plus >= 1.22.0, <2.0.0dev",
3232
"proto-plus >= 1.22.2, <2.0.0dev; python_version>='3.11'",
3333
"protobuf>=3.19.5,<5.0.0dev,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
34-
{# TODO: Remove after https://github.com/googleapis/gapic-generator-python/pull/1240 is merged. #}
35-
{% if api.requires_package(('google', 'iam', 'v1')) or opts.add_iam_methods or api.has_iam_mixin %}
36-
'grpc-google-iam-v1 >= 0.12.4, < 1.0.0dev',
34+
{% for package_tuple, package_info in pypi_packages.items() %}
35+
{# Quick check to make sure the package is different from this setup.py #}
36+
{% if api.naming.warehouse_package_name != package_info.package_name %}
37+
{% if api.requires_package(package_tuple) %}
38+
"{{ package_info.package_name }} >= {{ package_info.lower_bound }}, <{{ package_info.upper_bound }}",
3739
{% endif %}
38-
{% if api.requires_package(('google', 'cloud', 'documentai', 'v1')) %}
39-
'google-cloud-documentai >= 1.2.1, < 3.0.0dev',
4040
{% endif %}
41+
{% endfor %}
4142
]
4243
url = "https://github.com/googleapis/python-{{ api.naming.warehouse_package_name|replace("google-cloud-", "")|replace("google-", "") }}"
4344

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
{% from '_pypi_packages.j2' import pypi_packages %}
12
# This constraints file is required for unit tests.
23
# List all library dependencies and extras in this file.
34
google-api-core
45
proto-plus
56
protobuf
6-
{% if api.requires_package(('google', 'iam', 'v1')) or opts.add_iam_methods or api.has_iam_mixin %}
7-
grpc-google-iam-v1
7+
{% for package_tuple, package_info in pypi_packages.items() %}
8+
{# Quick check to make sure the package is different from this setup.py #}
9+
{% if api.naming.warehouse_package_name != package_info.package_name %}
10+
{% if api.requires_package(package_tuple) %}
11+
{{ package_info.package_name }}
812
{% endif %}
9-
{% if api.requires_package(('google', 'cloud', 'documentai', 'v1')) %}
10-
google-cloud-documentai
11-
{% endif %}
13+
{% endif %}
14+
{% endfor %}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% from '_pypi_packages.j2' import pypi_packages %}
12
# This constraints file is used to check that lower bounds
23
# are correct in setup.py
34
# List all library dependencies and extras in this file.
@@ -7,9 +8,11 @@
78
google-api-core==1.34.0
89
proto-plus==1.22.0
910
protobuf==3.19.5
10-
{% if api.requires_package(('google', 'iam', 'v1')) or opts.add_iam_methods or api.has_iam_mixin %}
11-
grpc-google-iam-v1==0.12.4
11+
{% for package_tuple, package_info in pypi_packages.items() %}
12+
{# Quick check to make sure the package is different from this setup.py #}
13+
{% if api.naming.warehouse_package_name != package_info.package_name %}
14+
{% if api.requires_package(package_tuple) %}
15+
{{ package_info.package_name }}=={{ package_info.lower_bound }}
1216
{% endif %}
13-
{% if api.requires_package(('google', 'cloud', 'documentai', 'v1')) %}
14-
google-cloud-documentai==1.2.1
1517
{% endif %}
18+
{% endfor %}

0 commit comments

Comments
 (0)