Skip to content

Commit 34f7c43

Browse files
committed
feat(templates): add generated _compat.py across integration test goldens
1 parent 9db6171 commit 34f7c43

8 files changed

Lines changed: 2520 additions & 0 deletions

File tree

  • packages/gapic-generator/tests/integration/goldens
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# # Copyright 2026 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+
16+
"""A compatibility module for older versions of google-api-core."""
17+
18+
import functools
19+
import json
20+
import operator
21+
import os
22+
import re
23+
import uuid
24+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
25+
from google.auth.exceptions import MutualTLSChannelError
26+
import google.protobuf.message
27+
28+
29+
try:
30+
from google.api_core.universe import (
31+
get_default_mtls_endpoint,
32+
get_api_endpoint,
33+
get_universe_domain,
34+
)
35+
except ImportError:
36+
# TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
37+
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
38+
"""Converts api endpoint to mTLS endpoint."""
39+
if not api_endpoint:
40+
return api_endpoint
41+
42+
mtls_endpoint_re = re.compile(
43+
r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
44+
)
45+
46+
m = mtls_endpoint_re.match(api_endpoint)
47+
if m is None:
48+
# Could not parse api_endpoint; return as-is.
49+
return api_endpoint
50+
51+
name, mtls, sandbox, googledomain = m.groups()
52+
if mtls or not googledomain:
53+
return api_endpoint
54+
55+
if sandbox:
56+
return api_endpoint.replace(
57+
"sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
58+
)
59+
60+
return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
61+
62+
def get_api_endpoint(
63+
api_override: Optional[str],
64+
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
65+
universe_domain: str,
66+
use_mtls_endpoint: str,
67+
default_universe: str,
68+
default_mtls_endpoint: Optional[str],
69+
default_endpoint_template: str,
70+
) -> Optional[str]:
71+
"""Return the API endpoint used by the client."""
72+
if api_override is not None:
73+
api_endpoint = api_override
74+
elif use_mtls_endpoint == "always" or (use_mtls_endpoint == "auto" and client_cert_source):
75+
if universe_domain != default_universe:
76+
raise MutualTLSChannelError(
77+
f"mTLS is not supported in any universe other than {default_universe}."
78+
)
79+
api_endpoint = default_mtls_endpoint
80+
else:
81+
api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)
82+
return api_endpoint
83+
84+
def get_universe_domain(
85+
client_universe_domain: Optional[str],
86+
universe_domain_env: Optional[str],
87+
default_universe: str,
88+
) -> str:
89+
"""Return the universe domain used by the client."""
90+
universe_domain = default_universe
91+
if client_universe_domain is not None:
92+
universe_domain = client_universe_domain
93+
elif universe_domain_env is not None:
94+
universe_domain = universe_domain_env
95+
if len(universe_domain.strip()) == 0:
96+
raise ValueError("Universe Domain cannot be an empty string.")
97+
return universe_domain
98+
99+
100+
try:
101+
from google.api_core.gapic_v1.config import (
102+
use_client_cert_effective,
103+
get_client_cert_source,
104+
read_environment_variables,
105+
)
106+
except ImportError:
107+
from google.auth.transport import mtls # type: ignore
108+
109+
# TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
110+
111+
def use_client_cert_effective() -> bool:
112+
"""Returns whether client certificate should be used for mTLS."""
113+
if hasattr(mtls, "should_use_client_cert"):
114+
return mtls.should_use_client_cert()
115+
else:
116+
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
117+
if use_client_cert_str not in ("true", "false"):
118+
raise ValueError(
119+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
120+
" either `true` or `false`"
121+
)
122+
return use_client_cert_str == "true"
123+
124+
def get_client_cert_source(
125+
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
126+
use_cert_flag: bool,
127+
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
128+
"""Return the client cert source to be used by the client."""
129+
client_cert_source = None
130+
if use_cert_flag:
131+
if provided_cert_source:
132+
client_cert_source = provided_cert_source
133+
elif (
134+
hasattr(mtls, "has_default_client_cert_source")
135+
and mtls.has_default_client_cert_source()
136+
):
137+
client_cert_source = mtls.default_client_cert_source()
138+
else:
139+
raise ValueError(
140+
"Client certificate is required for mTLS, but no client certificate source was provided or found."
141+
)
142+
return client_cert_source
143+
144+
def read_environment_variables() -> Tuple[bool, str, Optional[str]]:
145+
"""Returns the environment variables used by the client."""
146+
use_client_cert = use_client_cert_effective()
147+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
148+
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
149+
if use_mtls_endpoint not in ("auto", "never", "always"):
150+
raise MutualTLSChannelError(
151+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
152+
"must be `never`, `auto` or `always`"
153+
)
154+
return use_client_cert, use_mtls_endpoint, universe_domain_env
155+
156+
157+
try:
158+
from google.api_core.gapic_v1.request import setup_request_id # type: ignore
159+
except ImportError:
160+
# TODO(https://github.com/googleapis/google-cloud-python/issues/17813): Remove this fallback when google-api-core >= 2.26.0 is the minimum required version.
161+
def setup_request_id(request, field_name: str, is_proto3_optional: bool):
162+
"""Populate a UUID4 field in the request if it is not already set.
163+
164+
Args:
165+
request (Union[google.protobuf.message.Message, dict]): The request object.
166+
field_name (str): The name of the field to populate.
167+
is_proto3_optional (bool): Whether the field is proto3 optional.
168+
"""
169+
request_id_val = str(uuid.uuid4())
170+
if request is None:
171+
return
172+
173+
if isinstance(request, dict):
174+
if is_proto3_optional:
175+
if field_name not in request or request[field_name] is None:
176+
request[field_name] = request_id_val
177+
elif not request.get(field_name):
178+
request[field_name] = request_id_val
179+
return
180+
181+
if is_proto3_optional:
182+
try:
183+
# Pure protobuf messages
184+
if not request.HasField(field_name):
185+
setattr(request, field_name, request_id_val)
186+
except (AttributeError, ValueError):
187+
# Proto-plus messages or other objects
188+
if getattr(request, field_name, None) is None:
189+
setattr(request, field_name, request_id_val)
190+
else:
191+
if not getattr(request, field_name, None):
192+
setattr(request, field_name, request_id_val)
193+
194+
195+
try:
196+
from google.api_core.rest_helpers import ( # type: ignore
197+
flatten_query_params,
198+
transcode_request as _core_transcode_request,
199+
)
200+
import inspect
201+
if "rest_numeric_enums" not in inspect.signature(_core_transcode_request).parameters:
202+
raise ImportError
203+
transcode_request = _core_transcode_request
204+
except (ImportError, AttributeError): # pragma: NO COVER
205+
# TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
206+
from google.protobuf import json_format # type: ignore
207+
from google.api_core import path_template # type: ignore
208+
209+
def flatten_query_params(obj, strict=False): # pragma: NO COVER
210+
if obj is not None and not isinstance(obj, dict):
211+
raise TypeError("flatten_query_params must be called with dict object")
212+
return _flatten(obj, key_path=[], strict=strict)
213+
214+
def _flatten(obj, key_path, strict=False): # pragma: NO COVER
215+
if obj is None:
216+
return []
217+
if isinstance(obj, dict):
218+
return _flatten_dict(obj, key_path=key_path, strict=strict)
219+
if isinstance(obj, list):
220+
return _flatten_list(obj, key_path=key_path, strict=strict)
221+
return _flatten_value(obj, key_path=key_path, strict=strict)
222+
223+
def _is_primitive_value(obj): # pragma: NO COVER
224+
if obj is None:
225+
return False
226+
if isinstance(obj, (list, dict)):
227+
raise ValueError("query params may not contain repeated dicts or lists")
228+
return True
229+
230+
def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER
231+
return [(".".join(key_path), _canonicalize(obj, strict=strict))]
232+
233+
def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER
234+
items = (
235+
_flatten(value, key_path=key_path + [key], strict=strict)
236+
for key, value in obj.items()
237+
)
238+
return functools.reduce(operator.concat, items, []) # type: ignore
239+
240+
def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER
241+
items = (
242+
_flatten_value(elem, key_path=key_path, strict=strict)
243+
for elem in elems
244+
if _is_primitive_value(elem)
245+
)
246+
return functools.reduce(operator.concat, items, []) # type: ignore
247+
248+
def _canonicalize(obj, strict=False): # pragma: NO COVER
249+
if strict:
250+
value = str(obj)
251+
if isinstance(obj, bool):
252+
value = value.lower()
253+
return value
254+
return obj
255+
256+
def transcode_request( # pragma: NO COVER
257+
http_options: List[Dict[str, str]],
258+
request: Any,
259+
required_fields_default_values: Optional[Dict[str, Any]] = None,
260+
rest_numeric_enums: bool = False,
261+
) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]:
262+
pb_request = getattr(request, "_pb", request)
263+
transcoded_request = path_template.transcode(http_options, pb_request)
264+
265+
body_json = None
266+
if transcoded_request.get("body") is not None:
267+
body_json = json_format.MessageToJson(
268+
transcoded_request["body"],
269+
use_integers_for_enums=rest_numeric_enums,
270+
)
271+
272+
query_params_json = {}
273+
if transcoded_request.get("query_params") is not None:
274+
query_params_json = json.loads(
275+
json_format.MessageToJson(
276+
transcoded_request["query_params"],
277+
use_integers_for_enums=rest_numeric_enums,
278+
)
279+
)
280+
281+
if required_fields_default_values:
282+
matched_option = None
283+
for option in http_options:
284+
if option.get("method", "").lower() == transcoded_request.get("method", "").lower():
285+
if path_template.validate(
286+
option.get("uri", ""), transcoded_request.get("uri", "")
287+
):
288+
matched_option = option
289+
break
290+
291+
bound_fields = set()
292+
if matched_option:
293+
uri_template = matched_option.get("uri", "")
294+
for m in path_template._VARIABLE_RE.finditer(uri_template):
295+
name = m.group("name")
296+
if name:
297+
bound_fields.add(name.split(".")[0])
298+
body_param = matched_option.get("body")
299+
if body_param:
300+
if body_param == "*":
301+
bound_fields = None
302+
else:
303+
bound_fields.add(body_param.split(".")[0])
304+
305+
if bound_fields is not None:
306+
for k, v in required_fields_default_values.items():
307+
if k in bound_fields:
308+
continue
309+
if k not in query_params_json:
310+
query_params_json[k] = v
311+
312+
if rest_numeric_enums:
313+
query_params_json["$alt"] = "json;enum-encoding=int"
314+
315+
return transcoded_request, body_json, query_params_json

0 commit comments

Comments
 (0)