From 45d60a7a90628b302d6f99849a1e912fe6f4f8c4 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Mon, 14 Jul 2025 17:04:30 -0700 Subject: [PATCH 1/4] feat: expose universe_domain for tpc --- google/cloud/bigtable/data/_async/client.py | 32 ++++++ .../bigtable/data/_sync_autogen/client.py | 24 ++++ tests/unit/data/_async/test_client.py | 106 ++++++++++++++++++ tests/unit/data/_sync_autogen/test_client.py | 88 +++++++++++++++ 4 files changed, 250 insertions(+) diff --git a/google/cloud/bigtable/data/_async/client.py b/google/cloud/bigtable/data/_async/client.py index 6ee21b554..d1f80a09c 100644 --- a/google/cloud/bigtable/data/_async/client.py +++ b/google/cloud/bigtable/data/_async/client.py @@ -211,6 +211,20 @@ def __init__( *args, **kwargs, channel=custom_channel ), ) + if ( + credentials + and credentials._universe_domain != self.universe_domain + and self._emulator_host is None + ): + # validate that the universe domain of the credentials matches the + # universe domain configured in client_options + raise ValueError( + f"The configured universe domain ({self.universe_domain}) does " + "not match the universe domain found in the credentials " + f"({self._credentials.universe_domain}). If you haven't " + "configured the universe domain explicitly, `googleapis.com` " + "is the default." + ) self._is_closed = CrossSync.Event() self.transport = cast(TransportType, self._gapic_client.transport) # keep track of active instances to for warmup on channel refresh @@ -235,6 +249,24 @@ def __init__( stacklevel=2, ) + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used by the client instance. + """ + return self._gapic_client.universe_domain + + @property + def api_endpoint(self) -> str: + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance. + """ + return self._gapic_client.api_endpoint + @staticmethod def _client_version() -> str: """ diff --git a/google/cloud/bigtable/data/_sync_autogen/client.py b/google/cloud/bigtable/data/_sync_autogen/client.py index b36bf359a..1703a99af 100644 --- a/google/cloud/bigtable/data/_sync_autogen/client.py +++ b/google/cloud/bigtable/data/_sync_autogen/client.py @@ -158,6 +158,14 @@ def __init__( *args, **kwargs, channel=custom_channel ), ) + if ( + credentials + and credentials._universe_domain != self.universe_domain + and (self._emulator_host is None) + ): + raise ValueError( + f"The configured universe domain ({self.universe_domain}) does not match the universe domain found in the credentials ({self._credentials.universe_domain}). If you haven't configured the universe domain explicitly, `googleapis.com` is the default." + ) self._is_closed = CrossSync._Sync_Impl.Event() self.transport = cast(TransportType, self._gapic_client.transport) self._active_instances: Set[_WarmedInstanceKey] = set() @@ -179,6 +187,22 @@ def __init__( stacklevel=2, ) + @property + def universe_domain(self) -> str: + """Return the universe domain used by the client instance. + + Returns: + str: The universe domain used by the client instance.""" + return self._gapic_client.universe_domain + + @property + def api_endpoint(self) -> str: + """Return the API endpoint used by the client instance. + + Returns: + str: The API endpoint used by the client instance.""" + return self._gapic_client.api_endpoint + @staticmethod def _client_version() -> str: """Helper function to return the client version string for this client""" diff --git a/tests/unit/data/_async/test_client.py b/tests/unit/data/_async/test_client.py index 5e7302d75..2defd37a8 100644 --- a/tests/unit/data/_async/test_client.py +++ b/tests/unit/data/_async/test_client.py @@ -26,6 +26,7 @@ from google.cloud.bigtable_v2.types import ReadRowsResponse from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery from google.api_core import exceptions as core_exceptions +from google.api_core import client_options from google.cloud.bigtable.data.exceptions import InvalidChunk from google.cloud.bigtable.data.exceptions import _MutateRowsIncomplete from google.cloud.bigtable.data.mutations import DeleteAllFromRow @@ -1038,6 +1039,111 @@ def test_client_ctor_sync(self): assert client.project == "project-id" assert client._channel_refresh_task is None + @CrossSync.pytest + async def test_default_universe_domain(self): + """ + When not passed, universe_domain should default to googleapis.com + """ + async with self._make_client(project="project-id", credentials=None) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + @CrossSync.pytest + async def test_custom_universe_domain(self): + """test with a customized universe domain value""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + async with self._make_client( + project="project_id", + client_options=options, + use_emulator=False, + credentials=None, + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + + @CrossSync.pytest + async def test_custom_universe_domain_w_emulator(self): + """test with a customized universe domain value and emulator enabled""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + async with self._make_client( + project="project_id", + client_options=options, + use_emulator=True, + credentials=None, + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + + @CrossSync.pytest + async def test_configured_universe_domain_matches_GDU(self): + """that configured universe domain succeeds with matched GDU credentials.""" + universe_domain = "googleapis.com" + options = client_options.ClientOptions(universe_domain=universe_domain) + async with self._make_client( + project="project_id", client_options=options, credentials=None + ) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + @CrossSync.pytest + async def test_credential_universe_domain_matches_GDU(self): + """Test with credentials""" + creds = AnonymousCredentials() + creds._universe_domain = "googleapis.com" + async with self._make_client(project="project_id", credentials=creds) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + @CrossSync.pytest + async def test_anomynous_credential_universe_domain(self): + """Anomynopus credentials should use default universe domain""" + creds = AnonymousCredentials() + async with self._make_client(project="project_id", credentials=creds) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + @CrossSync.pytest + async def test_configured_universe_domain_mismatched_credentials(self): + """Test that configured universe domain errors with mismatched universe + domain credentials. + """ + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + creds = AnonymousCredentials() + creds._universe_domain = "different-universe" + with pytest.raises(ValueError) as exc: + self._make_client( + project="project_id", + client_options=options, + use_emulator=False, + credentials=creds, + ) + err_msg = ( + f"The configured universe domain ({universe_domain}) does " + "not match the universe domain found in the credentials " + f"({creds.universe_domain}). If you haven't " + "configured the universe domain explicitly, `googleapis.com` " + "is the default." + ) + assert exc.value.args[0] == err_msg + + @CrossSync.pytest + async def test_configured_universe_domain_matches_credentials(self): + """Test that configured universe domain succeeds with matching universe + domain credentials. + """ + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + creds = AnonymousCredentials() + creds._universe_domain = universe_domain + async with self._make_client( + project="project_id", credentials=creds, client_options=options + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + @CrossSync.convert_class("TestTable", add_mapping_for_name="TestTable") class TestTableAsync: diff --git a/tests/unit/data/_sync_autogen/test_client.py b/tests/unit/data/_sync_autogen/test_client.py index 38866c9dd..35dbfe01d 100644 --- a/tests/unit/data/_sync_autogen/test_client.py +++ b/tests/unit/data/_sync_autogen/test_client.py @@ -25,6 +25,7 @@ from google.cloud.bigtable_v2.types import ReadRowsResponse from google.cloud.bigtable.data.read_rows_query import ReadRowsQuery from google.api_core import exceptions as core_exceptions +from google.api_core import client_options from google.cloud.bigtable.data.exceptions import InvalidChunk from google.cloud.bigtable.data.exceptions import _MutateRowsIncomplete from google.cloud.bigtable.data.mutations import DeleteAllFromRow @@ -836,6 +837,93 @@ def test_context_manager(self): close_mock.assert_called_once() true_close() + def test_default_universe_domain(self): + """When not passed, universe_domain should default to googleapis.com""" + with self._make_client(project="project-id", credentials=None) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + def test_custom_universe_domain(self): + """test with a customized universe domain value""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + with self._make_client( + project="project_id", + client_options=options, + use_emulator=False, + credentials=None, + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + + def test_custom_universe_domain_w_emulator(self): + """test with a customized universe domain value and emulator enabled""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + with self._make_client( + project="project_id", + client_options=options, + use_emulator=True, + credentials=None, + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + + def test_configured_universe_domain_matches_GDU(self): + """that configured universe domain succeeds with matched GDU credentials.""" + universe_domain = "googleapis.com" + options = client_options.ClientOptions(universe_domain=universe_domain) + with self._make_client( + project="project_id", client_options=options, credentials=None + ) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + def test_credential_universe_domain_matches_GDU(self): + """Test with credentials""" + creds = AnonymousCredentials() + creds._universe_domain = "googleapis.com" + with self._make_client(project="project_id", credentials=creds) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + def test_anomynous_credential_universe_domain(self): + """Anomynopus credentials should use default universe domain""" + creds = AnonymousCredentials() + with self._make_client(project="project_id", credentials=creds) as client: + assert client.universe_domain == "googleapis.com" + assert client.api_endpoint == "bigtable.googleapis.com" + + def test_configured_universe_domain_mismatched_credentials(self): + """Test that configured universe domain errors with mismatched universe + domain credentials.""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + creds = AnonymousCredentials() + creds._universe_domain = "different-universe" + with pytest.raises(ValueError) as exc: + self._make_client( + project="project_id", + client_options=options, + use_emulator=False, + credentials=creds, + ) + err_msg = f"The configured universe domain ({universe_domain}) does not match the universe domain found in the credentials ({creds.universe_domain}). If you haven't configured the universe domain explicitly, `googleapis.com` is the default." + assert exc.value.args[0] == err_msg + + def test_configured_universe_domain_matches_credentials(self): + """Test that configured universe domain succeeds with matching universe + domain credentials.""" + universe_domain = "test-universe.test" + options = client_options.ClientOptions(universe_domain=universe_domain) + creds = AnonymousCredentials() + creds._universe_domain = universe_domain + with self._make_client( + project="project_id", credentials=creds, client_options=options + ) as client: + assert client.universe_domain == universe_domain + assert client.api_endpoint == f"bigtable.{universe_domain}" + @CrossSync._Sync_Impl.add_mapping_decorator("TestTable") class TestTable: From 94f04dd2a6dfd9762b709331efea5939516d7bb5 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 15 Jul 2025 10:21:26 -0700 Subject: [PATCH 2/4] update google-auth min version to 2.23.0 --- google/cloud/bigtable/data/_async/client.py | 2 +- setup.py | 2 +- testing/constraints-3.7.txt | 2 +- testing/constraints-3.8.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/google/cloud/bigtable/data/_async/client.py b/google/cloud/bigtable/data/_async/client.py index d1f80a09c..d7eac2a4d 100644 --- a/google/cloud/bigtable/data/_async/client.py +++ b/google/cloud/bigtable/data/_async/client.py @@ -213,7 +213,7 @@ def __init__( ) if ( credentials - and credentials._universe_domain != self.universe_domain + and credentials.universe_domain != self.universe_domain and self._emulator_host is None ): # validate that the universe domain of the credentials matches the diff --git a/setup.py b/setup.py index 7e89af11b..5a4f56ae6 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ dependencies = [ "google-api-core[grpc] >= 2.16.0, <3.0.0", "google-cloud-core >= 1.4.4, <3.0.0", - "google-auth >= 2.14.1, <3.0.0,!=2.24.0,!=2.25.0", + "google-auth >= 2.23.0, <3.0.0,!=2.24.0,!=2.25.0", "grpc-google-iam-v1 >= 0.12.4, <1.0.0", "proto-plus >= 1.22.3, <2.0.0", "proto-plus >= 1.25.0, <2.0.0; python_version>='3.13'", diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt index 5a3f3e3fc..78448c9da 100644 --- a/testing/constraints-3.7.txt +++ b/testing/constraints-3.7.txt @@ -6,7 +6,7 @@ # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 google-api-core==2.16.0 -google-auth==2.14.1 +google-auth==2.23.0 google-cloud-core==2.0.0 grpc-google-iam-v1==0.12.4 proto-plus==1.22.3 diff --git a/testing/constraints-3.8.txt b/testing/constraints-3.8.txt index 5ed0c2fb9..b64d3139f 100644 --- a/testing/constraints-3.8.txt +++ b/testing/constraints-3.8.txt @@ -6,7 +6,7 @@ # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 google-api-core==2.16.0 -google-auth==2.14.1 +google-auth==2.23.0 google-cloud-core==2.0.0 grpc-google-iam-v1==0.12.4 proto-plus==1.22.3 From 8a980d66a7f381e8f016d9da42b30c839f78a184 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 15 Jul 2025 10:23:59 -0700 Subject: [PATCH 3/4] regenerated sync file --- google/cloud/bigtable/data/_sync_autogen/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/bigtable/data/_sync_autogen/client.py b/google/cloud/bigtable/data/_sync_autogen/client.py index 1703a99af..a7e07e20d 100644 --- a/google/cloud/bigtable/data/_sync_autogen/client.py +++ b/google/cloud/bigtable/data/_sync_autogen/client.py @@ -160,7 +160,7 @@ def __init__( ) if ( credentials - and credentials._universe_domain != self.universe_domain + and credentials.universe_domain != self.universe_domain and (self._emulator_host is None) ): raise ValueError( From 2fa8ec28c91259bc6c1505522946d5b4fd41ea1b Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 15 Jul 2025 10:29:27 -0700 Subject: [PATCH 4/4] removed redundant test --- tests/unit/data/_async/test_client.py | 14 -------------- tests/unit/data/_sync_autogen/test_client.py | 13 ------------- 2 files changed, 27 deletions(-) diff --git a/tests/unit/data/_async/test_client.py b/tests/unit/data/_async/test_client.py index 2defd37a8..97eec56ce 100644 --- a/tests/unit/data/_async/test_client.py +++ b/tests/unit/data/_async/test_client.py @@ -1050,20 +1050,6 @@ async def test_default_universe_domain(self): @CrossSync.pytest async def test_custom_universe_domain(self): - """test with a customized universe domain value""" - universe_domain = "test-universe.test" - options = client_options.ClientOptions(universe_domain=universe_domain) - async with self._make_client( - project="project_id", - client_options=options, - use_emulator=False, - credentials=None, - ) as client: - assert client.universe_domain == universe_domain - assert client.api_endpoint == f"bigtable.{universe_domain}" - - @CrossSync.pytest - async def test_custom_universe_domain_w_emulator(self): """test with a customized universe domain value and emulator enabled""" universe_domain = "test-universe.test" options = client_options.ClientOptions(universe_domain=universe_domain) diff --git a/tests/unit/data/_sync_autogen/test_client.py b/tests/unit/data/_sync_autogen/test_client.py index 35dbfe01d..5e71cc683 100644 --- a/tests/unit/data/_sync_autogen/test_client.py +++ b/tests/unit/data/_sync_autogen/test_client.py @@ -844,19 +844,6 @@ def test_default_universe_domain(self): assert client.api_endpoint == "bigtable.googleapis.com" def test_custom_universe_domain(self): - """test with a customized universe domain value""" - universe_domain = "test-universe.test" - options = client_options.ClientOptions(universe_domain=universe_domain) - with self._make_client( - project="project_id", - client_options=options, - use_emulator=False, - credentials=None, - ) as client: - assert client.universe_domain == universe_domain - assert client.api_endpoint == f"bigtable.{universe_domain}" - - def test_custom_universe_domain_w_emulator(self): """test with a customized universe domain value and emulator enabled""" universe_domain = "test-universe.test" options = client_options.ClientOptions(universe_domain=universe_domain)