Skip to content

Commit d2ab9da

Browse files
authored
feat: port REST transport to Ads templates (#1003)
Add REST transport to Ads templates Add REST transport tests to Ads generated unit tests Boost google-api-core dependency version Rewrite of REST transport call stubs to assist testing Add fragment tests for Ads Multiple bugfixes
1 parent cef667c commit d2ab9da

File tree

42 files changed

+3552
-1146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3552
-1146
lines changed

.github/sync-repo-settings.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ branchProtectionRules:
2222
- 'showcase-unit (3.9)'
2323
- 'showcase-unit (3.9, _alternative_templates)'
2424
- 'showcase-unit-add-iam-methods'
25+
- 'integration'
2526
- 'style-check'
2627
- 'unit (3.6)'
2728
- 'unit (3.7)'
2829
- 'unit (3.8)'
2930
- 'unit (3.9)'
31+
- 'fragment (3.6)'
32+
- 'fragment (3.7)'
33+
- 'fragment (3.8)'
34+
- 'fragment (3.9)'
35+
- 'fragment (3.7, _alternative_templates)'
36+
- 'fragment (3.8, _alternative_templates)'
37+
- 'fragment (3.9, _alternative_templates)'
3038
requiredApprovingReviewCount: 1
3139
requiresCodeOwnerReviews: true
3240
requiresStrictStatusChecks: true

.github/workflows/tests.yaml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
./gapic-showcase run &
7575
cd -
7676
env:
77-
SHOWCASE_VERSION: 0.16.0
77+
SHOWCASE_VERSION: 0.18.0
7878
- name: Install nox.
7979
run: python -m pip install nox
8080
- name: Install protoc 3.12.1.
@@ -144,7 +144,7 @@ jobs:
144144
cd ..
145145
nox -s ${{ matrix.target }}
146146
env:
147-
SHOWCASE_VERSION: 0.16.0
147+
SHOWCASE_VERSION: 0.18.0
148148
# TODO(yon-mg): add compute unit tests
149149
showcase-unit:
150150
strategy:
@@ -183,7 +183,7 @@ jobs:
183183
- name: Run unit tests.
184184
run: nox -s showcase_unit${{ matrix.variant }}-${{ matrix.python }}
185185
env:
186-
SHOWCASE_VERSION: 0.16.0
186+
SHOWCASE_VERSION: 0.18.0
187187
showcase-unit-add-iam-methods:
188188
runs-on: ubuntu-latest
189189
steps:
@@ -214,7 +214,7 @@ jobs:
214214
- name: Run unit tests.
215215
run: nox -s showcase_unit_add_iam_methods
216216
env:
217-
SHOWCASE_VERSION: 0.16.0
217+
SHOWCASE_VERSION: 0.18.0
218218
showcase-mypy:
219219
runs-on: ubuntu-latest
220220
strategy:
@@ -248,7 +248,7 @@ jobs:
248248
- name: Typecheck the generated output.
249249
run: nox -s showcase_mypy${{ matrix.variant }}
250250
env:
251-
SHOWCASE_VERSION: 0.16.0
251+
SHOWCASE_VERSION: 0.18.0
252252
snippetgen:
253253
runs-on: ubuntu-latest
254254
steps:
@@ -299,6 +299,10 @@ jobs:
299299
strategy:
300300
matrix:
301301
python: [3.6, 3.7, 3.8, 3.9]
302+
variant: ['', _alternative_templates]
303+
exclude:
304+
- python: 3.6
305+
variant: _alternative_templates
302306
runs-on: ubuntu-latest
303307
steps:
304308
- name: Cancel Previous Runs
@@ -319,7 +323,7 @@ jobs:
319323
run: |
320324
python -m pip install nox
321325
- name: Run fragment tests.
322-
run: nox -s fragment-${{ matrix.python }}
326+
run: nox -s fragment${{ matrix.variant }}-${{ matrix.python }}
323327
integration:
324328
runs-on: ubuntu-latest
325329
container: gcr.io/gapic-images/googleapis-bazel:20210105

gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2

Lines changed: 376 additions & 55 deletions
Large diffs are not rendered by default.

gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/pagers.py.j2

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{# This lives within the loop in order to ensure that this template
88
is empty if there are no paged methods.
99
-#}
10-
from typing import Any, Callable, Iterable, Sequence, Tuple
10+
from typing import Any, Callable, Iterable, Sequence, Tuple, Optional, Iterator
1111

1212
{% filter sort_lines %}
1313
{% for method in service.methods.values() | selectattr('paged_result_field') %}
@@ -70,9 +70,18 @@ class {{ method.name }}Pager:
7070
self._response = self._method(self._request, metadata=self._metadata)
7171
yield self._response
7272

73-
def __iter__(self) -> {{ method.paged_result_field.ident | replace('Sequence', 'Iterable') }}:
73+
{% if method.paged_result_field.map %}
74+
def __iter__(self) -> Iterator[Tuple[str, {{ method.paged_result_field.type.fields.get('value').ident }}]]:
75+
for page in self.pages:
76+
yield from page.{{ method.paged_result_field.name}}.items()
77+
78+
def get(self, key: str) -> Optional[{{ method.paged_result_field.type.fields.get('value').ident }}]:
79+
return self._response.{{ method.paged_result_field.name }}.get(key)
80+
{% else %}
81+
def __iter__(self) -> {{ method.paged_result_field.ident | replace('Sequence', 'Iterator') }}:
7482
for page in self.pages:
7583
yield from page.{{ method.paged_result_field.name }}
84+
{% endif %}
7685

7786
def __repr__(self) -> str:
7887
return '{0}<{1!r}>'.format(self.__class__.__name__, self._response)

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,29 @@ from collections import OrderedDict
66
from typing import Dict, Type
77

88
from .base import {{ service.name }}Transport
9+
{% if 'grpc' in opts.transport %}
910
from .grpc import {{ service.name }}GrpcTransport
10-
11+
{% endif %}
12+
{% if 'rest' in opts.transport %}
13+
from .rest import {{ service.name }}RestTransport
14+
{% endif %}
1115

1216
# Compile a registry of transports.
1317
_transport_registry = OrderedDict() # type: Dict[str, Type[{{ service.name }}Transport]]
18+
{% if 'grpc' in opts.transport %}
1419
_transport_registry['grpc'] = {{ service.name }}GrpcTransport
15-
20+
{% endif %}
21+
{% if 'rest' in opts.transport %}
22+
_transport_registry['rest'] = {{ service.name }}RestTransport
23+
{% endif %}
1624

1725
__all__ = (
1826
'{{ service.name }}Transport',
27+
{% if 'grpc' in opts.transport %}
1928
'{{ service.name }}GrpcTransport',
29+
{% endif %}
30+
{% if 'rest' in opts.transport %}
31+
'{{ service.name }}RestTransport',
32+
{% endif %}
2033
)
2134
{% endblock %}

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

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,29 @@
33
{% block content %}
44

55
import abc
6-
import typing
6+
from typing import Awaitable, Callable, Dict, Optional, Sequence, Union
77
import pkg_resources
88

99
import google.auth # type: ignore
10-
from google.api_core import gapic_v1
11-
from google.api_core import retry as retries
10+
import google.api_core # type: ignore
11+
from google.api_core import exceptions as core_exceptions # type: ignore
12+
from google.api_core import gapic_v1 # type: ignore
13+
from google.api_core import retry as retries # type: ignore
1214
{% if service.has_lro %}
1315
from google.api_core import operations_v1
1416
{% endif %}
1517
from google.auth import credentials as ga_credentials # type: ignore
18+
from google.oauth2 import service_account # type: ignore
1619

1720
{% filter sort_lines %}
1821
{% for method in service.methods.values() %}
1922
{{ method.input.ident.python_import }}
2023
{{ method.output.ident.python_import }}
2124
{% endfor %}
25+
{% if opts.add_iam_methods %}
26+
from google.iam.v1 import iam_policy_pb2 # type: ignore
27+
from google.iam.v1 import policy_pb2 # type: ignore
28+
{% endif %}
2229
{% endfilter %}
2330

2431
try:
@@ -31,7 +38,7 @@ except pkg_resources.DistributionNotFound:
3138
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
3239

3340

34-
class {{ service.name }}Transport(metaclass=abc.ABCMeta):
41+
class {{ service.name }}Transport(abc.ABC):
3542
"""Abstract transport class for {{ service.name }}."""
3643

3744
AUTH_SCOPES = (
@@ -40,11 +47,18 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
4047
{% endfor %}
4148
)
4249

50+
DEFAULT_HOST: str = {% if service.host %}'{{ service.host }}'{% else %}{{ '' }}{% endif %}
51+
4352
def __init__(
4453
self, *,
45-
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
54+
host: str = DEFAULT_HOST,
4655
credentials: ga_credentials.Credentials = None,
56+
credentials_file: Optional[str] = None,
57+
scopes: Optional[Sequence[str]] = None,
58+
quota_project_id: Optional[str] = None,
4759
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
60+
always_use_jwt_access: Optional[bool] = False,
61+
**kwargs,
4862
) -> None:
4963
"""Instantiate the transport.
5064

@@ -56,30 +70,54 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
5670
credentials identify the application to the service; if none
5771
are specified, the client will attempt to ascertain the
5872
credentials from the environment.
73+
credentials_file (Optional[str]): A file with credentials that can
74+
be loaded with :func:`google.auth.load_credentials_from_file`.
75+
This argument is mutually exclusive with credentials.
76+
scopes (Optional[Sequence[str]]): A list of scopes.
77+
quota_project_id (Optional[str]): An optional project to use for billing
78+
and quota.
5979
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
6080
The client info used to send a user-agent string along with
6181
API requests. If ``None``, then default info will be used.
6282
Generally, you only need to set this if you're developing
6383
your own client library.
84+
always_use_jwt_access (Optional[bool]): Whether self signed JWT should
85+
be used for service account credentials.
6486
"""
6587
# Save the hostname. Default to port 443 (HTTPS) if none is specified.
6688
if ':' not in host:
6789
host += ':443'
6890
self._host = host
6991

92+
scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES}
93+
94+
# Save the scopes.
95+
self._scopes = scopes
96+
7097
# If no credentials are provided, then determine the appropriate
7198
# defaults.
72-
if credentials is None:
73-
credentials, _ = google.auth.default(scopes=self.AUTH_SCOPES)
99+
if credentials and credentials_file:
100+
raise core_exceptions.DuplicateCredentialArgs("'credentials_file' and 'credentials' are mutually exclusive")
101+
102+
if credentials_file is not None:
103+
credentials, _ = google.auth.load_credentials_from_file(
104+
credentials_file,
105+
**scopes_kwargs,
106+
quota_project_id=quota_project_id
107+
)
108+
elif credentials is None:
109+
credentials, _ = google.auth.default(**scopes_kwargs, quota_project_id=quota_project_id)
110+
111+
# If the credentials are service account credentials, then always try to use self signed JWT.
112+
if always_use_jwt_access and isinstance(credentials, service_account.Credentials) and hasattr(service_account.Credentials, "with_always_use_jwt_access"):
113+
credentials = credentials.with_always_use_jwt_access(True)
74114

75115
# Save the credentials.
76116
self._credentials = credentials
77117

78-
# Lifted into its own function so it can be stubbed out during tests.
79-
self._prep_wrapped_messages(client_info)
80118

81119
def _prep_wrapped_messages(self, client_info):
82-
# Precomputed wrapped methods
120+
# Precompute the wrapped methods.
83121
self._wrapped_methods = {
84122
{% for method in service.methods.values() %}
85123
self.{{ method.name|snake_case }}: gapic_v1.method.wrap_method(
@@ -106,7 +144,7 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
106144
def close(self):
107145
"""Closes resources associated with the transport.
108146

109-
.. warning::
147+
.. warning::
110148
Only call this method if the transport is NOT shared
111149
with other clients - this may cause errors in other clients!
112150
"""
@@ -117,18 +155,53 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
117155
@property
118156
def operations_client(self):
119157
"""Return the client designed to process long-running operations."""
120-
raise NotImplementedError
158+
raise NotImplementedError()
121159
{% endif %}
122160
{% for method in service.methods.values() %}
123161

124162
@property
125-
def {{ method.name|snake_case }}(self) -> typing.Callable[
163+
def {{ method.name|snake_case }}(self) -> Callable[
126164
[{{ method.input.ident }}],
127-
{{ method.output.ident }}]:
128-
raise NotImplementedError
165+
Union[
166+
{{ method.output.ident }},
167+
Awaitable[{{ method.output.ident }}]
168+
]]:
169+
raise NotImplementedError()
129170
{% endfor %}
130171

131172

173+
{% if opts.add_iam_methods %}
174+
@property
175+
def set_iam_policy(
176+
self,
177+
) -> Callable[
178+
[iam_policy_pb2.SetIamPolicyRequest],
179+
Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
180+
]:
181+
raise NotImplementedError()
182+
183+
@property
184+
def get_iam_policy(
185+
self,
186+
) -> Callable[
187+
[iam_policy_pb2.GetIamPolicyRequest],
188+
Union[policy_pb2.Policy, Awaitable[policy_pb2.Policy]],
189+
]:
190+
raise NotImplementedError()
191+
192+
@property
193+
def test_iam_permissions(
194+
self,
195+
) -> Callable[
196+
[iam_policy_pb2.TestIamPermissionsRequest],
197+
Union[
198+
iam_policy_pb2.TestIamPermissionsResponse,
199+
Awaitable[iam_policy_pb2.TestIamPermissionsResponse],
200+
],
201+
]:
202+
raise NotImplementedError()
203+
{% endif %}
204+
132205
__all__ = (
133206
'{{ service.name }}Transport',
134207
)

0 commit comments

Comments
 (0)