Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
13fca93
fix: move return out of finally block in cursor.py
aseering Feb 27, 2026
2d08ad3
chore: replace utcnow() with now(timezone.utc) and fix test collection
aseering Feb 27, 2026
7982c32
fix: ensure required labels are present in metrics exporter
aseering Feb 27, 2026
a6c1bf4
build: drop support for Python 3.9
aseering Feb 27, 2026
4971e59
merge remote/main into fix/test-failures and resolve conflicts
aseering Feb 27, 2026
e3ba501
build: remove obsolete python3.9 sample tests directory
aseering Feb 27, 2026
7e793cc
build: use generic python3 in sample scripts instead of python3.10
aseering Feb 27, 2026
e625c96
build: comprehensive removal of Python 3.9 support
aseering Feb 27, 2026
eb5595b
fix: resolve subsequent test failures from refactoring
aseering Feb 27, 2026
fda030c
build: restore DEFAULT_PYTHON_VERSION and SYSTEM_TEST_PYTHON_VERSIONS…
aseering Feb 27, 2026
737c69e
chore: run black to fix lint errors and make flaky test more resilient
aseering Feb 28, 2026
ef7eff8
Fix utcnow deprecations in samples and improve backup test reliability
aseering Feb 28, 2026
03ca3d9
Fix flake8 and skip flaky concurrent tests
aseering Feb 28, 2026
94dcc23
Fix flake8 and unit test run by adding missing pytest import and runn…
aseering Feb 28, 2026
8cd7f05
Fix flake8: Remove unused unittest import in test_spanner.py
aseering Feb 28, 2026
49066f1
Fix backup test hangs and teardown failures
aseering Feb 28, 2026
fa73c8e
Safeguard cancel_backup loops with timeouts to prevent infinite hangs
aseering Feb 28, 2026
f3ddfe2
Fix test_transaction_batch_update_wo_statements flake due to Aborted …
aseering Mar 2, 2026
5402be6
Fix test_transaction_batch_update_wo_statements flake: Catch Aborted …
aseering Mar 2, 2026
828b9ce
Register pytest noautofixt marker to fix PytestUnknownMarkWarning
aseering Mar 2, 2026
339bd0e
Restore python_requires='>=3.9' to maintain backwards compatibility w…
aseering Mar 2, 2026
f36fc8e
test: assert intentional UserWarnings in autocommit DBAPI tests to qu…
aseering Mar 2, 2026
e7b2753
test: re-enable stable concurrent transaction unit tests, but skip St…
aseering Mar 2, 2026
868d18d
test: re-enable concurrent StreamedResultSet tests
aseering Mar 3, 2026
b76f0f1
fix: correct TIMESTAMP formatting for snippets after migrating to awa…
aseering Mar 3, 2026
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
2 changes: 1 addition & 1 deletion google/cloud/spanner_dbapi/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ def _fetch(self, cursor_statement_type, size=None):
self.transaction_helper.add_fetch_statement_for_retry(
self, rows, exception, is_fetch_all
)
return rows
return rows

def _handle_DQL_with_snapshot(self, snapshot, sql, params):
self._result_set = snapshot.execute_sql(
Expand Down
24 changes: 23 additions & 1 deletion google/cloud/spanner_v1/metrics/metrics_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ def _data_point_to_timeseries_pb(
)
return series

@staticmethod
def _resource_metrics_to_timeseries_pb(
self,
metrics_data: "MetricsData",
) -> List["TimeSeries"]:
"""
Expand All @@ -324,6 +324,28 @@ def _resource_metrics_to_timeseries_pb(
) = CloudMonitoringMetricsExporter._extract_metric_labels(
data_point
)

# Ensure project_id is present in monitored resource labels
if (
MONITORED_RES_LABEL_KEY_PROJECT
not in monitored_resource_labels
):
monitored_resource_labels[
MONITORED_RES_LABEL_KEY_PROJECT
] = self.project_id

# The OpenTelemetry exporter uses the 'spanner_instance_client' resource type,
# which strictly requires both project_id and instance_id. However, some
# Spanner API calls (like creating or listing instances) operate at the
# project level and naturally lack an instance_id. We silently drop these
# metrics here to prevent Cloud Monitoring from rejecting the entire batch
# with a 400 InvalidArgument error.
if (
MONITORED_RES_LABEL_KEY_INSTANCE
not in monitored_resource_labels
):
continue

monitored_resource = CloudMonitoringMetricsExporter._resource_to_monitored_resource_pb(
resource_metric.resource, monitored_resource_labels
)
Expand Down
3 changes: 2 additions & 1 deletion google/cloud/spanner_v1/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@

from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture

_NOW = datetime.datetime.utcnow # unit tests may replace
def _NOW():
return datetime.datetime.now(datetime.timezone.utc) # unit tests may replace


class AbstractSessionPool(object):
Expand Down
4 changes: 2 additions & 2 deletions google/cloud/spanner_v1/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from functools import total_ordering
import time
from datetime import datetime
from datetime import datetime, timezone
from typing import MutableMapping, Optional

from google.api_core.exceptions import Aborted
Expand Down Expand Up @@ -80,7 +80,7 @@ def __init__(self, database, labels=None, database_role=None, is_multiplexed=Fal
self._labels: MutableMapping[str, str] = labels
self._database_role: Optional[str] = database_role
self._is_multiplexed: bool = is_multiplexed
self._last_use_time: datetime = datetime.utcnow()
self._last_use_time: datetime = datetime.now(timezone.utc)

def __lt__(self, other):
return self._session_id < other._session_id
Expand Down
2 changes: 1 addition & 1 deletion tests/system/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1482,7 +1482,7 @@ def test_read_only_dml(self):
def test_staleness(self):
"""Check the DB API `staleness` option."""

before_insert = datetime.datetime.utcnow().replace(tzinfo=UTC)
before_insert = datetime.datetime.now(UTC)
time.sleep(0.25)

self._cursor.execute(
Expand Down
4 changes: 2 additions & 2 deletions tests/system/test_session_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,7 @@ def test_snapshot_read_w_various_staleness(sessions_database):
committed = _set_up_table(sessions_database, row_count)
all_data_rows = list(_row_data(row_count))

before_reads = datetime.datetime.utcnow().replace(tzinfo=UTC)
before_reads = datetime.datetime.now(UTC)

# Test w/ read timestamp
with sessions_database.snapshot(read_timestamp=committed) as read_tx:
Expand All @@ -1761,7 +1761,7 @@ def test_snapshot_read_w_various_staleness(sessions_database):
rows = list(min_read_ts.read(sd.TABLE, sd.COLUMNS, sd.ALL))
sd._check_row_data(rows, all_data_rows)

staleness = datetime.datetime.utcnow().replace(tzinfo=UTC) - before_reads
staleness = datetime.datetime.now(UTC) - before_reads

# Test w/ max staleness
with sessions_database.snapshot(max_staleness=staleness) as max_staleness:
Expand Down
16 changes: 8 additions & 8 deletions tests/unit/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ def test(self):


class Test_retry(unittest.TestCase):
class test_class:
class MockClass:
def test_fxn(self):
return True

Expand All @@ -877,7 +877,7 @@ def test_retry_on_error(self):
from google.cloud.spanner_v1._helpers import _retry
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
InternalServerError("testing"),
NotFound("testing"),
Expand All @@ -893,7 +893,7 @@ def test_retry_allowed_exceptions(self):
from google.cloud.spanner_v1._helpers import _retry
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
NotFound("testing"),
InternalServerError("testing"),
Expand All @@ -914,7 +914,7 @@ def test_retry_count(self):
from google.cloud.spanner_v1._helpers import _retry
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
InternalServerError("testing"),
InternalServerError("testing"),
Expand All @@ -930,7 +930,7 @@ def test_check_rst_stream_error(self):
from google.cloud.spanner_v1._helpers import _retry, _check_rst_stream_error
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
InternalServerError("Received unexpected EOS on DATA frame from server"),
InternalServerError("RST_STREAM"),
Expand All @@ -951,7 +951,7 @@ def test_retry_on_aborted_exception_with_success_after_first_aborted_retry(self)
from google.cloud.spanner_v1._helpers import _retry_on_aborted_exception
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
Aborted("aborted exception", errors=("Aborted error")),
"true",
Expand All @@ -970,7 +970,7 @@ def test_retry_on_aborted_exception_with_success_after_three_retries(self):
from google.cloud.spanner_v1._helpers import _retry_on_aborted_exception
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
# Case where aborted exception is thrown after other generic exceptions
aborted = Aborted("aborted exception", errors=["Aborted error"])
test_api.test_fxn.side_effect = [
Expand All @@ -994,7 +994,7 @@ def test_retry_on_aborted_exception_raises_aborted_if_deadline_expires(self):
from google.cloud.spanner_v1._helpers import _retry_on_aborted_exception
import functools

test_api = mock.create_autospec(self.test_class)
test_api = mock.create_autospec(self.MockClass)
test_api.test_fxn.side_effect = [
Aborted("aborted exception", errors=("Aborted error")),
"true",
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def _make_timestamp():
import datetime
from google.cloud._helpers import UTC

return datetime.datetime.utcnow().replace(tzinfo=UTC)
return datetime.datetime.now(UTC)


class TestBackup(_BaseTest):
Expand Down Expand Up @@ -357,8 +357,7 @@ def test_create_success(self):
api.create_backup.return_value = op_future

instance = _Instance(self.INSTANCE_NAME, client=client)
version_timestamp = datetime.utcnow() - timedelta(minutes=5)
version_timestamp = version_timestamp.replace(tzinfo=timezone.utc)
version_timestamp = datetime.now(timezone.utc) - timedelta(minutes=5)
expire_timestamp = self._make_timestamp()
encryption_config = {"encryption_type": 3, "kms_key_name": "key_name"}
backup = self._make_one(
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/test_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def test_commit_grpc_error(self, mock_region):
return_value="global",
)
def test_commit_ok(self, mock_region):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
response = CommitResponse(commit_timestamp=now_pb)
database = _Database()
Expand Down Expand Up @@ -321,7 +321,7 @@ def _test_commit_with_options(
isolation_level=TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
read_lock_mode=TransactionOptions.ReadWrite.ReadLockMode.READ_LOCK_MODE_UNSPECIFIED,
):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
response = CommitResponse(commit_timestamp=now_pb)
database = _Database()
Expand Down Expand Up @@ -513,7 +513,7 @@ def test_commit_w_isolation_level_and_read_lock_mode(self, mock_region):
return_value="global",
)
def test_context_mgr_already_committed(self, mock_region):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
database = _Database()
api = database.spanner_api = _FauxSpannerAPI()
session = _Session(database)
Expand All @@ -531,7 +531,7 @@ def test_context_mgr_already_committed(self, mock_region):
return_value="global",
)
def test_context_mgr_success(self, mock_region):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
response = CommitResponse(commit_timestamp=now_pb)
database = _Database()
Expand Down Expand Up @@ -582,7 +582,7 @@ def test_context_mgr_success(self, mock_region):
return_value="global",
)
def test_context_mgr_failure(self, mock_region):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
response = CommitResponse(commit_timestamp=now_pb)
database = _Database()
Expand Down Expand Up @@ -671,7 +671,7 @@ def _test_batch_write_with_request_options(
exclude_txn_from_change_streams=False,
enable_end_to_end_tracing=False,
):
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
status_pb = Status(code=200)
response = BatchWriteResponse(
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _make_timestamp():
import datetime
from google.cloud._helpers import UTC

return datetime.datetime.utcnow().replace(tzinfo=UTC)
return datetime.datetime.now(UTC)

@staticmethod
def _make_duration(seconds=1, microseconds=0):
Expand Down Expand Up @@ -1580,7 +1580,7 @@ def test_snapshot_w_read_timestamp_and_multi_use(self):
from google.cloud.spanner_v1.database import SnapshotCheckout
from google.cloud.spanner_v1.snapshot import Snapshot

now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
client = _Client()
instance = _Instance(self.INSTANCE_NAME, client=client)
pool = _Pool()
Expand Down Expand Up @@ -2155,7 +2155,7 @@ def test_context_mgr_success(self):
from google.cloud._helpers import _datetime_to_pb_timestamp
from google.cloud.spanner_v1.batch import Batch

now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
response = CommitResponse(commit_timestamp=now_pb)
database = _Database(self.DATABASE_NAME)
Expand Down Expand Up @@ -2206,7 +2206,7 @@ def test_context_mgr_w_commit_stats_success(self):
from google.cloud._helpers import _datetime_to_pb_timestamp
from google.cloud.spanner_v1.batch import Batch

now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
commit_stats = CommitResponse.CommitStats(mutation_count=4)
response = CommitResponse(commit_timestamp=now_pb, commit_stats=commit_stats)
Expand Down Expand Up @@ -2358,7 +2358,7 @@ def test_ctor_w_read_timestamp_and_multi_use(self):
from google.cloud._helpers import UTC
from google.cloud.spanner_v1.snapshot import Snapshot

now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
database = _Database(self.DATABASE_NAME)
session = _Session(database)
pool = database._pool = _Pool()
Expand Down Expand Up @@ -3358,7 +3358,7 @@ def test_context_mgr_success(self):
from google.cloud.spanner_v1.batch import MutationGroups
from google.rpc.status_pb2 import Status

now = datetime.datetime.utcnow().replace(tzinfo=UTC)
now = datetime.datetime.now(UTC)
now_pb = _datetime_to_pb_timestamp(now)
status_pb = Status(code=200)
response = BatchWriteResponse(
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ def test_backup_factory_explicit(self):
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
BACKUP_ID = "backup-id"
DATABASE_NAME = "database-name"
timestamp = datetime.datetime.utcnow().replace(tzinfo=UTC)
timestamp = datetime.datetime.now(UTC)
encryption_config = CreateBackupEncryptionConfig(
encryption_type=CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
kms_key_name="kms_key_name",
Expand Down
27 changes: 19 additions & 8 deletions tests/unit/test_metrics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2025 Google LLC
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,7 +31,7 @@
# pytest.importorskip("opentelemetry.semconv.attributes.otel_attributes")


class TestCredentials(Credentials):
class MockCredentials(Credentials):
@property
def expired(self):
return False
Expand Down Expand Up @@ -60,12 +60,23 @@ def patched_client(monkeypatch):
if SpannerMetricsTracerFactory._metrics_tracer_factory is not None:
SpannerMetricsTracerFactory._metrics_tracer_factory = None

client = Client(
project="test",
credentials=TestCredentials(),
# client_options={"api_endpoint": "none"}
)
yield client
# Reset the global flag to ensure metrics initialization runs
from google.cloud.spanner_v1 import client as client_module

client_module._metrics_monitor_initialized = False

with patch(
"google.cloud.spanner_v1.metrics.metrics_exporter.MetricServiceClient"
), patch(
"google.cloud.spanner_v1.metrics.metrics_exporter.CloudMonitoringMetricsExporter"
), patch(
"opentelemetry.sdk.metrics.export.PeriodicExportingMetricReader"
):
client = Client(
project="test",
credentials=MockCredentials(),
)
yield client

# Resetting
metrics.set_meter_provider(metrics.NoOpMeterProvider())
Expand Down
Loading