Skip to content

Commit 52c214f

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

File tree

4 files changed

+83
-11
lines changed

4 files changed

+83
-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/executor.py

+30-11
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ class TransactionApiExecutor:
241241
:type max_size: int
242242
:param allow_dirty_read: Allow reads from followers in a cluster.
243243
:type allow_dirty_read: bool | None
244+
:param transaction_id: Initialize using an existing transaction instead of starting
245+
a new transaction.
246+
:type transaction_id: str | None
244247
"""
245248

246249
def __init__(
@@ -254,6 +257,7 @@ def __init__(
254257
lock_timeout: Optional[int] = None,
255258
max_size: Optional[int] = None,
256259
allow_dirty_read: bool = False,
260+
transaction_id: Optional[str] = None,
257261
) -> None:
258262
self._conn = connection
259263

@@ -275,19 +279,34 @@ def __init__(
275279
if max_size is not None:
276280
data["maxTransactionSize"] = max_size
277281

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)
282+
self._id: str = ""
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 = result["id"]
299+
else:
300+
request = Request(
301+
method="get",
302+
endpoint=f"/_api/transaction/{transaction_id}",
303+
)
304+
resp = self._conn.send_request(request)
305+
306+
if not resp.is_success:
307+
raise TransactionInitError(resp, request)
288308

289-
result: Json = resp.body["result"]
290-
self._id: str = result["id"]
309+
self._id = transaction_id
291310

292311
@property
293312
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': 'Abby'})
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': 'Mike'})
79+
7180
# Abort the transaction
7281
txn_db.abort_transaction()
7382
assert 'Kate' not in col

tests/test_transaction.py

+31
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,37 @@ def test_transaction_commit(db, col, docs):
117117
assert err.value.error_code in {10, 1655}
118118

119119

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

0 commit comments

Comments
 (0)