Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 932c5d1

Browse files
kpdemetriouPhil Demetriouvmarkovtsev
authored
Allowing extra transaction options (#242)
* Allowing extra transaction options * Switching to Python 3.6-compatible asyncio primitives * Using native SQLAlchemy engine for independent queries in tests * Excluding postgresql+aiopg in parameterized transaction test * Clarifying test skip comment in parameterized transaction test * Adding missing type annotation * Formatting with black Co-authored-by: Phil Demetriou <[email protected]> Co-authored-by: Vadim Markovtsev <[email protected]>
1 parent 2d0bc0f commit 932c5d1

File tree

7 files changed

+70
-11
lines changed

7 files changed

+70
-11
lines changed

databases/backends/aiopg.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ def __init__(self, connection: AiopgConnection):
215215
self._is_root = False
216216
self._savepoint_name = ""
217217

218-
async def start(self, is_root: bool) -> None:
218+
async def start(
219+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
220+
) -> None:
219221
assert self._connection._connection is not None, "Connection is not acquired"
220222
self._is_root = is_root
221223
cursor = await self._connection._connection.cursor()

databases/backends/mysql.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,9 @@ def __init__(self, connection: MySQLConnection):
206206
self._is_root = False
207207
self._savepoint_name = ""
208208

209-
async def start(self, is_root: bool) -> None:
209+
async def start(
210+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
211+
) -> None:
210212
assert self._connection._connection is not None, "Connection is not acquired"
211213
self._is_root = is_root
212214
if self._is_root:

databases/backends/postgres.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,11 @@ def __init__(self, connection: PostgresConnection):
286286
None
287287
) # type: typing.Optional[asyncpg.transaction.Transaction]
288288

289-
async def start(self, is_root: bool) -> None:
289+
async def start(
290+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
291+
) -> None:
290292
assert self._connection._connection is not None, "Connection is not acquired"
291-
self._transaction = self._connection._connection.transaction()
293+
self._transaction = self._connection._connection.transaction(**extra_options)
292294
await self._transaction.start()
293295

294296
async def commit(self) -> None:

databases/backends/sqlite.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ def __init__(self, connection: SQLiteConnection):
178178
self._is_root = False
179179
self._savepoint_name = ""
180180

181-
async def start(self, is_root: bool) -> None:
181+
async def start(
182+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
183+
) -> None:
182184
assert self._connection._connection is not None, "Connection is not acquired"
183185
self._is_root = is_root
184186
if self._is_root:

databases/core.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,10 @@ def connection(self) -> "Connection":
184184
self._connection_context.set(connection)
185185
return connection
186186

187-
def transaction(self, *, force_rollback: bool = False) -> "Transaction":
188-
return Transaction(self.connection, force_rollback=force_rollback)
187+
def transaction(
188+
self, *, force_rollback: bool = False, **kwargs: typing.Any
189+
) -> "Transaction":
190+
return Transaction(self.connection, force_rollback=force_rollback, **kwargs)
189191

190192
@contextlib.contextmanager
191193
def force_rollback(self) -> typing.Iterator[None]:
@@ -276,11 +278,13 @@ async def iterate(
276278
async for record in self._connection.iterate(built_query):
277279
yield record
278280

279-
def transaction(self, *, force_rollback: bool = False) -> "Transaction":
281+
def transaction(
282+
self, *, force_rollback: bool = False, **kwargs: typing.Any
283+
) -> "Transaction":
280284
def connection_callable() -> Connection:
281285
return self
282286

283-
return Transaction(connection_callable, force_rollback)
287+
return Transaction(connection_callable, force_rollback, **kwargs)
284288

285289
@property
286290
def raw_connection(self) -> typing.Any:
@@ -305,9 +309,11 @@ def __init__(
305309
self,
306310
connection_callable: typing.Callable[[], Connection],
307311
force_rollback: bool,
312+
**kwargs: typing.Any,
308313
) -> None:
309314
self._connection_callable = connection_callable
310315
self._force_rollback = force_rollback
316+
self._extra_options = kwargs
311317

312318
async def __aenter__(self) -> "Transaction":
313319
"""
@@ -355,7 +361,9 @@ async def start(self) -> "Transaction":
355361
async with self._connection._transaction_lock:
356362
is_root = not self._connection._transaction_stack
357363
await self._connection.__aenter__()
358-
await self._transaction.start(is_root=is_root)
364+
await self._transaction.start(
365+
is_root=is_root, extra_options=self._extra_options
366+
)
359367
self._connection._transaction_stack.append(self)
360368
return self
361369

databases/interfaces.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def raw_connection(self) -> typing.Any:
5656

5757

5858
class TransactionBackend:
59-
async def start(self, is_root: bool) -> None:
59+
async def start(
60+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
61+
) -> None:
6062
raise NotImplementedError() # pragma: no cover
6163

6264
async def commit(self) -> None:

tests/test_databases.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,47 @@ async def test_transaction_commit(database_url):
438438
assert len(results) == 1
439439

440440

441+
@pytest.mark.parametrize("database_url", DATABASE_URLS)
442+
@async_adapter
443+
async def test_transaction_commit_serializable(database_url):
444+
"""
445+
Ensure that serializable transaction commit via extra parameters is supported.
446+
"""
447+
448+
database_url = DatabaseURL(database_url)
449+
450+
if database_url.scheme != "postgresql":
451+
pytest.skip("Test (currently) only supports asyncpg")
452+
453+
def insert_independently():
454+
engine = sqlalchemy.create_engine(str(database_url))
455+
conn = engine.connect()
456+
457+
query = notes.insert().values(text="example1", completed=True)
458+
conn.execute(query)
459+
460+
def delete_independently():
461+
engine = sqlalchemy.create_engine(str(database_url))
462+
conn = engine.connect()
463+
464+
query = notes.delete()
465+
conn.execute(query)
466+
467+
async with Database(database_url) as database:
468+
async with database.transaction(force_rollback=True, isolation="serializable"):
469+
query = notes.select()
470+
results = await database.fetch_all(query=query)
471+
assert len(results) == 0
472+
473+
insert_independently()
474+
475+
query = notes.select()
476+
results = await database.fetch_all(query=query)
477+
assert len(results) == 0
478+
479+
delete_independently()
480+
481+
441482
@pytest.mark.parametrize("database_url", DATABASE_URLS)
442483
@async_adapter
443484
async def test_transaction_rollback(database_url):

0 commit comments

Comments
 (0)