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

Commit 4f8af2f

Browse files
committed
dataclass for default transaction options
1 parent b350141 commit 4f8af2f

File tree

10 files changed

+50
-60
lines changed

10 files changed

+50
-60
lines changed

google/cloud/spanner_v1/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@
6464
from .types.type import TypeAnnotationCode
6565
from .types.type import TypeCode
6666
from .data_types import JsonObject
67-
from .transaction import BatchTransactionId
67+
from .transaction import (
68+
BatchTransactionId,
69+
DefaultTransactionOptions
70+
)
6871

6972
from google.cloud.spanner_v1 import param_types
7073
from google.cloud.spanner_v1.client import Client
@@ -149,4 +152,5 @@
149152
"SpannerClient",
150153
"SpannerAsyncClient",
151154
"BatchTransactionId",
155+
"DefaultTransactionOptions"
152156
)

google/cloud/spanner_v1/client.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from google.auth.credentials import AnonymousCredentials
3232
import google.api_core.client_options
3333
from google.cloud.client import ClientWithProject
34+
from typing import Optional
3435

3536

3637
from google.cloud.spanner_admin_database_v1 import DatabaseAdminClient
@@ -45,6 +46,7 @@
4546
from google.cloud.spanner_admin_instance_v1 import ListInstancesRequest
4647
from google.cloud.spanner_v1 import __version__
4748
from google.cloud.spanner_v1 import ExecuteSqlRequest
49+
from google.cloud.spanner_v1 import DefaultTransactionOptions
4850
from google.cloud.spanner_v1._helpers import _merge_query_options
4951
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
5052
from google.cloud.spanner_v1.instance import Instance
@@ -161,11 +163,10 @@ class Client(ClientWithProject):
161163
or you can use the environment variable `SPANNER_ENABLE_EXTENDED_TRACING=<boolean>`
162164
to control it.
163165
164-
:type default_transaction_options: :class:`~google.cloud.spanner_v1.TransactionOptions`
166+
:type default_transaction_options: :class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
165167
or :class:`dict`
166-
:param default_transaction_options: (Optional) Default options to use for all read/write transactions.
167-
Any fields other than `isolation_level` will be ignored.
168-
168+
:param default_transaction_options: (Optional) Default options to use for all transactions.
169+
169170
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
170171
and ``admin`` are :data:`True`
171172
"""
@@ -187,7 +188,7 @@ def __init__(
187188
route_to_leader_enabled=True,
188189
directed_read_options=None,
189190
observability_options=None,
190-
default_transaction_options=None,
191+
default_transaction_options: Optional[DefaultTransactionOptions] =None,
191192
):
192193
self._emulator_host = _get_spanner_emulator_host()
193194

@@ -249,6 +250,11 @@ def __init__(
249250
self._route_to_leader_enabled = route_to_leader_enabled
250251
self._directed_read_options = directed_read_options
251252
self._observability_options = observability_options
253+
if default_transaction_options is None:
254+
default_transaction_options = DefaultTransactionOptions()
255+
elif not isinstance(default_transaction_options, DefaultTransactionOptions):
256+
raise TypeError("default_transaction_options must be an instance of DefaultTransactionOptions")
257+
252258
self._default_transaction_options = default_transaction_options
253259

254260
@property
@@ -345,9 +351,9 @@ def default_transaction_options(self):
345351
"""Getter for default_transaction_options.
346352
347353
:rtype:
348-
:class:`~google.cloud.spanner_v1.TransactionOptions`
354+
:class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
349355
or :class:`dict`
350-
:returns: The default transaction options that are used by this client for all read/write transactions.
356+
:returns: The default transaction options that are used by this client for all transactions.
351357
"""
352358
return self._default_transaction_options
353359

@@ -498,11 +504,15 @@ def directed_read_options(self, directed_read_options):
498504
self._directed_read_options = directed_read_options
499505

500506
@default_transaction_options.setter
501-
def default_transaction_options(self, default_transaction_options):
507+
def default_transaction_options(self, default_transaction_options:DefaultTransactionOptions):
502508
"""Sets default_transaction_options for the client
503-
:type default_transaction_options: :class:`~google.cloud.spanner_v1.TransactionOptions`
509+
:type default_transaction_options: :class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
504510
or :class:`dict`
505-
:param default_transaction_options: Default options to use for all read/write transactions.
506-
Any fields other than `isolation_level` will be ignored.
511+
:param default_transaction_options: Default options to use for transactions.
507512
"""
513+
if default_transaction_options is None:
514+
default_transaction_options = DefaultTransactionOptions()
515+
elif not isinstance(default_transaction_options, DefaultTransactionOptions):
516+
raise TypeError("default_transaction_options must be an instance of DefaultTransactionOptions")
517+
508518
self._default_transaction_options = default_transaction_options

google/cloud/spanner_v1/database.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from google.cloud.spanner_v1 import TypeCode
4747
from google.cloud.spanner_v1 import TransactionSelector
4848
from google.cloud.spanner_v1 import TransactionOptions
49+
from google.cloud.spanner_v1 import DefaultTransactionOptions
4950
from google.cloud.spanner_v1 import RequestOptions
5051
from google.cloud.spanner_v1 import SpannerClient
5152
from google.cloud.spanner_v1._helpers import _merge_query_options
@@ -183,7 +184,7 @@ def __init__(
183184
self._enable_drop_protection = enable_drop_protection
184185
self._reconciling = False
185186
self._directed_read_options = self._instance._client.directed_read_options
186-
self.default_transaction_options = (
187+
self.default_transaction_options: DefaultTransactionOptions = (
187188
self._instance._client.default_transaction_options
188189
)
189190
self._proto_descriptors = proto_descriptors
@@ -823,7 +824,7 @@ def batch(
823824

824825
# Set isolation level
825826
if isolation_level is None:
826-
isolation_level = self.get_default_isolation_level()
827+
isolation_level = self.default_transaction_options.isolation_level
827828
return BatchCheckout(
828829
self,
829830
request_options,
@@ -1144,23 +1145,6 @@ def set_iam_policy(self, policy):
11441145
response = api.set_iam_policy(request=request, metadata=metadata)
11451146
return response
11461147

1147-
def get_default_isolation_level(self):
1148-
"""
1149-
Returns the isolation level set in default transaction options when creating
1150-
the SpannerClient.
1151-
"""
1152-
if isinstance(self.default_transaction_options, dict):
1153-
return self.default_transaction_options.get(
1154-
"isolation_level",
1155-
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
1156-
)
1157-
1158-
return getattr(
1159-
self.default_transaction_options,
1160-
"isolation_level",
1161-
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
1162-
)
1163-
11641148
@property
11651149
def observability_options(self):
11661150
"""

google/cloud/spanner_v1/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def run_in_transaction(self, func, *args, **kw):
465465
isolation_level = kw.pop("isolation_level", None)
466466

467467
if isolation_level is None:
468-
isolation_level = self._database.get_default_isolation_level()
468+
isolation_level = self._database.default_transaction_options.isolation_level
469469
attempts = 0
470470

471471
observability_options = getattr(self._database, "observability_options", None)

google/cloud/spanner_v1/transaction.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,3 +655,7 @@ class BatchTransactionId:
655655
transaction_id: str
656656
session_id: str
657657
read_timestamp: Any
658+
659+
@dataclass
660+
class DefaultTransactionOptions:
661+
isolation_level: TransactionOptions.IsolationLevel = TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED

tests/unit/test_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
import os
1818
import mock
19-
from google.cloud.spanner_v1 import DirectedReadOptions
19+
from google.cloud.spanner_v1 import (
20+
DirectedReadOptions,
21+
DefaultTransactionOptions
22+
)
23+
2024

2125

2226
def _make_credentials():
@@ -53,7 +57,7 @@ class TestClient(unittest.TestCase):
5357
"auto_failover_disabled": True,
5458
},
5559
}
56-
DEFAULT_TRANSACTION_OPTIONS = {"isolation_level": "SERIALIZABLE"}
60+
DEFAULT_TRANSACTION_OPTIONS = DefaultTransactionOptions(isolation_level="SERIALIZABLE")
5761

5862
def _get_target_class(self):
5963
from google.cloud import spanner

tests/unit/test_database.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from google.api_core.retry import Retry
2626
from google.protobuf.field_mask_pb2 import FieldMask
2727

28-
from google.cloud.spanner_v1 import RequestOptions, DirectedReadOptions
28+
from google.cloud.spanner_v1 import RequestOptions, DirectedReadOptions, DefaultTransactionOptions
2929

3030
DML_WO_PARAM = """
3131
DELETE FROM citizens
@@ -3116,7 +3116,7 @@ def __init__(
31163116
project=TestDatabase.PROJECT_ID,
31173117
route_to_leader_enabled=True,
31183118
directed_read_options=None,
3119-
default_transaction_options=None,
3119+
default_transaction_options=DefaultTransactionOptions(),
31203120
):
31213121
from google.cloud.spanner_v1 import ExecuteSqlRequest
31223122

@@ -3157,7 +3157,7 @@ def __init__(self, name, instance=None):
31573157
from logging import Logger
31583158

31593159
self.logger = mock.create_autospec(Logger, instance=True)
3160-
self._directed_read_options = None
3160+
self._directed_read_options = DefaultTransactionOptions()
31613161
self.default_transaction_options = None
31623162

31633163

tests/unit/test_instance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import unittest
1616
import mock
17+
from google.cloud.spanner_v1 import DefaultTransactionOptions
1718

1819

1920
class TestInstance(unittest.TestCase):
@@ -1019,7 +1020,7 @@ def __init__(self, project, timeout_seconds=None):
10191020
self.timeout_seconds = timeout_seconds
10201021
self.route_to_leader_enabled = True
10211022
self.directed_read_options = None
1022-
self.default_transaction_options = None
1023+
self.default_transaction_options = DefaultTransactionOptions()
10231024

10241025
def copy(self):
10251026
from copy import deepcopy

tests/unit/test_session.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from google.api_core.exceptions import Unknown, Aborted, NotFound, Cancelled
4949
from google.protobuf.struct_pb2 import Struct, Value
5050
from google.cloud.spanner_v1.batch import Batch
51+
from google.cloud.spanner_v1 import DefaultTransactionOptions
5152

5253

5354
def _make_rpc_error(error_cls, trailing_metadata=None):
@@ -84,7 +85,7 @@ def _make_one(self, *args, **kwargs):
8485

8586
@staticmethod
8687
def _make_database(
87-
name=DATABASE_NAME, database_role=None, default_transaction_options=None
88+
name=DATABASE_NAME, database_role=None, default_transaction_options=DefaultTransactionOptions()
8889
):
8990
database = mock.create_autospec(Database, instance=True)
9091
database.name = name
@@ -93,25 +94,6 @@ def _make_database(
9394
database._route_to_leader_enabled = True
9495
database.default_transaction_options = default_transaction_options
9596

96-
# Define side_effect function to use injected default_transaction_options
97-
def get_default_isolation_level_side_effect():
98-
if isinstance(database.default_transaction_options, dict):
99-
return database.default_transaction_options.get(
100-
"isolation_level",
101-
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
102-
)
103-
104-
return getattr(
105-
database.default_transaction_options,
106-
"isolation_level",
107-
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
108-
)
109-
110-
# Mock get_default_isolation_level method
111-
database.get_default_isolation_level = mock.Mock(
112-
side_effect=get_default_isolation_level_side_effect
113-
)
114-
11597
return database
11698

11799
@staticmethod
@@ -1794,7 +1776,7 @@ def test_run_in_transaction_w_isolation_level_at_client(self):
17941776
gax_api = self._make_spanner_api()
17951777
gax_api.begin_transaction.return_value = TransactionPB(id=b"FACEDACE")
17961778
database = self._make_database(
1797-
default_transaction_options={"isolation_level": "SERIALIZABLE"}
1779+
default_transaction_options=DefaultTransactionOptions(isolation_level="SERIALIZABLE")
17981780
)
17991781
database.spanner_api = gax_api
18001782
session = self._make_one(database)

tests/unit/test_spanner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
ExecuteBatchDmlRequest,
3333
ExecuteBatchDmlResponse,
3434
param_types,
35+
DefaultTransactionOptions,
3536
)
3637
from google.cloud.spanner_v1.types import transaction as transaction_type
3738
from google.cloud.spanner_v1.keyset import KeySet
@@ -1090,7 +1091,7 @@ def __init__(self):
10901091

10911092
self._query_options = ExecuteSqlRequest.QueryOptions(optimizer_version="1")
10921093
self.directed_read_options = None
1093-
self.default_transaction_options = None
1094+
self.default_transaction_options = DefaultTransactionOptions()
10941095

10951096

10961097
class _Instance(object):

0 commit comments

Comments
 (0)