Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit 9535e5e

Browse filesBrowse files
sagnghosrahul2393
andauthored
feat: add support for experimental host (#1452)
* feat: add support for experimental host * fix lint issues * fixed unit tests * added docmentation for new client option --------- Co-authored-by: rahul2393 <irahul@google.com>
1 parent d51a7a8 commit 9535e5e
Copy full SHA for 9535e5e

14 files changed

+128-13Lines changed: 128 additions & 13 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎google/cloud/spanner_dbapi/connection.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_dbapi/connection.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""DB-API Connection for the Google Cloud Spanner."""
1616
import warnings
1717

18+
from google.api_core.client_options import ClientOptions
1819
from google.api_core.exceptions import Aborted
1920
from google.api_core.gapic_v1.client_info import ClientInfo
2021
from google.auth.credentials import AnonymousCredentials
@@ -734,6 +735,7 @@ def connect(
734735
client=None,
735736
route_to_leader_enabled=True,
736737
database_role=None,
738+
experimental_host=None,
737739
**kwargs,
738740
):
739741
"""Creates a connection to a Google Cloud Spanner database.
@@ -805,6 +807,10 @@ def connect(
805807
client_options = None
806808
if isinstance(credentials, AnonymousCredentials):
807809
client_options = kwargs.get("client_options")
810+
if experimental_host is not None:
811+
project = "default"
812+
credentials = AnonymousCredentials()
813+
client_options = ClientOptions(api_endpoint=experimental_host)
808814
client = spanner.Client(
809815
project=project,
810816
credentials=credentials,
Collapse file

‎google/cloud/spanner_v1/client.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_v1/client.py
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ class Client(ClientWithProject):
176176
or :class:`dict`
177177
:param default_transaction_options: (Optional) Default options to use for all transactions.
178178
179+
:type experimental_host: str
180+
:param experimental_host: (Optional) The endpoint for a spanner experimental host deployment.
181+
This is intended only for experimental host spanner endpoints.
182+
If set, this will override the `api_endpoint` in `client_options`.
183+
179184
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
180185
and ``admin`` are :data:`True`
181186
"""
@@ -200,8 +205,10 @@ def __init__(
200205
directed_read_options=None,
201206
observability_options=None,
202207
default_transaction_options: Optional[DefaultTransactionOptions] = None,
208+
experimental_host=None,
203209
):
204210
self._emulator_host = _get_spanner_emulator_host()
211+
self._experimental_host = experimental_host
205212

206213
if client_options and type(client_options) is dict:
207214
self._client_options = google.api_core.client_options.from_dict(
@@ -212,6 +219,8 @@ def __init__(
212219

213220
if self._emulator_host:
214221
credentials = AnonymousCredentials()
222+
elif self._experimental_host:
223+
credentials = AnonymousCredentials()
215224
elif isinstance(credentials, AnonymousCredentials):
216225
self._emulator_host = self._client_options.api_endpoint
217226

@@ -324,6 +333,15 @@ def instance_admin_api(self):
324333
client_options=self._client_options,
325334
transport=transport,
326335
)
336+
elif self._experimental_host:
337+
transport = InstanceAdminGrpcTransport(
338+
channel=grpc.insecure_channel(target=self._experimental_host)
339+
)
340+
self._instance_admin_api = InstanceAdminClient(
341+
client_info=self._client_info,
342+
client_options=self._client_options,
343+
transport=transport,
344+
)
327345
else:
328346
self._instance_admin_api = InstanceAdminClient(
329347
credentials=self.credentials,
@@ -345,6 +363,15 @@ def database_admin_api(self):
345363
client_options=self._client_options,
346364
transport=transport,
347365
)
366+
elif self._experimental_host:
367+
transport = DatabaseAdminGrpcTransport(
368+
channel=grpc.insecure_channel(target=self._experimental_host)
369+
)
370+
self._database_admin_api = DatabaseAdminClient(
371+
client_info=self._client_info,
372+
client_options=self._client_options,
373+
transport=transport,
374+
)
348375
else:
349376
self._database_admin_api = DatabaseAdminClient(
350377
credentials=self.credentials,
@@ -485,6 +512,7 @@ def instance(
485512
self._emulator_host,
486513
labels,
487514
processing_units,
515+
self._experimental_host,
488516
)
489517

490518
def list_instances(self, filter_="", page_size=None):
Collapse file

‎google/cloud/spanner_v1/database.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_v1/database.py
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,11 @@ def __init__(
203203

204204
self._pool = pool
205205
pool.bind(self)
206+
is_experimental_host = self._instance.experimental_host is not None
206207

207-
self._sessions_manager = DatabaseSessionsManager(self, pool)
208+
self._sessions_manager = DatabaseSessionsManager(
209+
self, pool, is_experimental_host
210+
)
208211

209212
@classmethod
210213
def from_pb(cls, database_pb, instance, pool=None):
@@ -449,6 +452,16 @@ def spanner_api(self):
449452
client_info=client_info, transport=transport
450453
)
451454
return self._spanner_api
455+
if self._instance.experimental_host is not None:
456+
transport = SpannerGrpcTransport(
457+
channel=grpc.insecure_channel(self._instance.experimental_host)
458+
)
459+
self._spanner_api = SpannerClient(
460+
client_info=client_info,
461+
transport=transport,
462+
client_options=client_options,
463+
)
464+
return self._spanner_api
452465
credentials = self._instance._client.credentials
453466
if isinstance(credentials, google.auth.credentials.Scoped):
454467
credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,))
Collapse file

‎google/cloud/spanner_v1/database_sessions_manager.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_v1/database_sessions_manager.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ class DatabaseSessionsManager(object):
6262
_MAINTENANCE_THREAD_POLLING_INTERVAL = timedelta(minutes=10)
6363
_MAINTENANCE_THREAD_REFRESH_INTERVAL = timedelta(days=7)
6464

65-
def __init__(self, database, pool):
65+
def __init__(self, database, pool, is_experimental_host: bool = False):
6666
self._database = database
6767
self._pool = pool
68+
self._is_experimental_host = is_experimental_host
6869

6970
# Declare multiplexed session attributes. When a multiplexed session for the
7071
# database session manager is created, a maintenance thread is initialized to
@@ -88,7 +89,7 @@ def get_session(self, transaction_type: TransactionType) -> Session:
8889

8990
session = (
9091
self._get_multiplexed_session()
91-
if self._use_multiplexed(transaction_type)
92+
if self._use_multiplexed(transaction_type) or self._is_experimental_host
9293
else self._pool.get()
9394
)
9495

Collapse file

‎google/cloud/spanner_v1/instance.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_v1/instance.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def __init__(
122122
emulator_host=None,
123123
labels=None,
124124
processing_units=None,
125+
experimental_host=None,
125126
):
126127
self.instance_id = instance_id
127128
self._client = client
@@ -142,6 +143,7 @@ def __init__(
142143
self._node_count = processing_units // PROCESSING_UNITS_PER_NODE
143144
self.display_name = display_name or instance_id
144145
self.emulator_host = emulator_host
146+
self.experimental_host = experimental_host
145147
if labels is None:
146148
labels = {}
147149
self.labels = labels
Collapse file

‎google/cloud/spanner_v1/testing/database_test.py‎

Copy file name to clipboardExpand all lines: google/cloud/spanner_v1/testing/database_test.py
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ def spanner_api(self):
8686
transport=transport,
8787
)
8888
return self._spanner_api
89+
if self._instance.experimental_host is not None:
90+
channel = grpc.insecure_channel(self._instance.experimental_host)
91+
self._x_goog_request_id_interceptor = XGoogRequestIDHeaderInterceptor()
92+
self._interceptors.append(self._x_goog_request_id_interceptor)
93+
channel = grpc.intercept_channel(channel, *self._interceptors)
94+
transport = SpannerGrpcTransport(channel=channel)
95+
self._spanner_api = SpannerClient(
96+
client_info=client_info,
97+
transport=transport,
98+
client_options=client_options,
99+
)
100+
return self._spanner_api
89101
credentials = client.credentials
90102
if isinstance(credentials, google.auth.credentials.Scoped):
91103
credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,))
Collapse file

‎tests/system/_helpers.py‎

Copy file name to clipboardExpand all lines: tests/system/_helpers.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@
5656
EMULATOR_PROJECT_DEFAULT = "emulator-test-project"
5757
EMULATOR_PROJECT = os.getenv(EMULATOR_PROJECT_ENVVAR, EMULATOR_PROJECT_DEFAULT)
5858

59+
USE_EXPERIMENTAL_HOST_ENVVAR = "SPANNER_EXPERIMENTAL_HOST"
60+
EXPERIMENTAL_HOST = os.getenv(USE_EXPERIMENTAL_HOST_ENVVAR)
61+
USE_EXPERIMENTAL_HOST = EXPERIMENTAL_HOST is not None
62+
63+
EXPERIMENTAL_HOST_PROJECT = "default"
64+
EXPERIMENTAL_HOST_INSTANCE = "default"
5965

6066
DDL_STATEMENTS = (
6167
_fixtures.PG_DDL_STATEMENTS
Collapse file

‎tests/system/conftest.py‎

Copy file name to clipboardExpand all lines: tests/system/conftest.py
+18-2Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ def not_emulator():
4949
pytest.skip(f"{_helpers.USE_EMULATOR_ENVVAR} set in environment.")
5050

5151

52+
@pytest.fixture(scope="module")
53+
def not_experimental_host():
54+
if _helpers.USE_EXPERIMENTAL_HOST:
55+
pytest.skip(f"{_helpers.USE_EXPERIMENTAL_HOST_ENVVAR} set in environment.")
56+
57+
5258
@pytest.fixture(scope="session")
5359
def not_postgres(database_dialect):
5460
if database_dialect == DatabaseDialect.POSTGRESQL:
@@ -104,6 +110,15 @@ def spanner_client():
104110
project=_helpers.EMULATOR_PROJECT,
105111
credentials=credentials,
106112
)
113+
elif _helpers.USE_EXPERIMENTAL_HOST:
114+
from google.auth.credentials import AnonymousCredentials
115+
116+
credentials = AnonymousCredentials()
117+
return spanner_v1.Client(
118+
project=_helpers.EXPERIMENTAL_HOST_PROJECT,
119+
credentials=credentials,
120+
experimental_host=_helpers.EXPERIMENTAL_HOST,
121+
)
107122
else:
108123
client_options = {"api_endpoint": _helpers.API_ENDPOINT}
109124
return spanner_v1.Client(
@@ -130,15 +145,16 @@ def backup_operation_timeout():
130145
def shared_instance_id():
131146
if _helpers.CREATE_INSTANCE:
132147
return f"{_helpers.unique_id('google-cloud')}"
133-
148+
if _helpers.USE_EXPERIMENTAL_HOST:
149+
return _helpers.EXPERIMENTAL_HOST_INSTANCE
134150
return _helpers.INSTANCE_ID
135151

136152

137153
@pytest.fixture(scope="session")
138154
def instance_configs(spanner_client):
139155
configs = list(_helpers.retry_503(spanner_client.list_instance_configs)())
140156

141-
if not _helpers.USE_EMULATOR:
157+
if not _helpers.USE_EMULATOR and not _helpers.USE_EXPERIMENTAL_HOST:
142158
# Defend against back-end returning configs for regions we aren't
143159
# actually allowed to use.
144160
configs = [config for config in configs if "-us-" in config.name]
Collapse file

‎tests/system/test_backup_api.py‎

Copy file name to clipboardExpand all lines: tests/system/test_backup_api.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@
2626
Remove {_helpers.SKIP_BACKUP_TESTS_ENVVAR} from environment to run these tests.\
2727
"""
2828
skip_emulator_reason = "Backup operations not supported by emulator."
29+
skip_experimental_host_reason = (
30+
"Backup operations not supported on experimental host yet."
31+
)
2932

3033
pytestmark = [
3134
pytest.mark.skipif(_helpers.SKIP_BACKUP_TESTS, reason=skip_env_reason),
3235
pytest.mark.skipif(_helpers.USE_EMULATOR, reason=skip_emulator_reason),
36+
pytest.mark.skipif(
37+
_helpers.USE_EXPERIMENTAL_HOST, reason=skip_experimental_host_reason
38+
),
3339
]
3440

3541

Collapse file

‎tests/system/test_database_api.py‎

Copy file name to clipboardExpand all lines: tests/system/test_database_api.py
+15-2Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747

4848

4949
@pytest.fixture(scope="module")
50-
def multiregion_instance(spanner_client, instance_operation_timeout, not_postgres):
50+
def multiregion_instance(
51+
spanner_client, instance_operation_timeout, not_postgres, not_experimental_host
52+
):
5153
multi_region_instance_id = _helpers.unique_id("multi-region")
5254
multi_region_config = "nam3"
5355
config_name = "{}/instanceConfigs/{}".format(
@@ -97,6 +99,7 @@ def test_database_binding_of_fixed_size_pool(
9799
databases_to_delete,
98100
not_postgres,
99101
proto_descriptor_file,
102+
not_experimental_host,
100103
):
101104
temp_db_id = _helpers.unique_id("fixed_size_db", separator="_")
102105
temp_db = shared_instance.database(temp_db_id)
@@ -130,6 +133,7 @@ def test_database_binding_of_pinging_pool(
130133
databases_to_delete,
131134
not_postgres,
132135
proto_descriptor_file,
136+
not_experimental_host,
133137
):
134138
temp_db_id = _helpers.unique_id("binding_db", separator="_")
135139
temp_db = shared_instance.database(temp_db_id)
@@ -217,6 +221,7 @@ def test_create_database_pitr_success(
217221
def test_create_database_with_default_leader_success(
218222
not_emulator, # Default leader setting not supported by the emulator
219223
not_postgres,
224+
not_experimental_host,
220225
multiregion_instance,
221226
databases_to_delete,
222227
):
@@ -253,6 +258,7 @@ def test_create_database_with_default_leader_success(
253258

254259
def test_iam_policy(
255260
not_emulator,
261+
not_experimental_host,
256262
shared_instance,
257263
databases_to_delete,
258264
):
@@ -414,6 +420,7 @@ def test_update_ddl_w_pitr_success(
414420
def test_update_ddl_w_default_leader_success(
415421
not_emulator,
416422
not_postgres,
423+
not_experimental_host,
417424
multiregion_instance,
418425
databases_to_delete,
419426
proto_descriptor_file,
@@ -448,6 +455,7 @@ def test_update_ddl_w_default_leader_success(
448455

449456
def test_create_role_grant_access_success(
450457
not_emulator,
458+
not_experimental_host,
451459
shared_instance,
452460
databases_to_delete,
453461
database_dialect,
@@ -514,6 +522,7 @@ def test_create_role_grant_access_success(
514522

515523
def test_list_database_role_success(
516524
not_emulator,
525+
not_experimental_host,
517526
shared_instance,
518527
databases_to_delete,
519528
database_dialect,
@@ -757,7 +766,11 @@ def test_information_schema_referential_constraints_fkadc(
757766

758767

759768
def test_update_database_success(
760-
not_emulator, shared_database, shared_instance, database_operation_timeout
769+
not_emulator,
770+
not_experimental_host,
771+
shared_database,
772+
shared_instance,
773+
database_operation_timeout,
761774
):
762775
old_protection = shared_database.enable_drop_protection
763776
new_protection = True

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.