summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandy <andy@whiskeymedia.com>2011-07-17 15:43:46 -0700
committerandy <andy@whiskeymedia.com>2011-07-17 15:43:46 -0700
commit4c750ee8b7385ca19a82d15c8d5597f84cbf7961 (patch)
tree53b77afba73d5655bb652d2fc09315d6e0070436
parent2e185683e310513d4efdcf9ec212115383f03aff (diff)
downloadredis-py-4c750ee8b7385ca19a82d15c8d5597f84cbf7961.tar.gz
Added a `transaction` convenience method that eliminates boilerplate when
using pipelines while WATCHing variables.
-rw-r--r--CHANGES3
-rw-r--r--README.md15
-rw-r--r--redis/client.py16
-rw-r--r--tests/pipeline.py24
4 files changed, 58 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
index 7ff689b..f497438 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,9 @@
* Pipelines can now be used as context managers. This is the preferred way
of use to ensure that connections get cleaned up properly. Thanks
David Wolever.
+ * Added a convenience method called transaction() on the base Redis class.
+ This method eliminates much of the boilerplate used when using pipelines
+ to watch Redis keys. See the documentation for details on usage.
* 2.4.6
* Variadic arguments for SADD, SREM, ZREN, HDEL, LPUSH, and RPUSH. Thanks
Raphaƫl Vinot.
diff --git a/README.md b/README.md
index f77f627..2a0a18a 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,21 @@ explicity calling reset():
... finally:
... pipe.reset()
+A convenience method named "transaction" exists for handling all the
+boilerplate of handling and retrying watch errors. It takes a callable that
+should expect a single parameter, a pipeline object, and any number of keys to
+be WATCHED. Our client-side INCR command above can be written like this,
+which is much easier to read:
+
+ >>> def client_side_incr(pipe):
+ ... current_value = pipe.get('OUR-SEQUENCE-KEY')
+ ... next_value = int(current_value) + 1
+ ... pipe.multi()
+ ... pipe.set('OUR-SEQUENCE-KEY', next_value)
+ >>>
+ >>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY')
+ [True]
+
## API Reference
diff --git a/redis/client.py b/redis/client.py
index 9ab467f..1c245bf 100644
--- a/redis/client.py
+++ b/redis/client.py
@@ -203,6 +203,22 @@ class Redis(object):
transaction,
shard_hint)
+ def transaction(self, func, *watches, **kwargs):
+ """
+ Convenience method for executing the callable `func` as a transaction
+ while watching all keys specified in `watches`. The 'func' callable
+ should expect a single arguement which is a Pipeline object.
+ """
+ shard_hint = kwargs.pop('shard_hint', None)
+ with self.pipeline(True, shard_hint) as pipe:
+ while 1:
+ try:
+ pipe.watch(*watches)
+ func(pipe)
+ return pipe.execute()
+ except WatchError:
+ continue
+
def lock(self, name, timeout=None, sleep=0.1):
"""
Return a new Lock object using key ``name`` that mimics
diff --git a/tests/pipeline.py b/tests/pipeline.py
index b199f1c..f4845d3 100644
--- a/tests/pipeline.py
+++ b/tests/pipeline.py
@@ -119,3 +119,27 @@ class PipelineTestCase(unittest.TestCase):
self.assertEquals(pipe.watching, False)
pipe.get('a')
self.assertEquals(pipe.execute(), ['1'])
+
+ def test_transaction_callable(self):
+ self.client.set('a', 1)
+ self.client.set('b', 2)
+ has_run = []
+
+ def my_transaction(pipe):
+ a = pipe.get('a')
+ self.assert_(a in ('1', '2'))
+ b = pipe.get('b')
+ self.assertEquals(b, '2')
+
+ # silly one-once code... incr's a so WatchError should be raised
+ # forcing this all to run again
+ if not has_run:
+ self.client.incr('a')
+ has_run.append('it has')
+
+ pipe.multi()
+ pipe.set('c', int(a)+int(b))
+
+ result = self.client.transaction(my_transaction, 'a', 'b')
+ self.assertEquals(result, [True])
+ self.assertEquals(self.client.get('c'), '4')