Skip to content

Commit b8ed069

Browse files
committed
fix(generator): use unified _compat.py.j2 and remove downstream tests
1 parent e6a35db commit b8ed069

26 files changed

Lines changed: 1463 additions & 342 deletions

File tree

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

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# {% include '_license.j2' %}
22

3+
"""A compatibility module for older versions of google-api-core."""
4+
5+
import os
36
import re
7+
import uuid
48
from typing import Optional, Callable, Tuple, Union
59
from google.auth.exceptions import MutualTLSChannelError
610

@@ -11,8 +15,7 @@ try:
1115
get_universe_domain,
1216
)
1317
except ImportError:
14-
# TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
15-
18+
# 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.
1619
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
1720
"""Converts api endpoint to mTLS endpoint."""
1821
if not api_endpoint:
@@ -74,3 +77,98 @@ except ImportError:
7477
if len(universe_domain.strip()) == 0:
7578
raise ValueError("Universe Domain cannot be an empty string.")
7679
return universe_domain
80+
81+
82+
try:
83+
from google.api_core.gapic_v1.config import (
84+
use_client_cert_effective,
85+
get_client_cert_source,
86+
read_environment_variables,
87+
)
88+
except ImportError:
89+
from google.auth.transport import mtls # type: ignore
90+
91+
# 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.
92+
93+
def use_client_cert_effective() -> bool:
94+
"""Returns whether client certificate should be used for mTLS."""
95+
if hasattr(mtls, "should_use_client_cert"):
96+
return mtls.should_use_client_cert()
97+
else:
98+
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
99+
if use_client_cert_str not in ("true", "false"):
100+
raise ValueError(
101+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
102+
" either `true` or `false`"
103+
)
104+
return use_client_cert_str == "true"
105+
106+
def get_client_cert_source(
107+
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
108+
use_cert_flag: bool,
109+
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
110+
"""Return the client cert source to be used by the client."""
111+
client_cert_source = None
112+
if use_cert_flag:
113+
if provided_cert_source:
114+
client_cert_source = provided_cert_source
115+
elif (
116+
hasattr(mtls, "has_default_client_cert_source")
117+
and mtls.has_default_client_cert_source()
118+
):
119+
client_cert_source = mtls.default_client_cert_source()
120+
else:
121+
raise ValueError(
122+
"Client certificate is required for mTLS, but no client certificate source was provided or found."
123+
)
124+
return client_cert_source
125+
126+
def read_environment_variables() -> Tuple[bool, str, Optional[str]]:
127+
"""Returns the environment variables used by the client."""
128+
use_client_cert = use_client_cert_effective()
129+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
130+
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
131+
if use_mtls_endpoint not in ("auto", "never", "always"):
132+
raise MutualTLSChannelError(
133+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
134+
"must be `never`, `auto` or `always`"
135+
)
136+
return use_client_cert, use_mtls_endpoint, universe_domain_env
137+
138+
139+
try:
140+
from google.api_core.gapic_v1.request import setup_request_id # type: ignore
141+
except ImportError:
142+
# 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.
143+
def setup_request_id(request, field_name: str, is_proto3_optional: bool):
144+
"""Populate a UUID4 field in the request if it is not already set.
145+
146+
Args:
147+
request (Union[google.protobuf.message.Message, dict]): The request object.
148+
field_name (str): The name of the field to populate.
149+
is_proto3_optional (bool): Whether the field is proto3 optional.
150+
"""
151+
request_id_val = str(uuid.uuid4())
152+
if request is None:
153+
return
154+
155+
if isinstance(request, dict):
156+
if is_proto3_optional:
157+
if field_name not in request or request[field_name] is None:
158+
request[field_name] = request_id_val
159+
elif not request.get(field_name):
160+
request[field_name] = request_id_val
161+
return
162+
163+
if is_proto3_optional:
164+
try:
165+
# Pure protobuf messages
166+
if not request.HasField(field_name):
167+
setattr(request, field_name, request_id_val)
168+
except (AttributeError, ValueError):
169+
# Proto-plus messages or other objects
170+
if getattr(request, field_name, None) is None:
171+
setattr(request, field_name, request_id_val)
172+
else:
173+
if not getattr(request, field_name, None):
174+
setattr(request, field_name, request_id_val)

packages/gapic-generator/tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from google.api_core import client_options as client_options_lib
2828
from google.api_core import exceptions as core_exceptions
2929
from google.api_core import gapic_v1
30-
from google.api_core.gapic_v1 import client_utils
3130
from google.api_core import retry as retries
3231
from google.auth import credentials as ga_credentials # type: ignore
3332
from google.auth.transport import mtls # type: ignore
@@ -102,9 +101,38 @@ class AssetServiceClient(metaclass=AssetServiceClientMeta):
102101
"""Asset service definition."""
103102

104103
@staticmethod
105-
def _get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
106-
"""Converts api endpoint to mTLS endpoint."""
107-
return client_utils.get_default_mtls_endpoint(api_endpoint)
104+
def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
105+
"""Converts api endpoint to mTLS endpoint.
106+
107+
Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
108+
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
109+
Args:
110+
api_endpoint (Optional[str]): the api endpoint to convert.
111+
Returns:
112+
Optional[str]: converted mTLS api endpoint.
113+
"""
114+
if not api_endpoint:
115+
return api_endpoint
116+
117+
mtls_endpoint_re = re.compile(
118+
r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
119+
)
120+
121+
m = mtls_endpoint_re.match(api_endpoint)
122+
if m is None:
123+
# Could not parse api_endpoint; return as-is.
124+
return api_endpoint
125+
126+
name, mtls, sandbox, googledomain = m.groups()
127+
if mtls or not googledomain:
128+
return api_endpoint
129+
130+
if sandbox:
131+
return api_endpoint.replace(
132+
"sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
133+
)
134+
135+
return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
108136

109137
# Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
110138
DEFAULT_ENDPOINT = "cloudasset.googleapis.com"
@@ -422,31 +450,53 @@ def _get_client_cert_source(provided_cert_source, use_cert_flag):
422450
return client_cert_source
423451

424452
@staticmethod
425-
def _get_api_endpoint(
426-
api_override: Optional[str],
427-
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
428-
universe_domain: str,
429-
use_mtls_endpoint: str,
430-
) -> str:
431-
"""Return the API endpoint used by the client."""
432-
return client_utils.get_api_endpoint(
433-
api_override,
434-
client_cert_source,
435-
universe_domain,
436-
use_mtls_endpoint,
437-
AssetServiceClient._DEFAULT_UNIVERSE,
438-
AssetServiceClient.DEFAULT_MTLS_ENDPOINT,
439-
AssetServiceClient._DEFAULT_ENDPOINT_TEMPLATE,
440-
)
453+
def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str:
454+
"""Return the API endpoint used by the client.
455+
456+
Args:
457+
api_override (str): The API endpoint override. If specified, this is always
458+
the return value of this function and the other arguments are not used.
459+
client_cert_source (bytes): The client certificate source used by the client.
460+
universe_domain (str): The universe domain used by the client.
461+
use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
462+
Possible values are "always", "auto", or "never".
463+
464+
Returns:
465+
str: The API endpoint to be used by the client.
466+
"""
467+
if api_override is not None:
468+
api_endpoint = api_override
469+
elif use_mtls_endpoint == "always" or (use_mtls_endpoint == "auto" and client_cert_source):
470+
_default_universe = AssetServiceClient._DEFAULT_UNIVERSE
471+
if universe_domain != _default_universe:
472+
raise MutualTLSChannelError(f"mTLS is not supported in any universe other than {_default_universe}.")
473+
api_endpoint = AssetServiceClient.DEFAULT_MTLS_ENDPOINT
474+
else:
475+
api_endpoint = AssetServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(UNIVERSE_DOMAIN=universe_domain)
476+
return api_endpoint
441477

442478
@staticmethod
443479
def _get_universe_domain(client_universe_domain: Optional[str], universe_domain_env: Optional[str]) -> str:
444-
"""Return the universe domain used by the client."""
445-
return client_utils.get_universe_domain(
446-
client_universe_domain,
447-
universe_domain_env,
448-
AssetServiceClient._DEFAULT_UNIVERSE,
449-
)
480+
"""Return the universe domain used by the client.
481+
482+
Args:
483+
client_universe_domain (Optional[str]): The universe domain configured via the client options.
484+
universe_domain_env (Optional[str]): The universe domain configured via the "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.
485+
486+
Returns:
487+
str: The universe domain to be used by the client.
488+
489+
Raises:
490+
ValueError: If the universe domain is an empty string.
491+
"""
492+
universe_domain = AssetServiceClient._DEFAULT_UNIVERSE
493+
if client_universe_domain is not None:
494+
universe_domain = client_universe_domain
495+
elif universe_domain_env is not None:
496+
universe_domain = universe_domain_env
497+
if len(universe_domain.strip()) == 0:
498+
raise ValueError("Universe Domain cannot be an empty string.")
499+
return universe_domain
450500

451501
def _validate_universe_domain(self):
452502
"""Validates client's and credentials' universe domains are consistent.

packages/gapic-generator/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,41 @@ def test__get_client_cert_source():
278278
assert AssetServiceClient._get_client_cert_source(None, True) is mock_default_cert_source
279279
assert AssetServiceClient._get_client_cert_source(mock_provided_cert_source, "true") is mock_provided_cert_source
280280

281+
@mock.patch.object(AssetServiceClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(AssetServiceClient))
282+
@mock.patch.object(AssetServiceAsyncClient, "_DEFAULT_ENDPOINT_TEMPLATE", modify_default_endpoint_template(AssetServiceAsyncClient))
283+
def test__get_api_endpoint():
284+
api_override = "foo.com"
285+
mock_client_cert_source = mock.Mock()
286+
default_universe = AssetServiceClient._DEFAULT_UNIVERSE
287+
default_endpoint = AssetServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(UNIVERSE_DOMAIN=default_universe)
288+
mock_universe = "bar.com"
289+
mock_endpoint = AssetServiceClient._DEFAULT_ENDPOINT_TEMPLATE.format(UNIVERSE_DOMAIN=mock_universe)
290+
291+
assert AssetServiceClient._get_api_endpoint(api_override, mock_client_cert_source, default_universe, "always") == api_override
292+
assert AssetServiceClient._get_api_endpoint(None, mock_client_cert_source, default_universe, "auto") == AssetServiceClient.DEFAULT_MTLS_ENDPOINT
293+
assert AssetServiceClient._get_api_endpoint(None, None, default_universe, "auto") == default_endpoint
294+
assert AssetServiceClient._get_api_endpoint(None, None, default_universe, "always") == AssetServiceClient.DEFAULT_MTLS_ENDPOINT
295+
assert AssetServiceClient._get_api_endpoint(None, mock_client_cert_source, default_universe, "always") == AssetServiceClient.DEFAULT_MTLS_ENDPOINT
296+
assert AssetServiceClient._get_api_endpoint(None, None, mock_universe, "never") == mock_endpoint
297+
assert AssetServiceClient._get_api_endpoint(None, None, default_universe, "never") == default_endpoint
298+
299+
with pytest.raises(MutualTLSChannelError) as excinfo:
300+
AssetServiceClient._get_api_endpoint(None, mock_client_cert_source, mock_universe, "auto")
301+
assert str(excinfo.value) == "mTLS is not supported in any universe other than googleapis.com."
302+
303+
304+
def test__get_universe_domain():
305+
client_universe_domain = "foo.com"
306+
universe_domain_env = "bar.com"
307+
308+
assert AssetServiceClient._get_universe_domain(client_universe_domain, universe_domain_env) == client_universe_domain
309+
assert AssetServiceClient._get_universe_domain(None, universe_domain_env) == universe_domain_env
310+
assert AssetServiceClient._get_universe_domain(None, None) == AssetServiceClient._DEFAULT_UNIVERSE
311+
312+
with pytest.raises(ValueError) as excinfo:
313+
AssetServiceClient._get_universe_domain("", None)
314+
assert str(excinfo.value) == "Universe Domain cannot be an empty string."
315+
281316
@pytest.mark.parametrize("error_code,cred_info_json,show_cred_info", [
282317
(401, CRED_INFO_JSON, True),
283318
(403, CRED_INFO_JSON, True),
@@ -665,7 +700,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class):
665700
config_filename = "mock_certificate_config.json"
666701
config_file_content = json.dumps(config_data)
667702
m = mock.mock_open(read_data=config_file_content)
668-
with mock.patch("builtins.open", m):
703+
with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename):
669704
with mock.patch.dict(
670705
os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}
671706
):
@@ -712,7 +747,7 @@ def test_asset_service_client_get_mtls_endpoint_and_cert_source(client_class):
712747
config_filename = "mock_certificate_config.json"
713748
config_file_content = json.dumps(config_data)
714749
m = mock.mock_open(read_data=config_file_content)
715-
with mock.patch("builtins.open", m):
750+
with mock.patch("builtins.open", m), mock.patch("os.path.exists", side_effect=lambda path: os.path.basename(path) == config_filename):
716751
with mock.patch.dict(
717752
os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}
718753
):

0 commit comments

Comments
 (0)