Skip to content

Commit 75c2c99

Browse files
committed
new: StandardDatabase.fetch_transaction (#329)
1 parent ed35c9d commit 75c2c99

File tree

5 files changed

+85
-11
lines changed

5 files changed

+85
-11
lines changed

arango/database.py

+13
Original file line numberDiff line numberDiff line change
@@ -2948,6 +2948,14 @@ def begin_batch_execution(
29482948
"""
29492949
return BatchDatabase(self._conn, return_result, max_workers)
29502950

2951+
def fetch_transaction(self, transaction_id: str) -> "TransactionDatabase":
2952+
"""Fetch an existing transaction.
2953+
2954+
:param transaction_id: The ID of the existing transaction.
2955+
:type transaction_id: str
2956+
"""
2957+
return TransactionDatabase(connection=self._conn, transaction_id=transaction_id)
2958+
29512959
def begin_transaction(
29522960
self,
29532961
read: Union[str, Sequence[str], None] = None,
@@ -3125,6 +3133,9 @@ class TransactionDatabase(Database):
31253133
:type lock_timeout: int | None
31263134
:param max_size: Max transaction size in bytes.
31273135
:type max_size: int | None
3136+
:param transaction_id: Initialize using an existing transaction instead of creating
3137+
a new transaction.
3138+
:type transaction_id: str | None
31283139
"""
31293140

31303141
def __init__(
@@ -3137,6 +3148,7 @@ def __init__(
31373148
allow_implicit: Optional[bool] = None,
31383149
lock_timeout: Optional[int] = None,
31393150
max_size: Optional[int] = None,
3151+
transaction_id: Optional[str] = None,
31403152
) -> None:
31413153
self._executor: TransactionApiExecutor
31423154
super().__init__(
@@ -3150,6 +3162,7 @@ def __init__(
31503162
allow_implicit=allow_implicit,
31513163
lock_timeout=lock_timeout,
31523164
max_size=max_size,
3165+
transaction_id=transaction_id,
31533166
),
31543167
)
31553168

arango/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,10 @@ class TransactionAbortError(ArangoServerError):
772772
"""Failed to abort transaction."""
773773

774774

775+
class TransactionFetchError(ArangoServerError):
776+
"""Failed to fetch existing transaction."""
777+
778+
775779
class TransactionListError(ArangoServerError):
776780
"""Failed to retrieve transactions."""
777781

arango/executor.py

+26-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
OverloadControlExecutorError,
2020
TransactionAbortError,
2121
TransactionCommitError,
22+
TransactionFetchError,
2223
TransactionInitError,
2324
TransactionStatusError,
2425
)
@@ -241,6 +242,9 @@ class TransactionApiExecutor:
241242
:type max_size: int
242243
:param allow_dirty_read: Allow reads from followers in a cluster.
243244
:type allow_dirty_read: bool | None
245+
:param transaction_id: Initialize using an existing transaction instead of starting
246+
a new transaction.
247+
:type transaction_id: str | None
244248
"""
245249

246250
def __init__(
@@ -254,6 +258,7 @@ def __init__(
254258
lock_timeout: Optional[int] = None,
255259
max_size: Optional[int] = None,
256260
allow_dirty_read: bool = False,
261+
transaction_id: Optional[str] = None,
257262
) -> None:
258263
self._conn = connection
259264

@@ -275,19 +280,29 @@ def __init__(
275280
if max_size is not None:
276281
data["maxTransactionSize"] = max_size
277282

278-
request = Request(
279-
method="post",
280-
endpoint="/_api/transaction/begin",
281-
data=data,
282-
headers={"x-arango-allow-dirty-read": "true"} if allow_dirty_read else None,
283-
)
284-
resp = self._conn.send_request(request)
283+
if transaction_id is None:
284+
request = Request(
285+
method="post",
286+
endpoint="/_api/transaction/begin",
287+
data=data,
288+
headers={"x-arango-allow-dirty-read": "true"}
289+
if allow_dirty_read
290+
else None,
291+
)
292+
resp = self._conn.send_request(request)
285293

286-
if not resp.is_success:
287-
raise TransactionInitError(resp, request)
294+
if not resp.is_success:
295+
raise TransactionInitError(resp, request)
296+
297+
result = resp.body["result"]
298+
self._id: str = result["id"]
299+
else:
300+
self._id = transaction_id
288301

289-
result: Json = resp.body["result"]
290-
self._id: str = result["id"]
302+
try:
303+
self.status()
304+
except TransactionStatusError as err:
305+
raise TransactionFetchError(err.response, err.request)
291306

292307
@property
293308
def context(self) -> str:

docs/transaction.rst

+9
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ logical unit of work (ACID compliant).
6868
assert '_rev' in txn_col.insert({'_key': 'Lily'})
6969
assert len(txn_col) == 6
7070

71+
# Fetch an existing transaction. Useful if you have received a Transaction ID
72+
# from some other part of your system or an external system.
73+
original_txn = db.begin_transaction(write='students')
74+
txn_col = original_txn.collection('students')
75+
assert '_rev' in txn_col.insert({'_key': 'Chip'})
76+
txn_db = db.fetch_transaction(original_txn.transaction_id)
77+
txn_col = txn_db.collection('students')
78+
assert '_rev' in txn_col.insert({'_key': 'Alya'})
79+
7180
# Abort the transaction
7281
txn_db.abort_transaction()
7382
assert 'Kate' not in col

tests/test_transaction.py

+33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
TransactionAbortError,
66
TransactionCommitError,
77
TransactionExecuteError,
8+
TransactionFetchError,
89
TransactionInitError,
910
TransactionStatusError,
1011
)
@@ -117,6 +118,38 @@ def test_transaction_commit(db, col, docs):
117118
assert err.value.error_code in {10, 1655}
118119

119120

121+
def test_transaction_fetch_existing(db, col, docs):
122+
original_txn = db.begin_transaction(
123+
read=col.name,
124+
write=col.name,
125+
exclusive=[],
126+
sync=True,
127+
allow_implicit=False,
128+
lock_timeout=1000,
129+
max_size=10000,
130+
)
131+
txn_col = original_txn.collection(col.name)
132+
133+
assert "_rev" in txn_col.insert(docs[0])
134+
assert "_rev" in txn_col.delete(docs[0])
135+
136+
txn_db = db.fetch_transaction(transaction_id=original_txn.transaction_id)
137+
138+
txn_col = txn_db.collection(col.name)
139+
assert "_rev" in txn_col.insert(docs[1])
140+
assert "_rev" in txn_col.delete(docs[1])
141+
142+
txn_db.commit_transaction()
143+
assert txn_db.transaction_status() == "committed"
144+
assert original_txn.transaction_status() == "committed"
145+
assert txn_db.transaction_id == original_txn.transaction_id
146+
147+
# Test fetch transaction that does not exist
148+
with pytest.raises(TransactionFetchError) as err:
149+
db.fetch_transaction(transaction_id="illegal")
150+
assert err.value.error_code in {10, 1655}
151+
152+
120153
def test_transaction_abort(db, col, docs):
121154
txn_db = db.begin_transaction(write=col.name)
122155
txn_col = txn_db.collection(col.name)

0 commit comments

Comments
 (0)