Permalink
Browse files

Merge branch 'master' of github.com:fiorix/txredisapi

  • Loading branch information...
2 parents 23f339e + d9f6241 commit e8a29ac9576521ef4824c43474b80b5becf9b551 @gleicon gleicon committed Jan 1, 2013
Showing with 447 additions and 10 deletions.
  1. +55 −1 README.md
  2. +1 −1 setup.py
  3. +204 −0 tests/test_scripting.py
  4. +58 −0 tests/test_transactions.py
  5. +129 −8 txredisapi.py
View
@@ -51,7 +51,7 @@ Try the following:
$ python
>>> import cyclone.redis
>>> cyclone.redis.version
- '0.9'
+ '1.0'
However, if you really really insist in installing, get it from pypi:
@@ -423,6 +423,7 @@ But they work pretty good on normal or lazy connections, and connection pools.
NOTE: redis uses the following methods for transactions:
+- WATCH: synchronization
- MULTI: start the transaction
- EXEC: commit the transaction
- DISCARD: you got it.
@@ -478,6 +479,55 @@ Example:
main().addCallback(lambda ign: reactor.stop())
reactor.run()
+A "COUNTER" example, using WATCH/MULTI:
+
+ #!/usr/bin/env python
+ # coding: utf-8
+
+ import txredisapi as redis
+
+ from twisted.internet import defer
+ from twisted.internet import reactor
+
+
+ @defer.inlineCallbacks
+ def main():
+ rc = yield redis.ConnectionPool()
+
+ # Reset keys
+ yield rc.set("a1", 0)
+
+ # Synchronize and start transaction
+ t = yield rc.watch("a1")
+
+ # Load previous value
+ a1 = yield t.get("a1")
+
+ # start the transactional pipeline
+ yield t.multi()
+
+ # modify and retrieve the new a1 value
+ yield t.set("a1", a1 + 1)
+ yield t.get("a1")
+
+ print "simulating concurrency, this will abort the transaction"
+ yield rc.set("a1", 2)
+
+ try:
+ r = yield t.commit()
+ print "commit=", repr(r)
+ except redis.WatchError, e:
+ a1 = yield rc.get("a1")
+ print "transaction has failed."
+ print "current a1 value: ", a1
+
+ yield rc.disconnect()
+
+
+ if __name__ == "__main__":
+ main().addCallback(lambda ign: reactor.stop())
+ reactor.run()
+
Calling ``commit`` will cause it to return a list with the return of all
commands executed in the transaction. ``discard``, on the other hand, will
@@ -545,3 +595,7 @@ Thanks to (in no particular order):
- Christoph Tavan (@ctavan)
- Idea and test case for nested multi bulk replies, minor command enhancements.
+
+- dgvncsz0f
+
+ - WATCH/UNWATCH commands
View
@@ -18,7 +18,7 @@
setuptools.setup(
name="txredisapi",
- version="0.9",
+ version="1.0",
py_modules=["txredisapi"],
install_requires=["twisted"],
author="Alexandre Fiori",
View
@@ -0,0 +1,204 @@
+# coding: utf-8
+# Copyright 2009 Alexandre Fiori
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import hashlib
+
+import txredisapi as redis
+
+from twisted.internet import defer
+from twisted.trial import unittest
+from twisted.internet import reactor
+from twisted.python import failure
+
+redis_host = "localhost"
+redis_port = 6379
+
+
+class TestScripting(unittest.TestCase):
+ _SCRIPT = "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" # From redis example
+
+ @defer.inlineCallbacks
+ def setUp(self):
+ self.db = yield redis.Connection(redis_host, redis_port,
+ reconnect=False)
+ self.db1 = None
+ self.redis_2_6 = yield self.is_redis_2_6()
+ d = yield self.db.info("server")
+ self.redis_version = d[u'redis_version']
+ yield self.db.script_flush()
+
+ def _skipCheck(self):
+ if not self.redis_2_6:
+ skipMsg = "Redis version < 2.6 (found version: %s)"
+ raise unittest.SkipTest(skipMsg % self.redis_version)
+
+ @defer.inlineCallbacks
+ def tearDown(self):
+ yield self.db.disconnect()
+ if self.db1 is not None:
+ yield self.db1.disconnect()
+
+ @defer.inlineCallbacks
+ def is_redis_2_6(self):
+ """
+ Returns true if the Redis version >= 2.6
+ """
+ d = yield self.db.info("server")
+ if u'redis_version' not in d:
+ defer.returnValue(False)
+ ver = d[u'redis_version']
+ ver_list = [int(x) for x in ver.split(u'.')]
+ if len(ver_list) < 2:
+ defer.returnValue(False)
+ if ver_list[0] > 2:
+ defer.returnValue(True)
+ elif ver_list[0] == 2 and ver_list[1] >= 6:
+ defer.returnValue(True)
+ defer.returnValue(False)
+
+ @defer.inlineCallbacks
+ def test_eval(self):
+ self._skipCheck()
+ d = dict(key1="first", key2="second")
+ r = yield self.db.eval(self._SCRIPT, **d)
+ self._check_eval_result(d, r)
+ r = yield self.db.eval("return 10")
+ self.assertEqual(r, 10)
+ r = yield self.db.eval("return {1,2,3.3333,'foo',nil,'bar'}")
+ self.assertEqual(r, [1, 2, 3, "foo"])
+ # Test the case where the hash is in script_hashes,
+ # but redis doesn't have it
+ h = self._hash_script(self._SCRIPT)
+ yield self.db.script_flush()
+ self.db.script_hashes.add(h)
+ r = yield self.db.eval(self._SCRIPT, **d)
+ self._check_eval_result(d, r)
+
+ @defer.inlineCallbacks
+ def test_eval_error(self):
+ self._skipCheck()
+ try:
+ result = yield self.db.eval('return {err="My Error"}')
+ except redis.ResponseError:
+ pass
+ except:
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ 'txredisapi.ResponseError',
+ failure.Failure().getTraceback()))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % ('txredisapi.ResponseError', result))
+
+ @defer.inlineCallbacks
+ def test_evalsha(self):
+ self._skipCheck()
+ r = yield self.db.eval(self._SCRIPT)
+ h = self._hash_script(self._SCRIPT)
+ r = yield self.db.evalsha(h)
+ self._check_eval_result({}, r)
+
+ @defer.inlineCallbacks
+ def test_evalsha_error(self):
+ self._skipCheck()
+ h = self._hash_script(self._SCRIPT)
+ try:
+ result = yield self.db.evalsha(h)
+ except redis.ScriptDoesNotExist:
+ pass
+ except:
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ 'txredisapi.ScriptDoesNotExist',
+ failure.Failure().getTraceback()))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % ('txredisapi.ResponseError', result))
+
+ @defer.inlineCallbacks
+ def test_script_load(self):
+ self._skipCheck()
+ h = self._hash_script(self._SCRIPT)
+ r = yield self.db.script_exists(h)
+ self.assertFalse(r)
+ r = yield self.db.script_load(self._SCRIPT)
+ self.assertEqual(r, h)
+ r = yield self.db.script_exists(h)
+ self.assertTrue(r)
+
+ @defer.inlineCallbacks
+ def test_script_exists(self):
+ self._skipCheck()
+ h = self._hash_script(self._SCRIPT)
+ script1 = "return 1"
+ h1 = self._hash_script(script1)
+ r = yield self.db.script_exists(h)
+ self.assertFalse(r)
+ r = yield self.db.script_exists(h, h1)
+ self.assertEqual(r, [False, False])
+ yield self.db.script_load(script1)
+ r = yield self.db.script_exists(h, h1)
+ self.assertEqual(r, [False, True])
+ yield self.db.script_load(self._SCRIPT)
+ r = yield self.db.script_exists(h, h1)
+ self.assertEqual(r, [True, True])
+
+ @defer.inlineCallbacks
+ def test_script_kill(self):
+ self._skipCheck()
+ try:
+ result = yield self.db.script_kill()
+ except redis.NoScriptRunning:
+ pass
+ except:
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ 'txredisapi.NoScriptRunning',
+ failure.Failure().getTraceback()))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % ('txredisapi.ResponseError', result))
+ # Run an infinite loop script from one connection
+ # and kill it from another.
+ inf_loop = "while 1 do end"
+ self.db1 = yield redis.Connection(redis_host, redis_port,
+ reconnect=False)
+ eval_deferred = self.db1.eval(inf_loop)
+ reactor.iterate()
+ r = yield self.db.script_kill()
+ self.assertEqual(r, 'OK')
+ try:
+ result = yield eval_deferred
+ except redis.ResponseError:
+ pass
+ except:
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ 'txredisapi.ResponseError',
+ failure.Failure().getTraceback()))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % ('txredisapi.ResponseError', result))
+
+ def _check_eval_result(self, d, r):
+ n = len(r)
+ self.assertEqual(n, len(d) * 2)
+ new_d = dict(zip(r[:n/2], r[n/2:]))
+ for k, v in d.items():
+ assert new_d[k] == v
+
+ def _hash_script(self, script):
+ return hashlib.sha1(script).hexdigest()
View
@@ -68,3 +68,61 @@ def testRedisWithWatchAndMulti(self):
self.assertFalse(tx.inTransaction)
yield rapi.disconnect()
+
+ # some sort of probabilistic test
+ @defer.inlineCallbacks
+ def testWatchAndPools_1(self):
+ rapi = yield txredisapi.ConnectionPool(redis_host, redis_port, poolsize=2, reconnect=False)
+ tx1 = yield rapi.watch("foobar")
+ tx2 = yield tx1.watch("foobaz")
+ self.assertTrue(id(tx1) == id(tx2))
+ yield rapi.disconnect()
+
+ # some sort of probabilistic test
+ @defer.inlineCallbacks
+ def testWatchAndPools_2(self):
+ rapi = yield txredisapi.ConnectionPool(redis_host, redis_port, poolsize=2, reconnect=False)
+ tx1 = yield rapi.watch("foobar")
+ tx2 = yield rapi.watch("foobaz")
+ self.assertTrue(id(tx1) != id(tx2))
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_1(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.multi("foobar")
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_2(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.multi()
+ try:
+ yield tx.watch("foobar")
+ except txredisapi.ResponseError:
+ pass
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+ yield rapi.disconnect()
+
+ @defer.inlineCallbacks
+ def testWatchEdgeCase_3(self):
+ rapi = yield txredisapi.Connection(redis_host, redis_port)
+
+ tx = yield rapi.watch("foobar")
+ tx = yield tx.multi("foobaz")
+ yield tx.unwatch()
+ self.assertTrue(tx.inTransaction)
+ yield tx.discard()
+ self.assertFalse(tx.inTransaction)
+
+ yield rapi.disconnect()
Oops, something went wrong.

0 comments on commit e8a29ac

Please sign in to comment.