Skip to content
Draft

WIP #17999

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,120 +52,128 @@ except ImportError: # pragma: NO COVER

DEFAULT_UNIVERSE = "googleapis.com"

try:
from google.api_core.universe import get_default_mtls_endpoint
except ImportError: # pragma: NO COVER
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
"""Converts api endpoint to mTLS endpoint.

Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
Other URLs (including those that do not match these domain suffixes or
already contain '.mtls.') are passed through as-is.

Args:
api_endpoint (Optional[str]): the api endpoint to convert.

Returns:
Optional[str]: converted mTLS api endpoint.
"""
if not api_endpoint or ".mtls." in api_endpoint.lower():
return api_endpoint

has_scheme = "://" in api_endpoint
if not has_scheme:
parsed = urlparse("//" + api_endpoint)
else:
parsed = urlparse(api_endpoint)

host = parsed.hostname
if not host:
return api_endpoint

port = f":{parsed.port}" if parsed.port else ""

lowered_host = host.lower()
suffix_sandbox = ".sandbox.googleapis.com"
suffix_google = ".googleapis.com"
if lowered_host.endswith(suffix_sandbox):
new_host = host[: -len(suffix_sandbox)] + ".mtls.sandbox.googleapis.com"
elif lowered_host.endswith(suffix_google):
new_host = host[: -len(suffix_google)] + ".mtls.googleapis.com"
else:
return api_endpoint

netloc = new_host + port
new_parsed = parsed._replace(netloc=netloc)

if not has_scheme:
return urlunparse(new_parsed)[2:]
else:
return urlunparse(new_parsed)

def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
"""Converts api endpoint to mTLS endpoint.

Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
Other URLs (including those that do not match these domain suffixes or
already contain '.mtls.') are passed through as-is.

Args:
api_endpoint (Optional[str]): the api endpoint to convert.

Returns:
Optional[str]: converted mTLS api endpoint.
"""
if not api_endpoint or ".mtls." in api_endpoint.lower():
return api_endpoint

has_scheme = "://" in api_endpoint
if not has_scheme:
parsed = urlparse("//" + api_endpoint)
else:
parsed = urlparse(api_endpoint)

host = parsed.hostname
if not host:
return api_endpoint

port = f":{parsed.port}" if parsed.port else ""

lowered_host = host.lower()
suffix_sandbox = ".sandbox.googleapis.com"
suffix_google = ".googleapis.com"
if lowered_host.endswith(suffix_sandbox):
new_host = host[: -len(suffix_sandbox)] + ".mtls.sandbox.googleapis.com"
elif lowered_host.endswith(suffix_google):
new_host = host[: -len(suffix_google)] + ".mtls.googleapis.com"
else:
return api_endpoint

netloc = new_host + port
new_parsed = parsed._replace(netloc=netloc)

if not has_scheme:
return urlunparse(new_parsed)[2:]
else:
return urlunparse(new_parsed)

def get_api_endpoint(
api_override: Optional[str],
universe_domain: str,
default_universe: str,
default_mtls_endpoint: Optional[str],
default_endpoint_template: str,
use_mtls: bool,
) -> str:
"""Return the API endpoint used by the client.

Args:
api_override (Optional[str]): The API endpoint override. If specified,
this is always returned.
universe_domain (str): The universe domain used by the client.
default_universe (str): The default universe domain.
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
default_endpoint_template (str): The default endpoint template containing
a placeholder `{UNIVERSE_DOMAIN}`.
use_mtls (bool): Whether to use the mTLS endpoint.

Returns:
str: The API endpoint to be used by the client.

Raises:
google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but
not supported in the configured universe domain.
ValueError: If mTLS is requested but no mTLS endpoint is available.
"""
if api_override is not None:
return api_override

if use_mtls:
if universe_domain.lower() != default_universe.lower():
raise MutualTLSChannelError(
f"mTLS is not supported in any universe other than {default_universe}."
)
if not default_mtls_endpoint:
raise ValueError("mTLS endpoint is not available.")
return default_mtls_endpoint
else:
return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)

def get_universe_domain(
*potential_universes: Optional[str],
default_universe: str = DEFAULT_UNIVERSE,
) -> str:
"""Return the universe domain used by the client.

Args:
*potential_universes (Optional[str]): Potential universe domains in order of preference.
default_universe (str): The default universe domain.

Returns:
str: The universe domain to be used by the client.
try:
from google.api_core.universe import get_api_endpoint
except ImportError: # pragma: NO COVER
def get_api_endpoint(
api_override: Optional[str],
universe_domain: str,
default_universe: str,
default_mtls_endpoint: Optional[str],
default_endpoint_template: str,
use_mtls: bool,
) -> str:
"""Return the API endpoint used by the client.

Args:
api_override (Optional[str]): The API endpoint override. If specified,
this is always returned.
universe_domain (str): The universe domain used by the client.
default_universe (str): The default universe domain.
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
default_endpoint_template (str): The default endpoint template containing
a placeholder `{UNIVERSE_DOMAIN}`.
use_mtls (bool): Whether to use the mTLS endpoint.

Returns:
str: The API endpoint to be used by the client.

Raises:
google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but
not supported in the configured universe domain.
ValueError: If mTLS is requested but no mTLS endpoint is available.
"""
if api_override is not None:
return api_override

if use_mtls:
if universe_domain.lower() != default_universe.lower():
raise MutualTLSChannelError(
f"mTLS is not supported in any universe other than {default_universe}."
)
if not default_mtls_endpoint:
raise ValueError("mTLS endpoint is not available.")
return default_mtls_endpoint
else:
return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)

Raises:
EmptyUniverseError: If the resolved universe domain is an empty string.
"""
resolved = next(
(x.strip() for x in potential_universes if x is not None),
default_universe,
)
try:
from google.api_core.universe import get_universe_domain
except ImportError: # pragma: NO COVER
def get_universe_domain(
*potential_universes: Optional[str],
default_universe: str,
) -> str:
Comment on lines +153 to +156

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback implementation of get_universe_domain is missing the default value for default_universe (default_universe: str = DEFAULT_UNIVERSE). This breaks compatibility with the original signature and with google.api_core.universe.get_universe_domain, which makes default_universe optional. Please restore the default value to ensure it remains a drop-in replacement.

    def get_universe_domain(
        *potential_universes: Optional[str],
        default_universe: str = DEFAULT_UNIVERSE,
    ) -> str:

"""Return the universe domain used by the client.

Args:
*potential_universes (Optional[str]): Potential universe domains in order of preference.
default_universe (str): The default universe domain.

Returns:
str: The universe domain to be used by the client.

Raises:
EmptyUniverseError: If the resolved universe domain is an empty string.
"""
resolved = next(
(x.strip() for x in potential_universes if x is not None),
default_universe,
)

if not resolved:
raise EmptyUniverseError()
return resolved
if not resolved:
raise EmptyUniverseError()
return resolved

{% if has_auto_populated_fields %}

Expand Down Expand Up @@ -215,61 +223,64 @@ def setup_request_id(

{% endif %}

def transcode_request(
http_options: List[Dict[str, str]],
request: Any,
required_fields_default_values: Optional[Dict[str, Any]] = None,
rest_numeric_enums: bool = False,
) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]:
"""Transcodes a request into HTTP method, URI, body, and query parameters.

Args:
http_options (List[Dict[str, str]]): List of HTTP transcoding rules.
request (Any): The protobuf or proto-plus request message.
required_fields_default_values (Optional[Dict[str, Any]]): Dictionary
of required fields default values to merge into query parameters if missing.
rest_numeric_enums (bool): Whether to encode enums as integers.

Returns:
Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: A tuple containing:
- The raw transcoded request dictionary (containing keys like 'uri', 'method').
- The serialized request body JSON string, or None if no body.
- The query parameters dictionary.
"""
if request is None:
raise TypeError("request cannot be None")

# Convert proto-plus message to its underlying protobuf message if needed
pb_request = getattr(request, "_pb", request)

transcoded_request = path_template.transcode(http_options, pb_request)

body_json = None
if transcoded_request.get("body") is not None:
body_json = json_format.MessageToJson(
transcoded_request["body"],
use_integers_for_enums=rest_numeric_enums,
)

query_params_json = {}
if transcoded_request.get("query_params") is not None:
query_params_json = json.loads(
json_format.MessageToJson(
transcoded_request["query_params"],
try:
from google.api_core.rest_helpers import transcode_request
except ImportError: # pragma: NO COVER
def transcode_request(
http_options: List[Dict[str, str]],
request: Any,
required_fields_default_values: Optional[Dict[str, Any]] = None,
rest_numeric_enums: bool = False,
) -> Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]:
"""Transcodes a request into HTTP method, URI, body, and query parameters.

Args:
http_options (List[Dict[str, str]]): List of HTTP transcoding rules.
request (Any): The protobuf or proto-plus request message.
required_fields_default_values (Optional[Dict[str, Any]]): Dictionary
of required fields default values to merge into query parameters if missing.
rest_numeric_enums (bool): Whether to encode enums as integers.

Returns:
Tuple[Dict[str, Any], Optional[str], Dict[str, Any]]: A tuple containing:
- The raw transcoded request dictionary (containing keys like 'uri', 'method').
- The serialized request body JSON string, or None if no body.
- The query parameters dictionary.
"""
if request is None:
raise TypeError("request cannot be None")

# Convert proto-plus message to its underlying protobuf message if needed
pb_request = getattr(request, "_pb", request)

transcoded_request = path_template.transcode(http_options, pb_request)

body_json = None
if transcoded_request.get("body") is not None:
body_json = json_format.MessageToJson(
transcoded_request["body"],
use_integers_for_enums=rest_numeric_enums,
)
)

# If required_fields_default_values is provided, we merge default values for missing
# required fields into the query parameters.
if required_fields_default_values:
for k, v in required_fields_default_values.items():
if k not in query_params_json:
query_params_json[k] = v
query_params_json = {}
if transcoded_request.get("query_params") is not None:
query_params_json = json.loads(
json_format.MessageToJson(
transcoded_request["query_params"],
use_integers_for_enums=rest_numeric_enums,
)
)

# If required_fields_default_values is provided, we merge default values for missing
# required fields into the query parameters.
if required_fields_default_values:
for k, v in required_fields_default_values.items():
if k not in query_params_json:
query_params_json[k] = v

if rest_numeric_enums:
query_params_json["$alt"] = "json;enum-encoding=int"
if rest_numeric_enums:
query_params_json["$alt"] = "json;enum-encoding=int"

return transcoded_request, body_json, query_params_json
return transcoded_request, body_json, query_params_json

{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):

self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = {{ service.client_name }}._read_environment_variables()
self._client_cert_source = {{ service.client_name }}._get_client_cert_source(self._client_options.client_cert_source, self._use_client_cert)
self._universe_domain = get_universe_domain(universe_domain_opt, self._universe_domain_env)
self._universe_domain = get_universe_domain(universe_domain_opt, self._universe_domain_env, default_universe="googleapis.com")
self._api_endpoint: str = "" # updated below, depending on `transport`

# Initialize the universe domain validation.
Expand Down
Loading