feat: support transaction isolation level in dbapi · googleapis/python-spanner@fb43fd6 · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

Commit fb43fd6

Browse files
committed
feat: support transaction isolation level in dbapi
1 parent 33f3750 commit fb43fd6

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

google/cloud/spanner_dbapi/connection.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from google.cloud.spanner_dbapi.parsed_statement import ParsedStatement, Statement
3030
from google.cloud.spanner_dbapi.transaction_helper import TransactionRetryHelper
3131
from google.cloud.spanner_dbapi.cursor import Cursor
32-
from google.cloud.spanner_v1 import RequestOptions
32+
from google.cloud.spanner_v1 import RequestOptions, TransactionOptions
3333
from google.cloud.spanner_v1.snapshot import Snapshot
3434

3535
from google.cloud.spanner_dbapi.exceptions import (
@@ -283,6 +283,32 @@ def transaction_tag(self, value):
283283
"""
284284
self._connection_variables["transaction_tag"] = value
285285

286+
@property
287+
def isolation_level(self):
288+
"""The default isolation level that is used for all read/write
289+
transactions on this `Connection`.
290+
291+
Returns:
292+
google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel:
293+
The isolation level that is used for read/write transactions on
294+
this `Connection`.
295+
"""
296+
return self._connection_variables.get(
297+
"isolation_level",
298+
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED)
299+
300+
@isolation_level.setter
301+
def isolation_level(self, value: TransactionOptions.IsolationLevel):
302+
"""Sets the isolation level that is used for all read/write
303+
transactions on this `Connection`.
304+
305+
Args:
306+
value (google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel):
307+
The isolation level for all read/write transactions on this
308+
`Connection`.
309+
"""
310+
self._connection_variables["isolation_level"] = value
311+
286312
@property
287313
def staleness(self):
288314
"""Current read staleness option value of this `Connection`.
@@ -363,6 +389,7 @@ def transaction_checkout(self):
363389
if not self._spanner_transaction_started:
364390
self._transaction = self._session_checkout().transaction()
365391
self._transaction.transaction_tag = self.transaction_tag
392+
self._transaction.isolation_level = self.isolation_level
366393
self.transaction_tag = None
367394
self._snapshot = None
368395
self._spanner_transaction_started = True
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2025 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from google.cloud.spanner_dbapi import Connection
16+
from google.cloud.spanner_v1 import (
17+
BeginTransactionRequest, TransactionOptions,
18+
)
19+
from tests.mockserver_tests.mock_server_test_base import (
20+
MockServerTestBase,
21+
add_update_count,
22+
)
23+
24+
25+
class TestDbapiIsolationLevel(MockServerTestBase):
26+
@classmethod
27+
def setup_class(cls):
28+
super().setup_class()
29+
add_update_count("insert into singers (id, name) values (1, 'Some Singer')", 1)
30+
31+
def test_isolation_level_default(self):
32+
connection = Connection(self.instance, self.database)
33+
with connection.cursor() as cursor:
34+
cursor.execute("insert into singers (id, name) values (1, 'Some Singer')")
35+
self.assertEqual(1, cursor.rowcount)
36+
connection.commit()
37+
begin_requests = list(
38+
filter(
39+
lambda msg: isinstance(msg, BeginTransactionRequest),
40+
self.spanner_service.requests,
41+
)
42+
)
43+
self.assertEqual(1, len(begin_requests))
44+
self.assertEqual(
45+
begin_requests[0].options.isolation_level,
46+
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED)
47+
48+
def test_custom_isolation_level(self):
49+
connection = Connection(self.instance, self.database)
50+
for level in [
51+
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
52+
TransactionOptions.IsolationLevel.REPEATABLE_READ,
53+
TransactionOptions.IsolationLevel.SERIALIZABLE,
54+
]:
55+
connection.isolation_level = level
56+
with connection.cursor() as cursor:
57+
cursor.execute("insert into singers (id, name) values (1, 'Some Singer')")
58+
self.assertEqual(1, cursor.rowcount)
59+
connection.commit()
60+
begin_requests = list(
61+
filter(
62+
lambda msg: isinstance(msg, BeginTransactionRequest),
63+
self.spanner_service.requests,
64+
)
65+
)
66+
self.assertEqual(1, len(begin_requests))
67+
self.assertEqual(begin_requests[0].options.isolation_level, level)
68+
MockServerTestBase.spanner_service.clear_requests()
69+
70+
def test_isolation_level_in_connection_kwargs(self):
71+
for level in [
72+
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
73+
TransactionOptions.IsolationLevel.REPEATABLE_READ,
74+
TransactionOptions.IsolationLevel.SERIALIZABLE,
75+
]:
76+
connection = Connection(
77+
self.instance,
78+
self.database,
79+
kwargs={'isolation_level': level},)
80+
with connection.cursor() as cursor:
81+
cursor.execute("insert into singers (id, name) values (1, 'Some Singer')")
82+
self.assertEqual(1, cursor.rowcount)
83+
connection.commit()
84+
begin_requests = list(
85+
filter(
86+
lambda msg: isinstance(msg, BeginTransactionRequest),
87+
self.spanner_service.requests,
88+
)
89+
)
90+
self.assertEqual(1, len(begin_requests))
91+
self.assertEqual(begin_requests[0].options.isolation_level, level)
92+
MockServerTestBase.spanner_service.clear_requests()

0 commit comments

Comments
 (0)

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.