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

Commit 20c8e81

Browse files
committed
dataclass for default transaction options
1 parent 840626b commit 20c8e81

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
@@ -165,11 +167,10 @@ class Client(ClientWithProject):
165167
or you can use the environment variable `SPANNER_ENABLE_END_TO_END_TRACING=<boolean>`
166168
to control it.
167169
168-
:type default_transaction_options: :class:`~google.cloud.spanner_v1.TransactionOptions`
170+
:type default_transaction_options: :class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
169171
or :class:`dict`
170-
:param default_transaction_options: (Optional) Default options to use for all read/write transactions.
171-
Any fields other than `isolation_level` will be ignored.
172-
172+
:param default_transaction_options: (Optional) Default options to use for all transactions.
173+
173174
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
174175
and ``admin`` are :data:`True`
175176
"""
@@ -191,7 +192,7 @@ def __init__(
191192
route_to_leader_enabled=True,
192193
directed_read_options=None,
193194
observability_options=None,
194-
default_transaction_options=None,
195+
default_transaction_options: Optional[DefaultTransactionOptions] =None,
195196
):
196197
self._emulator_host = _get_spanner_emulator_host()
197198

@@ -253,6 +254,11 @@ def __init__(
253254
self._route_to_leader_enabled = route_to_leader_enabled
254255
self._directed_read_options = directed_read_options
255256
self._observability_options = observability_options
257+
if default_transaction_options is None:
258+
default_transaction_options = DefaultTransactionOptions()
259+
elif not isinstance(default_transaction_options, DefaultTransactionOptions):
260+
raise TypeError("default_transaction_options must be an instance of DefaultTransactionOptions")
261+
256262
self._default_transaction_options = default_transaction_options
257263

258264
@property
@@ -349,9 +355,9 @@ def default_transaction_options(self):
349355
"""Getter for default_transaction_options.
350356
351357
:rtype:
352-
:class:`~google.cloud.spanner_v1.TransactionOptions`
358+
:class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
353359
or :class:`dict`
354-
:returns: The default transaction options that are used by this client for all read/write transactions.
360+
:returns: The default transaction options that are used by this client for all transactions.
355361
"""
356362
return self._default_transaction_options
357363

@@ -502,11 +508,15 @@ def directed_read_options(self, directed_read_options):
502508
self._directed_read_options = directed_read_options
503509

504510
@default_transaction_options.setter
505-
def default_transaction_options(self, default_transaction_options):
511+
def default_transaction_options(self, default_transaction_options:DefaultTransactionOptions):
506512
"""Sets default_transaction_options for the client
507-
:type default_transaction_options: :class:`~google.cloud.spanner_v1.TransactionOptions`
513+
:type default_transaction_options: :class:`~google.cloud.spanner_v1.DefaultTransactionOptions`
508514
or :class:`dict`
509-
:param default_transaction_options: Default options to use for all read/write transactions.
510-
Any fields other than `isolation_level` will be ignored.
515+
:param default_transaction_options: Default options to use for transactions.
511516
"""
517+
if default_transaction_options is None:
518+
default_transaction_options = DefaultTransactionOptions()
519+
elif not isinstance(default_transaction_options, DefaultTransactionOptions):
520+
raise TypeError("default_transaction_options must be an instance of DefaultTransactionOptions")
521+
512522
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
@@ -824,7 +825,7 @@ def batch(
824825

825826
# Set isolation level
826827
if isolation_level is None:
827-
isolation_level = self.get_default_isolation_level()
828+
isolation_level = self.default_transaction_options.isolation_level
828829
return BatchCheckout(
829830
self,
830831
request_options,
@@ -1145,23 +1146,6 @@ def set_iam_policy(self, policy):
11451146
response = api.set_iam_policy(request=request, metadata=metadata)
11461147
return response
11471148

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

google/cloud/spanner_v1/session.py

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

472472
if isolation_level is None:
473-
isolation_level = self._database.get_default_isolation_level()
473+
isolation_level = self._database.default_transaction_options.isolation_level
474474
attempts = 0
475475

476476
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
@@ -664,3 +664,7 @@ class BatchTransactionId:
664664
transaction_id: str
665665
session_id: str
666666
read_timestamp: Any
667+
668+
@dataclass
669+
class DefaultTransactionOptions:
670+
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)