Skip to content

Commit c28a7f1

Browse files
committed
Merge pull request #93 from tomlinford/transaction_method
Add transaction method to client
2 parents 8fba948 + e717bb6 commit c28a7f1

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

mockredis/client.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from mockredis.clock import SystemClock
1515
from mockredis.lock import MockRedisLock
16-
from mockredis.exceptions import RedisError, ResponseError
16+
from mockredis.exceptions import RedisError, ResponseError, WatchError
1717
from mockredis.pipeline import MockRedisPipeline
1818
from mockredis.script import Script
1919
from mockredis.sortedset import SortedSet
@@ -77,6 +77,30 @@ def pipeline(self, transaction=True, shard_hint=None):
7777
"""Emulate a redis-python pipeline."""
7878
return MockRedisPipeline(self, transaction, shard_hint)
7979

80+
def transaction(self, func, *watches, **kwargs):
81+
"""
82+
Convenience method for executing the callable `func` as a transaction
83+
while watching all keys specified in `watches`. The 'func' callable
84+
should expect a single argument which is a Pipeline object.
85+
86+
Copied directly from redis-py.
87+
"""
88+
shard_hint = kwargs.pop('shard_hint', None)
89+
value_from_callable = kwargs.pop('value_from_callable', False)
90+
watch_delay = kwargs.pop('watch_delay', None)
91+
with self.pipeline(True, shard_hint) as pipe:
92+
while 1:
93+
try:
94+
if watches:
95+
pipe.watch(*watches)
96+
func_value = func(pipe)
97+
exec_value = pipe.execute()
98+
return func_value if value_from_callable else exec_value
99+
except WatchError:
100+
if watch_delay is not None and watch_delay > 0:
101+
time.sleep(watch_delay)
102+
continue
103+
80104
def watch(self, *argv, **kwargs):
81105
"""
82106
Mock does not support command buffering so watch

mockredis/tests/test_pipeline.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@ def test_pipeline_args(self):
3333
with self.redis.pipeline(transaction=False, shard_hint=None):
3434
pass
3535

36+
def test_transaction(self):
37+
self.redis["a"] = 1
38+
self.redis["b"] = 2
39+
has_run = []
40+
41+
def my_transaction(pipe):
42+
a_value = pipe.get("a")
43+
assert a_value in (b"1", b"2")
44+
b_value = pipe.get("b")
45+
assert b_value == b"2"
46+
47+
# silly run-once code... incr's "a" so WatchError should be raised
48+
# forcing this all to run again. this should incr "a" once to "2"
49+
if not has_run:
50+
self.redis.incr("a")
51+
has_run.append(True)
52+
53+
pipe.multi()
54+
pipe.set("c", int(a_value) + int(b_value))
55+
56+
result = self.redis.transaction(my_transaction, "a", "b")
57+
eq_([True], result)
58+
eq_(b"4", self.redis["c"])
59+
3660
def test_set_and_get(self):
3761
"""
3862
Pipeline execution returns the pipeline, not the intermediate value.

0 commit comments

Comments
 (0)