diff options
author | andy <andy@whiskeymedia.com> | 2011-07-17 15:43:46 -0700 |
---|---|---|
committer | andy <andy@whiskeymedia.com> | 2011-07-17 15:43:46 -0700 |
commit | 4c750ee8b7385ca19a82d15c8d5597f84cbf7961 (patch) | |
tree | 53b77afba73d5655bb652d2fc09315d6e0070436 | |
parent | 2e185683e310513d4efdcf9ec212115383f03aff (diff) | |
download | redis-py-4c750ee8b7385ca19a82d15c8d5597f84cbf7961.tar.gz |
Added a `transaction` convenience method that eliminates boilerplate when
using pipelines while WATCHing variables.
-rw-r--r-- | CHANGES | 3 | ||||
-rw-r--r-- | README.md | 15 | ||||
-rw-r--r-- | redis/client.py | 16 | ||||
-rw-r--r-- | tests/pipeline.py | 24 |
4 files changed, 58 insertions, 0 deletions
@@ -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. @@ -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') |