Skip to content

Commit 4b861b2

Browse files
committed
chore: remove api-core changes from generator routing branch
1 parent 1f95ebc commit 4b861b2

7 files changed

Lines changed: 397 additions & 22 deletions

File tree

packages/google-api-core/.coveragerc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
branch = True
33

44
[report]
5-
fail_under = 99
5+
fail_under = 100
66
show_missing = True
7+
omit =
8+
tests/*
9+
*/tests/*
710
exclude_lines =
811
# Re-enable the standard pragma
912
pragma: NO COVER

packages/google-api-core/google/api_core/gapic_v1/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@
2525
# Older Python versions safely ignore this variable.
2626
__lazy_modules__: Set[str] = {
2727
"google.api_core.gapic_v1.client_info",
28-
"google.api_core.gapic_v1.client_utils",
2928
"google.api_core.gapic_v1.requests",
3029
"google.api_core.gapic_v1.routing_header",
3130
}
32-
__all__ = ["client_info", "client_utils", "requests", "routing_header"]
31+
__all__ = ["client_info", "requests", "routing_header"]
3332

3433
if _has_grpc:
3534
__lazy_modules__.update(
@@ -43,7 +42,6 @@
4342

4443
from google.api_core.gapic_v1 import ( # noqa: E402
4544
client_info,
46-
client_utils,
4745
requests,
4846
routing_header,
4947
)

packages/google-api-core/google/api_core/gapic_v1/requests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
if they are not already set.
2222
"""
2323

24-
from typing import Union
2524
import uuid
25+
from typing import Union
2626

2727
import google.protobuf.message
2828

@@ -65,7 +65,7 @@ def setup_request_id(
6565
setattr(request, field_name, str(uuid.uuid4()))
6666
except (AttributeError, ValueError):
6767
# Proto-plus messages or other objects
68-
if not getattr(request, field_name, None):
68+
if getattr(request, field_name, None) is None:
6969
setattr(request, field_name, str(uuid.uuid4()))
7070
else:
7171
if not getattr(request, field_name, None):

packages/google-api-core/google/api_core/universe.py

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"""Helpers for universe domain."""
1616

1717
from typing import Any, Optional
18+
from urllib.parse import urlparse, urlunparse
19+
20+
from google.auth.exceptions import MutualTLSChannelError # type: ignore
1821

1922
DEFAULT_UNIVERSE = "googleapis.com"
2023

@@ -36,6 +39,32 @@ def __init__(self, client_universe, credentials_universe):
3639
super().__init__(message)
3740

3841

42+
def get_universe_domain(
43+
*potential_universes: Optional[str],
44+
default_universe: str,
45+
) -> str:
46+
"""Return the universe domain used by the client.
47+
48+
Args:
49+
*potential_universes (Optional[str]): Potential universe domains in order of preference.
50+
default_universe (str): The default universe domain.
51+
52+
Returns:
53+
str: The universe domain to be used by the client.
54+
55+
Raises:
56+
EmptyUniverseError: If the resolved universe domain is an empty string.
57+
"""
58+
resolved = next(
59+
(x.strip() for x in potential_universes if x is not None),
60+
default_universe,
61+
)
62+
63+
if not resolved:
64+
raise EmptyUniverseError()
65+
return resolved
66+
67+
3968
def determine_domain(
4069
client_universe_domain: Optional[str], universe_domain_env: Optional[str]
4170
) -> str:
@@ -52,14 +81,11 @@ def determine_domain(
5281
Raises:
5382
ValueError: If the universe domain is an empty string.
5483
"""
55-
universe_domain = DEFAULT_UNIVERSE
56-
if client_universe_domain is not None:
57-
universe_domain = client_universe_domain
58-
elif universe_domain_env is not None:
59-
universe_domain = universe_domain_env
60-
if len(universe_domain.strip()) == 0:
61-
raise EmptyUniverseError
62-
return universe_domain
84+
return get_universe_domain(
85+
client_universe_domain,
86+
universe_domain_env,
87+
default_universe=DEFAULT_UNIVERSE,
88+
)
6389

6490

6591
def compare_domains(client_universe: str, credentials: Any) -> bool:
@@ -80,3 +106,94 @@ def compare_domains(client_universe: str, credentials: Any) -> bool:
80106
if client_universe != credentials_universe:
81107
raise UniverseMismatchError(client_universe, credentials_universe)
82108
return True
109+
110+
111+
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
112+
"""Converts api endpoint to mTLS endpoint.
113+
114+
Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
115+
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
116+
Other URLs (including those that do not match these domain suffixes or
117+
already contain '.mtls.') are passed through as-is.
118+
119+
Args:
120+
api_endpoint (Optional[str]): the api endpoint to convert.
121+
122+
Returns:
123+
Optional[str]: converted mTLS api endpoint.
124+
"""
125+
if not api_endpoint or ".mtls." in api_endpoint.lower():
126+
return api_endpoint
127+
128+
has_scheme = "://" in api_endpoint
129+
if not has_scheme:
130+
parsed = urlparse("//" + api_endpoint)
131+
else:
132+
parsed = urlparse(api_endpoint)
133+
134+
host = parsed.hostname
135+
if not host:
136+
return api_endpoint
137+
138+
port = f":{parsed.port}" if parsed.port else ""
139+
140+
lowered_host = host.lower()
141+
suffix_sandbox = ".sandbox.googleapis.com"
142+
suffix_google = ".googleapis.com"
143+
if lowered_host.endswith(suffix_sandbox):
144+
new_host = host[: -len(suffix_sandbox)] + ".mtls.sandbox.googleapis.com"
145+
elif lowered_host.endswith(suffix_google):
146+
new_host = host[: -len(suffix_google)] + ".mtls.googleapis.com"
147+
else:
148+
return api_endpoint
149+
150+
netloc = new_host + port
151+
new_parsed = parsed._replace(netloc=netloc)
152+
153+
if not has_scheme:
154+
return urlunparse(new_parsed)[2:]
155+
else:
156+
return urlunparse(new_parsed)
157+
158+
159+
def get_api_endpoint(
160+
api_override: Optional[str],
161+
universe_domain: str,
162+
default_universe: str,
163+
default_mtls_endpoint: Optional[str],
164+
default_endpoint_template: str,
165+
use_mtls: bool,
166+
) -> str:
167+
"""Return the API endpoint used by the client.
168+
169+
Args:
170+
api_override (Optional[str]): The API endpoint override. If specified,
171+
this is always returned.
172+
universe_domain (str): The universe domain used by the client.
173+
default_universe (str): The default universe domain.
174+
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
175+
default_endpoint_template (str): The default endpoint template containing
176+
a placeholder `{UNIVERSE_DOMAIN}`.
177+
use_mtls (bool): Whether to use the mTLS endpoint.
178+
179+
Returns:
180+
str: The API endpoint to be used by the client.
181+
182+
Raises:
183+
google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but
184+
not supported in the configured universe domain.
185+
ValueError: If mTLS is requested but no mTLS endpoint is available.
186+
"""
187+
if api_override is not None:
188+
return api_override
189+
190+
if use_mtls:
191+
if universe_domain.lower() != default_universe.lower():
192+
raise MutualTLSChannelError(
193+
f"mTLS is not supported in any universe other than {default_universe}."
194+
)
195+
if not default_mtls_endpoint:
196+
raise ValueError("mTLS endpoint is not available.")
197+
return default_mtls_endpoint
198+
else:
199+
return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)

packages/google-api-core/tests/unit/gapic/test_requests.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
from google.api_core.gapic_v1.requests import setup_request_id
2121

22-
2322
# --- Mock Request Helper Classes ---
2423

2524

@@ -28,9 +27,6 @@ def __init__(self, **kwargs):
2827
for k, v in kwargs.items():
2928
setattr(self, k, v)
3029

31-
def __contains__(self, key):
32-
return hasattr(self, key)
33-
3430

3531
class MockProtoRequest:
3632
def __init__(self, **kwargs):
@@ -45,9 +41,6 @@ class MockValueErrorRequest:
4541
def HasField(self, key):
4642
raise ValueError("Mismatched field")
4743

48-
def __contains__(self, key):
49-
return hasattr(self, key)
50-
5144

5245
# --- Parameterized Test ---
5346

@@ -60,6 +53,7 @@ def __contains__(self, key):
6053
# MockRequest cases
6154
(MockRequest(), True, "uuid"),
6255
(MockRequest(request_id="already_set"), True, "already_set"),
56+
(MockRequest(request_id=""), True, ""),
6357
(MockRequest(request_id=""), False, "uuid"),
6458
(MockRequest(request_id="already_set"), False, "already_set"),
6559
# MockProtoRequest cases
@@ -71,6 +65,7 @@ def __contains__(self, key):
7165
({}, True, "uuid"),
7266
({"request_id": None}, True, "uuid"),
7367
({"request_id": "already_set"}, True, "already_set"),
68+
({"request_id": ""}, True, ""),
7469
({"request_id": ""}, False, "uuid"),
7570
({"request_id": None}, False, "uuid"),
7671
({"request_id": "already_set"}, False, "already_set"),
@@ -80,6 +75,7 @@ def __contains__(self, key):
8075
ids=[
8176
"proto3_optional_not_in_request",
8277
"proto3_optional_already_in_request",
78+
"proto3_optional_explicit_empty",
8379
"non_proto3_optional_empty",
8480
"non_proto3_optional_already_set",
8581
"proto3_optional_not_in_request_proto",
@@ -88,6 +84,7 @@ def __contains__(self, key):
8884
"dict_proto3_optional_not_in_request",
8985
"dict_proto3_optional_value_none",
9086
"dict_proto3_optional_already_in_request",
87+
"dict_proto3_optional_explicit_empty",
9188
"dict_non_proto3_optional_empty",
9289
"dict_non_proto3_optional_value_none",
9390
"dict_non_proto3_optional_already_set",
@@ -111,6 +108,20 @@ def test_setup_request_id(request_obj, is_proto3_optional, expected):
111108
)
112109

113110
if expected == "uuid":
114-
assert re.match(UUID_REGEX, value)
111+
assert re.fullmatch(UUID_REGEX, value)
115112
else:
116113
assert value == expected
114+
115+
116+
def test_setup_request_id_assertion_strictness(mocker):
117+
# Mock uuid.uuid4 to return a UUID with trailing characters
118+
mock_uuid = mocker.patch("uuid.uuid4")
119+
mock_uuid.return_value.__str__.return_value = (
120+
"12345678-1234-4123-8123-123456789012-extra"
121+
)
122+
123+
# We expect test_setup_request_id to fail (raise AssertionError) because the UUID is invalid.
124+
# If test_setup_request_id uses re.fullmatch, it will fail (raise AssertionError), so pytest.raises passes.
125+
# If test_setup_request_id uses re.match, it will NOT fail, so pytest.raises fails!
126+
with pytest.raises(AssertionError):
127+
test_setup_request_id(MockRequest(), is_proto3_optional=True, expected="uuid")

packages/google-api-core/tests/unit/operations_v1/test_operations_client.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,52 @@ def test_cancel_operation():
100100

101101
def test_operations_client_config():
102102
assert operations_client_config.config["interfaces"]
103+
104+
105+
def test_operations_v1_transport_base_to_dict_protobuf_versions(monkeypatch):
106+
from google.auth import credentials as ga_credentials
107+
from google.longrunning import operations_pb2
108+
109+
from google.api_core.operations_v1.transports import base
110+
111+
message = operations_pb2.Operation(name="test_op")
112+
transport = base.OperationsTransport(
113+
credentials=ga_credentials.AnonymousCredentials()
114+
)
115+
116+
calls = []
117+
118+
def mock_message_to_dict(*args, **kwargs):
119+
calls.append(kwargs)
120+
return {"name": "test_op"}
121+
122+
monkeypatch.setattr(base.json_format, "MessageToDict", mock_message_to_dict)
123+
124+
monkeypatch.setattr(base, "PROTOBUF_VERSION", "3.20.0")
125+
res3 = transport._convert_protobuf_message_to_dict(message)
126+
assert res3.get("name") == "test_op"
127+
assert "including_default_value_fields" in calls[-1]
128+
129+
monkeypatch.setattr(base, "PROTOBUF_VERSION", "5.26.0")
130+
res5 = transport._convert_protobuf_message_to_dict(message)
131+
assert res5.get("name") == "test_op"
132+
assert "always_print_fields_with_no_presence" in calls[-1]
133+
134+
135+
def test_operations_v1_init_import_error_fallback(monkeypatch):
136+
import importlib
137+
138+
import google.api_core.operations_v1 as op_v1
139+
140+
orig_import = __import__
141+
142+
def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
143+
if "operations_rest_client_async" in name or (
144+
fromlist and "AsyncOperationsRestClient" in fromlist
145+
):
146+
raise ImportError("Simulated async rest import error")
147+
return orig_import(name, globals, locals, fromlist, level)
148+
149+
monkeypatch.setattr("builtins.__import__", mock_import)
150+
monkeypatch.setattr(op_v1, "_has_async_rest", True)
151+
importlib.reload(op_v1)

0 commit comments

Comments
 (0)