Skip to content

Commit 6f07478

Browse files
committed
fix(generator): use unified _compat.py.j2
1 parent 44e43ce commit 6f07478

1 file changed

Lines changed: 227 additions & 0 deletions

File tree

  • packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub

packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/_compat.py.j2

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,144 @@
22

33
"""A compatibility module for older versions of google-api-core."""
44

5+
import functools
6+
import json
7+
import operator
8+
import os
9+
import re
510
import uuid
11+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
12+
from google.auth.exceptions import MutualTLSChannelError
13+
import google.protobuf.message
14+
15+
16+
try:
17+
from google.api_core.universe import (
18+
get_default_mtls_endpoint,
19+
get_api_endpoint,
20+
get_universe_domain,
21+
)
22+
except ImportError:
23+
# 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.
24+
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
25+
"""Converts api endpoint to mTLS endpoint."""
26+
if not api_endpoint:
27+
return api_endpoint
28+
29+
mtls_endpoint_re = re.compile(
30+
r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
31+
)
32+
33+
m = mtls_endpoint_re.match(api_endpoint)
34+
if m is None:
35+
# Could not parse api_endpoint; return as-is.
36+
return api_endpoint
37+
38+
name, mtls, sandbox, googledomain = m.groups()
39+
if mtls or not googledomain:
40+
return api_endpoint
41+
42+
if sandbox:
43+
return api_endpoint.replace(
44+
"sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
45+
)
46+
47+
return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
48+
49+
def get_api_endpoint(
50+
api_override: Optional[str],
51+
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
52+
universe_domain: str,
53+
use_mtls_endpoint: str,
54+
default_universe: str,
55+
default_mtls_endpoint: Optional[str],
56+
default_endpoint_template: str,
57+
) -> Optional[str]:
58+
"""Return the API endpoint used by the client."""
59+
if api_override is not None:
60+
api_endpoint = api_override
61+
elif use_mtls_endpoint == "always" or (use_mtls_endpoint == "auto" and client_cert_source):
62+
if universe_domain != default_universe:
63+
raise MutualTLSChannelError(
64+
f"mTLS is not supported in any universe other than {default_universe}."
65+
)
66+
api_endpoint = default_mtls_endpoint
67+
else:
68+
api_endpoint = default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)
69+
return api_endpoint
70+
71+
def get_universe_domain(
72+
client_universe_domain: Optional[str],
73+
universe_domain_env: Optional[str],
74+
default_universe: str,
75+
) -> str:
76+
"""Return the universe domain used by the client."""
77+
universe_domain = default_universe
78+
if client_universe_domain is not None:
79+
universe_domain = client_universe_domain
80+
elif universe_domain_env is not None:
81+
universe_domain = universe_domain_env
82+
if len(universe_domain.strip()) == 0:
83+
raise ValueError("Universe Domain cannot be an empty string.")
84+
return universe_domain
85+
86+
87+
try:
88+
from google.api_core.gapic_v1.config import (
89+
use_client_cert_effective,
90+
get_client_cert_source,
91+
read_environment_variables,
92+
)
93+
except ImportError:
94+
from google.auth.transport import mtls # type: ignore
95+
96+
# 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.
97+
98+
def use_client_cert_effective() -> bool:
99+
"""Returns whether client certificate should be used for mTLS."""
100+
if hasattr(mtls, "should_use_client_cert"):
101+
return mtls.should_use_client_cert()
102+
else:
103+
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
104+
if use_client_cert_str not in ("true", "false"):
105+
raise ValueError(
106+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
107+
" either `true` or `false`"
108+
)
109+
return use_client_cert_str == "true"
110+
111+
def get_client_cert_source(
112+
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
113+
use_cert_flag: bool,
114+
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
115+
"""Return the client cert source to be used by the client."""
116+
client_cert_source = None
117+
if use_cert_flag:
118+
if provided_cert_source:
119+
client_cert_source = provided_cert_source
120+
elif (
121+
hasattr(mtls, "has_default_client_cert_source")
122+
and mtls.has_default_client_cert_source()
123+
):
124+
client_cert_source = mtls.default_client_cert_source()
125+
else:
126+
raise ValueError(
127+
"Client certificate is required for mTLS, but no client certificate source was provided or found."
128+
)
129+
return client_cert_source
130+
131+
def read_environment_variables() -> Tuple[bool, str, Optional[str]]:
132+
"""Returns the environment variables used by the client."""
133+
use_client_cert = use_client_cert_effective()
134+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
135+
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
136+
if use_mtls_endpoint not in ("auto", "never", "always"):
137+
raise MutualTLSChannelError(
138+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
139+
"must be `never`, `auto` or `always`"
140+
)
141+
return use_client_cert, use_mtls_endpoint, universe_domain_env
142+
6143

7144
try:
8145
from google.api_core.gapic_v1.request import setup_request_id # type: ignore
@@ -41,3 +178,93 @@ except ImportError:
41178
if not getattr(request, field_name, None):
42179
setattr(request, field_name, request_id_val)
43180

181+
182+
try:
183+
from google.api_core.rest_helpers import (
184+
flatten_query_params,
185+
transcode_request,
186+
)
187+
except ImportError: # pragma: NO COVER
188+
# TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
189+
from google.protobuf import json_format # type: ignore
190+
from google.api_core import path_template # type: ignore
191+
192+
def flatten_query_params(obj, strict=False): # pragma: NO COVER
193+
if obj is not None and not isinstance(obj, dict):
194+
raise TypeError("flatten_query_params must be called with dict object")
195+
return _flatten(obj, key_path=[], strict=strict)
196+
197+
def _flatten(obj, key_path, strict=False): # pragma: NO COVER
198+
if obj is None:
199+
return []
200+
if isinstance(obj, dict):
201+
return _flatten_dict(obj, key_path=key_path, strict=strict)
202+
if isinstance(obj, list):
203+
return _flatten_list(obj, key_path=key_path, strict=strict)
204+
return _flatten_value(obj, key_path=key_path, strict=strict)
205+
206+
def _is_primitive_value(obj): # pragma: NO COVER
207+
if obj is None:
208+
return False
209+
if isinstance(obj, (list, dict)):
210+
raise ValueError("query params may not contain repeated dicts or lists")
211+
return True
212+
213+
def _flatten_value(obj, key_path, strict=False): # pragma: NO COVER
214+
return [(".".join(key_path), _canonicalize(obj, strict=strict))]
215+
216+
def _flatten_dict(obj, key_path, strict=False): # pragma: NO COVER
217+
items = (
218+
_flatten(value, key_path=key_path + [key], strict=strict)
219+
for key, value in obj.items()
220+
)
221+
return functools.reduce(operator.concat, items, [])
222+
223+
def _flatten_list(elems, key_path, strict=False): # pragma: NO COVER
224+
items = (
225+
_flatten_value(elem, key_path=key_path, strict=strict)
226+
for elem in elems
227+
if _is_primitive_value(elem)
228+
)
229+
return functools.reduce(operator.concat, items, [])
230+
231+
def _canonicalize(obj, strict=False): # pragma: NO COVER
232+
if strict:
233+
value = str(obj)
234+
if isinstance(obj, bool):
235+
value = value.lower()
236+
return value
237+
return obj
238+
239+
def transcode_request( # pragma: NO COVER
240+
http_options: List[Dict[str, str]],
241+
request: Any,
242+
required_fields_default_values: Optional[Dict[str, Any]] = None,
243+
rest_numeric_enums: bool = False,
244+
) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]:
245+
pb_request = getattr(request, "_pb", request)
246+
transcoded_request = path_template.transcode(http_options, pb_request)
247+
248+
body_json = None
249+
if transcoded_request.get("body") is not None:
250+
body_json = json_format.MessageToJson(
251+
transcoded_request["body"],
252+
use_integers_for_enums=rest_numeric_enums,
253+
)
254+
255+
query_params_json = {}
256+
if transcoded_request.get("query_params") is not None:
257+
query_params_json = json.loads(json_format.MessageToJson(
258+
transcoded_request["query_params"],
259+
use_integers_for_enums=rest_numeric_enums,
260+
))
261+
262+
if required_fields_default_values:
263+
for k, v in required_fields_default_values.items():
264+
if k not in query_params_json:
265+
query_params_json[k] = v
266+
267+
if rest_numeric_enums:
268+
query_params_json["$alt"] = "json;enum-encoding=int"
269+
270+
return transcoded_request, body_json, query_params_json

0 commit comments

Comments
 (0)