Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 84878ba

Browse files
authored
Merge branch 'main' into support-db-roles-in-connect
2 parents 8ac7ba8 + 686bda6 commit 84878ba

30 files changed

+479
-128
lines changed

.github/workflows/mock_server_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
pull_request:
66
name: Run Spanner tests against an in-mem mock server
77
jobs:
8-
system-tests:
8+
mock-server-tests:
99
runs-on: ubuntu-latest
1010

1111
steps:

.github/workflows/presubmit.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
pull_request:
6+
name: Presubmit checks
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
jobs:
11+
lint:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
- name: Setup Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: 3.8
21+
- name: Install nox
22+
run: python -m pip install nox
23+
- name: Check formatting
24+
run: nox -s lint
25+
units:
26+
runs-on: ubuntu-latest
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
31+
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
- name: Setup Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: ${{matrix.python}}
39+
- name: Install nox
40+
run: python -m pip install nox
41+
- name: Run unit tests
42+
run: nox -s unit-${{matrix.python}}

.kokoro/presubmit/presubmit.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Format: //devtools/kokoro/config/proto/build.proto
22

3-
# Disable system tests.
3+
# Only run a subset of all nox sessions
44
env_vars: {
5-
key: "RUN_SYSTEM_TESTS"
6-
value: "false"
5+
key: "NOX_SESSION"
6+
value: "unit-3.8 unit-3.12 cover docs docfx"
77
}

google/cloud/spanner_dbapi/connection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
from google.api_core.exceptions import Aborted
1919
from google.api_core.gapic_v1.client_info import ClientInfo
20+
from google.auth.credentials import AnonymousCredentials
21+
2022
from google.cloud import spanner_v1 as spanner
2123
from google.cloud.spanner_dbapi import partition_helper
2224
from google.cloud.spanner_dbapi.batch_dml_executor import BatchMode, BatchDmlExecutor
@@ -789,11 +791,15 @@ def connect(
789791
route_to_leader_enabled=route_to_leader_enabled,
790792
)
791793
else:
794+
client_options = None
795+
if isinstance(credentials, AnonymousCredentials):
796+
client_options = kwargs.get("client_options")
792797
client = spanner.Client(
793798
project=project,
794799
credentials=credentials,
795800
client_info=client_info,
796801
route_to_leader_enabled=route_to_leader_enabled,
802+
client_options=client_options,
797803
)
798804
else:
799805
if project is not None and client.project != project:

google/cloud/spanner_dbapi/transaction_helper.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def add_execute_statement_for_retry(
162162
self._last_statement_details_per_cursor[cursor] = last_statement_result_details
163163
self._statement_result_details_list.append(last_statement_result_details)
164164

165-
def retry_transaction(self):
165+
def retry_transaction(self, default_retry_delay=None):
166166
"""Retry the aborted transaction.
167167
168168
All the statements executed in the original transaction
@@ -202,7 +202,9 @@ def retry_transaction(self):
202202
raise RetryAborted(RETRY_ABORTED_ERROR, ex)
203203
return
204204
except Aborted as ex:
205-
delay = _get_retry_delay(ex.errors[0], attempt)
205+
delay = _get_retry_delay(
206+
ex.errors[0], attempt, default_retry_delay=default_retry_delay
207+
)
206208
if delay:
207209
time.sleep(delay)
208210

google/cloud/spanner_v1/_helpers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ def _metadata_with_prefix(prefix, **kw):
510510
def _retry_on_aborted_exception(
511511
func,
512512
deadline,
513+
default_retry_delay=None,
513514
):
514515
"""
515516
Handles retry logic for Aborted exceptions, considering the deadline.
@@ -520,7 +521,12 @@ def _retry_on_aborted_exception(
520521
attempts += 1
521522
return func()
522523
except Aborted as exc:
523-
_delay_until_retry(exc, deadline=deadline, attempts=attempts)
524+
_delay_until_retry(
525+
exc,
526+
deadline=deadline,
527+
attempts=attempts,
528+
default_retry_delay=default_retry_delay,
529+
)
524530
continue
525531

526532

@@ -608,7 +614,7 @@ def _metadata_with_span_context(metadata: List[Tuple[str, str]], **kw) -> None:
608614
inject(setter=OpenTelemetryContextSetter(), carrier=metadata)
609615

610616

611-
def _delay_until_retry(exc, deadline, attempts):
617+
def _delay_until_retry(exc, deadline, attempts, default_retry_delay=None):
612618
"""Helper for :meth:`Session.run_in_transaction`.
613619
614620
Detect retryable abort, and impose server-supplied delay.
@@ -628,15 +634,15 @@ def _delay_until_retry(exc, deadline, attempts):
628634
if now >= deadline:
629635
raise
630636

631-
delay = _get_retry_delay(cause, attempts)
637+
delay = _get_retry_delay(cause, attempts, default_retry_delay=default_retry_delay)
632638
if delay is not None:
633639
if now + delay > deadline:
634640
raise
635641

636642
time.sleep(delay)
637643

638644

639-
def _get_retry_delay(cause, attempts):
645+
def _get_retry_delay(cause, attempts, default_retry_delay=None):
640646
"""Helper for :func:`_delay_until_retry`.
641647
642648
:type exc: :class:`grpc.Call`
@@ -658,6 +664,8 @@ def _get_retry_delay(cause, attempts):
658664
retry_info.ParseFromString(retry_info_pb)
659665
nanos = retry_info.retry_delay.nanos
660666
return retry_info.retry_delay.seconds + nanos / 1.0e9
667+
if default_retry_delay is not None:
668+
return default_retry_delay
661669

662670
return 2**attempts + random.random()
663671

@@ -699,6 +707,10 @@ def __radd__(self, n):
699707
"""
700708
return self.__add__(n)
701709

710+
def reset(self):
711+
with self.__lock:
712+
self.__value = 0
713+
702714

703715
def _metadata_with_request_id(*args, **kwargs):
704716
return with_request_id(*args, **kwargs)

google/cloud/spanner_v1/batch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,11 @@ def commit(
257257
deadline = time.time() + kwargs.get(
258258
"timeout_secs", DEFAULT_RETRY_TIMEOUT_SECS
259259
)
260+
default_retry_delay = kwargs.get("default_retry_delay", None)
260261
response = _retry_on_aborted_exception(
261262
method,
262263
deadline=deadline,
264+
default_retry_delay=default_retry_delay,
263265
)
264266
self.committed = response.commit_timestamp
265267
self.commit_stats = response.commit_stats

google/cloud/spanner_v1/client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
except ImportError: # pragma: NO COVER
7171
HAS_GOOGLE_CLOUD_MONITORING_INSTALLED = False
7272

73+
from google.cloud.spanner_v1._helpers import AtomicCounter
7374

7475
_CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__)
7576
EMULATOR_ENV_VAR = "SPANNER_EMULATOR_HOST"
@@ -182,6 +183,8 @@ class Client(ClientWithProject):
182183
SCOPE = (SPANNER_ADMIN_SCOPE,)
183184
"""The scopes required for Google Cloud Spanner."""
184185

186+
NTH_CLIENT = AtomicCounter()
187+
185188
def __init__(
186189
self,
187190
project=None,
@@ -241,7 +244,9 @@ def __init__(
241244
meter_provider = MeterProvider(
242245
metric_readers=[
243246
PeriodicExportingMetricReader(
244-
CloudMonitoringMetricsExporter(),
247+
CloudMonitoringMetricsExporter(
248+
project_id=project, credentials=credentials
249+
),
245250
export_interval_millis=METRIC_EXPORT_INTERVAL_MS,
246251
)
247252
]
@@ -261,6 +266,12 @@ def __init__(
261266
"default_transaction_options must be an instance of DefaultTransactionOptions"
262267
)
263268
self._default_transaction_options = default_transaction_options
269+
self._nth_client_id = Client.NTH_CLIENT.increment()
270+
self._nth_request = AtomicCounter(0)
271+
272+
@property
273+
def _next_nth_request(self):
274+
return self._nth_request.increment()
264275

265276
@property
266277
def credentials(self):

google/cloud/spanner_v1/metrics/metrics_exporter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import Optional, List, Union, NoReturn, Tuple, Dict
2727

2828
import google.auth
29+
from google.auth import credentials as ga_credentials
2930
from google.api.distribution_pb2 import ( # pylint: disable=no-name-in-module
3031
Distribution,
3132
)
@@ -111,6 +112,7 @@ def __init__(
111112
self,
112113
project_id: Optional[str] = None,
113114
client: Optional["MetricServiceClient"] = None,
115+
credentials: Optional[ga_credentials.Credentials] = None,
114116
):
115117
"""Initialize a custom exporter to send metrics for the Spanner Service Metrics."""
116118
# Default preferred_temporality is all CUMULATIVE so need to customize
@@ -121,6 +123,7 @@ def __init__(
121123
transport=MetricServiceGrpcTransport(
122124
channel=MetricServiceGrpcTransport.create_channel(
123125
options=_OPTIONS,
126+
credentials=credentials,
124127
)
125128
)
126129
)

google/cloud/spanner_v1/request_id_header.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ def generate_rand_uint64():
3737

3838
def with_request_id(client_id, channel_id, nth_request, attempt, other_metadata=[]):
3939
req_id = f"{REQ_ID_VERSION}.{REQ_RAND_PROCESS_ID}.{client_id}.{channel_id}.{nth_request}.{attempt}"
40-
all_metadata = other_metadata.copy()
40+
all_metadata = (other_metadata or []).copy()
4141
all_metadata.append((REQ_ID_HEADER_KEY, req_id))
4242
return all_metadata

0 commit comments

Comments
 (0)