Skip to content

Commit a63bb24

Browse files
committed
allow disabling rollbacks for Transaction
1 parent 1d5f0d0 commit a63bb24

File tree

2 files changed

+43
-26
lines changed

2 files changed

+43
-26
lines changed

tests/fsutil/test_transaction.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from zappend.fsutil.fileobj import FileObj
1111
from zappend.fsutil.transaction import Transaction
12-
from zappend.fsutil.transaction import LOCK_EXT
1312
from zappend.fsutil.transaction import ROLLBACK_FILE
1413
from ..helpers import clear_memory_fs
1514

@@ -21,12 +20,15 @@ def setUp(self):
2120
clear_memory_fs()
2221

2322
def test_transaction_success(self):
24-
self._run_transaction_test(fail=False)
23+
self._run_transaction_test(fail=False, rollback=True)
2524

26-
def test_transaction_with_rollback(self):
27-
self._run_transaction_test(fail=True)
25+
def test_transaction_failure_with_rollback(self):
26+
self._run_transaction_test(fail=True, rollback=True)
2827

29-
def _run_transaction_test(self, fail: bool):
28+
def test_transaction_failure_without_rollback(self):
29+
self._run_transaction_test(fail=True, rollback=False)
30+
31+
def _run_transaction_test(self, fail: bool, rollback: bool):
3032

3133
test_root = FileObj("memory://test")
3234
test_root.mkdir()
@@ -42,7 +44,8 @@ def _run_transaction_test(self, fail: bool):
4244
self.assertFalse(test_file_3.exists())
4345

4446
temp_dir = FileObj("memory://temp")
45-
transaction = Transaction(test_root, temp_dir)
47+
transaction = Transaction(test_root, temp_dir,
48+
disable_rollback=not rollback)
4649

4750
self.assertEqual(test_root, transaction.target_dir)
4851

@@ -69,25 +72,26 @@ def create_test_folder(rollback_cb: Callable):
6972

7073
try:
7174
with transaction as rollback_cb:
72-
self.assertTrue(rollback_file.exists())
7375
self.assertTrue(lock_file.exists())
76+
self.assertEqual(rollback, rollback_file.exists())
7477

7578
change_test_file_1(rollback_cb)
7679
create_test_file_2(rollback_cb)
7780
create_test_folder(rollback_cb)
7881

79-
rollback_data = rollback_file.read(mode="rt")
80-
rollback_records = [line.split()[:2]
81-
for line in rollback_data.split("\n")]
82-
self.assertEqual(
83-
[
84-
["replace_file", "file-1.txt"],
85-
["delete_file", "file-2.txt"],
86-
["delete_dir", "folder"],
87-
[]
88-
],
89-
rollback_records
90-
)
82+
if rollback:
83+
rollback_data = rollback_file.read(mode="rt")
84+
rollback_records = [line.split()[:2]
85+
for line in rollback_data.split("\n")]
86+
self.assertEqual(
87+
[
88+
["replace_file", "file-1.txt"],
89+
["delete_file", "file-2.txt"],
90+
["delete_dir", "folder"],
91+
[]
92+
],
93+
rollback_records
94+
)
9195

9296
if fail:
9397
raise OSError("disk full")
@@ -101,7 +105,7 @@ def create_test_folder(rollback_cb: Callable):
101105
self.assertFalse(lock_file.exists())
102106
self.assertFalse(rollback_dir.exists())
103107

104-
if fail:
108+
if fail and rollback:
105109
self.assertTrue(test_root.exists())
106110
self.assertTrue(test_file_1.exists()) # replace_file by rollback
107111
self.assertEqual(b"A-B-C", test_file_1.read())

zappend/fsutil/transaction.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
ROLLBACK_ACTIONS = "delete_dir", "delete_file", "replace_file"
2828

2929

30-
# TODO: allow disabling rollbacks entirely
31-
3230
class Transaction:
3331
"""
3432
A filesystem transaction.
@@ -57,17 +55,26 @@ class Transaction:
5755
Reported paths must be relative to *target_dir*. The empty path ``""``
5856
refers to *target_dir* itself.
5957
58+
When entering the context, a lock file will be created which prevents
59+
other transactions to modify *target_dir*. The lock file will be placed
60+
next to *target_dir* and its name is the filename of *target_dir* with a
61+
``.lock`` extension. The lock file will be removed on context exit.
62+
6063
:param target_dir: The target directory that is subject to this
6164
transaction. All paths emitted to the rollback callback must be
6265
relative to *target_dir*. The directory may or may not exist yet.
6366
:param temp_dir: Temporary directory in which a unique subdirectory
6467
will be created that will be used to collect
6568
rollback data during the transaction. The directory must exist.
69+
:param disable_rollback: Disable rollback entirely.
70+
No rollback data will be written, however a lock file will still
71+
be created for the duration of the transaction.
6672
"""
6773

6874
def __init__(self,
6975
target_dir: FileObj,
70-
temp_dir: FileObj):
76+
temp_dir: FileObj,
77+
disable_rollback: bool = False):
7178
transaction_id = f"zappend-{uuid.uuid4()}"
7279
rollback_dir = temp_dir / transaction_id
7380
lock_file = target_dir.parent / (target_dir.filename + LOCK_EXT)
@@ -76,6 +83,7 @@ def __init__(self,
7683
self._rollback_file = rollback_dir / ROLLBACK_FILE
7784
self._target_dir = target_dir
7885
self._lock_file = lock_file
86+
self._disable_rollback = disable_rollback
7987
self._entered_ctx = False
8088

8189
@property
@@ -104,8 +112,9 @@ def __enter__(self):
104112
raise IOError(f"Target is locked: {lock_file.uri}")
105113
lock_file.write(self._rollback_dir.uri)
106114

107-
self._rollback_dir.mkdir()
108-
self._rollback_file.write("") # touch
115+
if not self._disable_rollback:
116+
self._rollback_dir.mkdir()
117+
self._rollback_file.write("") # touch
109118

110119
return self._add_rollback_action
111120

@@ -131,7 +140,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
131140
action_method = getattr(self, "_" + action)
132141
action_method(*args)
133142

134-
self._rollback_dir.delete(recursive=True)
143+
if not self._disable_rollback:
144+
self._rollback_dir.delete(recursive=True)
135145

136146
lock_file = self._lock_file
137147
try:
@@ -177,6 +187,9 @@ def _add_rollback_action(self,
177187
if data is not None:
178188
raise ValueError(f"Value of 'data' argument must be None")
179189

190+
if self._disable_rollback:
191+
return
192+
180193
assert hasattr(self, "_" + action)
181194
if data is not None:
182195
backup_id = str(uuid.uuid4())

0 commit comments

Comments
 (0)