Skip to content

Commit 950e89b

Browse files
committed
wip
1 parent e403acc commit 950e89b

File tree

12 files changed

+130
-3
lines changed

12 files changed

+130
-3
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ class {{ service.client_name }}Meta(type):
9494

9595

9696
class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
97-
"""{{ service.meta.doc|rst(width=72, indent=4) }}"""
97+
"""{{ service.meta.doc|rst(width=72, indent=4) }}{% if service.version|length %}
98+
This class implements API version {{ service.version }}."""
99+
{% else %}"""
100+
{% endif %}
98101

99102
@staticmethod
100103
def _get_default_mtls_endpoint(api_endpoint):

gapic/schema/wrappers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,17 @@ def host(self) -> str:
17341734
return self.options.Extensions[client_pb2.default_host]
17351735
return ''
17361736

1737+
@property
1738+
def version(self) -> str:
1739+
"""Return the API version for this service, if specified.
1740+
1741+
Returns:
1742+
str: The API version for this service.
1743+
"""
1744+
if self.options.Extensions[client_pb2.api_version]:
1745+
return self.options.Extensions[client_pb2.api_version]
1746+
return ''
1747+
17371748
@property
17381749
def shortname(self) -> str:
17391750
"""Return the API short name. DRIFT uses this to identify

gapic/templates/%namespace/%name_%version/%sub/services/%service/_client_macros.j2

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
)
184184
{% endif %} {# method.explicit_routing #}
185185

186+
{% if service.version %}
187+
metadata = tuple(metadata) + (
188+
gapic_v1.version_header.to_grpc_metadata("{{ service.version }}"),
189+
)
190+
{% endif %}
191+
186192
{{ auto_populate_uuid4_fields(api, method) }}
187193

188194
# Validate the universe domain.

gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ from .client import {{ service.client_name }}
5454

5555
{# TODO(yon-mg): handle rest transport async client interaction #}
5656
class {{ service.async_client_name }}:
57-
"""{{ service.meta.doc|rst(width=72, indent=4) }}"""
57+
"""{{ service.meta.doc|rst(width=72, indent=4) }}{% if service.version|length %}
58+
This class implements API version {{ service.version }}."""
59+
{% else %}"""
60+
{% endif %}
5861

5962
_client: {{ service.client_name }}
6063

@@ -388,6 +391,12 @@ class {{ service.async_client_name }}:
388391
)
389392
{% endif %}
390393

394+
{% if service.version %}
395+
metadata = tuple(metadata) + (
396+
gapic_v1.version_header.to_grpc_metadata("{{ service.version }}")
397+
)
398+
{% endif %}
399+
391400
{{ macros.auto_populate_uuid4_fields(api, method) }}
392401

393402
# Validate the universe domain.

gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ class {{ service.client_name }}Meta(type):
102102

103103

104104
class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
105-
"""{{ service.meta.doc|rst(width=72, indent=4) }}"""
105+
"""{{ service.meta.doc|rst(width=72, indent=4) }}{% if service.version|length %}
106+
This class implements API version {{ service.version }}."""
107+
{% else %}"""
108+
{% endif %}
106109

107110
@staticmethod
108111
def _get_default_mtls_endpoint(api_endpoint):

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/_rest_mixins.py.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
# Jsonify the query params
6161
query_params = json.loads(json.dumps(transcoded_request['query_params']))
6262

63+
{% if service.version %}
64+
query_params["$apiVersion"] = "{{ service.version }}"
65+
{% endif %}
66+
6367
# Send the request
6468
headers = dict(metadata)
6569
headers['Content-Type'] = 'application/json'

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ class {{service.name}}RestTransport({{service.name}}Transport):
397397
query_params["$alt"] = "json;enum-encoding=int"
398398
{% endif %}
399399

400+
{% if service.version %}
401+
query_params["$apiVersion"] = "{{ service.version }}"
402+
{% endif %}
403+
400404
# Send the request
401405
headers = dict(metadata)
402406
headers['Content-Type'] = 'application/json'

gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,29 @@ def test_{{ method_name }}(request_type, transport: str = 'grpc'):
121121
{% endif %}
122122
{% endwith %}{# auto_populated_field_sample_value #}
123123

124+
{% if service.version %}
125+
{% for mode in ["", "async"] %}
126+
{% if mode == "async" %}
127+
async def test_{{ method_name }}_api_version_header_async(client={{ service.async_client_name }}()):
128+
{% else %}
129+
def test_{{ method_name }}_api_version_header(client={{ service.client_name }}()):
130+
{% endif %}
131+
# Mock the actual call within the gRPC stub, and fake the request.
132+
with mock.patch.object(
133+
type(client.transport.{{ method.transport_safe_name|snake_case }}),
134+
'__call__'
135+
) as call:
136+
{% if mode == "async" %}
137+
await client.{{ method_name }}()
138+
{% else %}
139+
client.{{ method_name }}()
140+
{% endif %}
141+
142+
# Establish that the api version header was sent.
143+
_, _, kw = call.mock_calls[0]
144+
assert kw['metadata'][0] == (gapic_v1.version_header.API_VERSION_METADATA_KEY, "{{ service.version }}")
145+
{% endfor %}{# mode #}
146+
{% endif %}{# service.version #}
124147

125148
{% if not method.client_streaming %}
126149
def test_{{ method_name }}_empty_call():

test_utils/test_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def make_service(
3232
visible_resources: typing.Optional[
3333
typing.Mapping[str, wrappers.CommonResource]
3434
] = None,
35+
version: str = "",
3536
) -> wrappers.Service:
3637
visible_resources = visible_resources or {}
3738
# Define a service descriptor, and set a host and oauth scopes if
@@ -40,6 +41,8 @@ def make_service(
4041
if host:
4142
service_pb.options.Extensions[client_pb2.default_host] = host
4243
service_pb.options.Extensions[client_pb2.oauth_scopes] = ','.join(scopes)
44+
if version:
45+
service_pb.options.Extensions[client_pb2.api_version] = version
4346

4447
# Return a service object to test.
4548
return wrappers.Service(

tests/fragments/google/api/client.proto

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,20 @@ extend google.protobuf.ServiceOptions {
9696
// ...
9797
// }
9898
string oauth_scopes = 1050;
99+
100+
// The API version of this service, which should be sent by version-aware
101+
// clients to the service. This allows services to abide by the schema and
102+
// behavior of the service at the time this API version was deployed.
103+
// The format of the API version must be treated as opaque by clients.
104+
// Services may use a format with an apparent structure, but clients must
105+
// not rely on this to determine components within an API version, or attempt
106+
// to construct other valid API versions. Note that this is for upcoming
107+
// functionality and may not be implemented for all services.
108+
//
109+
// Example:
110+
//
111+
// service Foo {
112+
// option (google.api.api_version) = "v1_20230821_preview";
113+
// }
114+
string api_version = 525000001;
99115
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (C) 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package google.fragment;
18+
19+
import "google/protobuf/struct.proto";
20+
import "google/api/client.proto";
21+
22+
service MyServiceWithVersion {
23+
option (google.api.api_version) = "v1_20230601";
24+
option (google.api.default_host) = "my.example.com";
25+
26+
rpc MyMethod(MethodRequest) returns (MethodResponse) {
27+
option (google.api.method_signature) = "parameter";
28+
}
29+
}
30+
31+
message MethodRequest {
32+
google.protobuf.Value parameter = 1;
33+
}
34+
35+
message MethodResponse {
36+
google.protobuf.Value result = 1;
37+
}

tests/unit/schema/wrappers/test_service.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ def test_service_host():
7070
service = make_service(host='thingdoer.googleapis.com')
7171
assert service.host == 'thingdoer.googleapis.com'
7272

73+
def test_service_api_version_not_specified():
74+
service = make_service(host='thingdoer.googleapis.com')
75+
assert not service.version
76+
77+
def test_service_api_version_exists():
78+
service = make_service(host='thingdoer.googleapis.com', version="goose")
79+
assert service.version == "goose"
80+
7381

7482
def test_service_no_host():
7583
service = make_service()

0 commit comments

Comments
 (0)