Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Implemented redis bitwise operations

  • Loading branch information...
commit a312bc7c883b140015c9c8270ca786985faec5e8 1 parent e8a29ac
Jeethu Rao jeethu authored
2  .gitignore
View
@@ -5,3 +5,5 @@ _trial_temp
tmp
dist
txredisapi.egg-info
+.idea/
+atlassian-ide-plugin.xml
46 tests/mixins.py
View
@@ -0,0 +1,46 @@
+# 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.
+
+from twisted.trial import unittest
+from twisted.internet import defer
+
+REDIS_HOST = "localhost"
+REDIS_PORT = 6379
+
+
+class Redis26CheckMixin(object):
+ @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']
+ self.redis_version = ver
+ 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)
+
+ def _skipCheck(self):
+ if not self.redis_2_6:
+ skipMsg = "Redis version < 2.6 (found version: %s)"
+ raise unittest.SkipTest(skipMsg % self.redis_version)
143 tests/test_bitops.py
View
@@ -0,0 +1,143 @@
+# 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 operator
+
+import txredisapi as redis
+from twisted.internet import defer
+from twisted.trial import unittest
+from twisted.python import failure
+
+from .mixins import Redis26CheckMixin, REDIS_HOST, REDIS_PORT
+
+
+class TestBitOps(unittest.TestCase, Redis26CheckMixin):
+ _KEYS = ['_bitops_test_key1', '_bitops_test_key2',
+ '_bitops_test_key3']
+
+ @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()
+ yield self.db.delete(*self._KEYS)
+ yield self.db.script_flush()
+
+ @defer.inlineCallbacks
+ def tearDown(self):
+ yield self.db.delete(*self._KEYS)
+ yield self.db.disconnect()
+
+ @defer.inlineCallbacks
+ def test_getbit(self):
+ key = self._KEYS[0]
+ yield self.db.set(key, '\xaa')
+ l = [1, 0, 1, 0, 1, 0, 1, 0]
+ for x in range(8):
+ r = yield self.db.getbit(key, x)
+ self.assertEqual(r, l[x])
+
+ @defer.inlineCallbacks
+ def test_setbit(self):
+ key = self._KEYS[0]
+ r = yield self.db.setbit(key, 7, 1)
+ self.assertEqual(r, 0)
+ r = yield self.db.setbit(key, 7, 0)
+ self.assertEqual(r, 1)
+ r = yield self.db.setbit(key, 7, True)
+ self.assertEqual(r, 0)
+ r = yield self.db.setbit(key, 7, False)
+ self.assertEqual(r, 1)
+
+ @defer.inlineCallbacks
+ def test_bitcount(self):
+ self._skipCheck()
+ key = self._KEYS[0]
+ yield self.db.set(key, "foobar")
+ r = yield self.db.bitcount(key)
+ self.assertEqual(r, 26)
+ r = yield self.db.bitcount(key, 0, 0)
+ self.assertEqual(r, 4)
+ r = yield self.db.bitcount(key, 1, 1)
+ self.assertEqual(r, 6)
+
+ def test_bitop_not(self):
+ return self._test_bitop([operator.__not__, operator.not_,
+ 'not', 'NOT', 'NoT'],
+ '\x0f\x0f\x0f\x0f',
+ None,
+ '\xf0\xf0\xf0\xf0')
+
+ def test_bitop_or(self):
+ return self._test_bitop([operator.__or__, operator.or_,
+ 'or', 'OR', 'oR'],
+ '\x0f\x0f\x0f\x0f',
+ '\xf0\xf0\xf0\xf0',
+ '\xff\xff\xff\xff')
+
+ def test_bitop_and(self):
+ return self._test_bitop([operator.__and__, operator.and_,
+ 'and', 'AND', 'AnD'],
+ '\x0f\x0f\x0f\x0f',
+ '\xf0\xf0\xf0\xf0',
+ '\x00\x00\x00\x00')
+
+ def test_bitop_xor(self):
+ return self._test_bitop([operator.__xor__, operator.xor,
+ 'xor', 'XOR', 'XoR'],
+ '\x9c\x9c\x9c\x9c',
+ '\x6c\x6c\x6c\x6c',
+ '\xf0\xf0\xf0\xf0')
+
+ @defer.inlineCallbacks
+ def _test_bitop(self, op_list, value1, value2, expected):
+ self._skipCheck()
+ src_key = self._KEYS[0]
+ src_key1 = self._KEYS[1]
+ dest_key = self._KEYS[2]
+ is_unary = value2 is None
+ yield self.db.set(src_key, value1)
+ if not is_unary:
+ yield self.db.set(src_key1, value2)
+ t = (src_key, src_key1)
+ else:
+ t = (src_key, )
+ for op in op_list:
+ yield self.db.bitop(op, dest_key, *t)
+ r = yield self.db.get(dest_key)
+ self.assertEqual(r, expected)
+ # Test out failure cases
+ # Specify only dest and no src key(s)
+ cases = [self.db.bitop(op, dest_key)]
+ if is_unary:
+ # Try calling unary operator with > 1 operands
+ cases.append(self.db.bitop(op, dest_key, src_key, src_key1))
+ for case in cases:
+ try:
+ r = yield case
+ except redis.RedisError:
+ pass
+ except:
+ tb = failure.Failure().getTraceback()
+ raise self.failureException('%s raised instead of %s:\n %s'
+ % (sys.exc_info()[0],
+ 'txredisapi.RedisError',
+ tb))
+ else:
+ raise self.failureException('%s not raised (%r returned)'
+ % ('txredisapi.RedisError',
+ r))
34 tests/test_scripting.py
View
@@ -23,28 +23,20 @@
from twisted.internet import reactor
from twisted.python import failure
-redis_host = "localhost"
-redis_port = 6379
+from .mixins import Redis26CheckMixin, REDIS_HOST, REDIS_PORT
-class TestScripting(unittest.TestCase):
+class TestScripting(unittest.TestCase, Redis26CheckMixin):
_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,
+ 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()
@@ -52,24 +44,6 @@ def tearDown(self):
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")
@@ -174,7 +148,7 @@ def test_script_kill(self):
# 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,
+ self.db1 = yield redis.Connection(REDIS_HOST, REDIS_PORT,
reconnect=False)
eval_deferred = self.db1.eval(inf_loop)
reactor.iterate()
50 txredisapi.py
View
@@ -586,6 +586,12 @@ def get(self, key):
"""
return self.execute_command("GET", key)
+ def getbit(self, key, offset):
+ """
+ Return the bit value at offset in the string value stored at key
+ """
+ return self.execute_command("GETBIT", key, offset)
+
def getset(self, key, value):
"""
Set a key to a string returning the old value of the key
@@ -599,6 +605,14 @@ def mget(self, keys, *args):
keys = list_or_args("mget", keys, args)
return self.execute_command("MGET", *keys)
+ def setbit(self, key, offset, value):
+ """
+ Sets or clears the bit at offset in the string value stored at key
+ """
+ if isinstance(value, bool):
+ value = int(value)
+ return self.execute_command("SETBIT", key, offset, value)
+
def setnx(self, key, value):
"""
Set a key to a string value if the key does not exist
@@ -631,6 +645,42 @@ def msetnx(self, mapping):
items.extend(pair)
return self.execute_command("MSETNX", *items)
+ def bitop(self, operation, destkey, *srckeys):
+ """
+ Perform a bitwise operation between multiple keys
+ and store the result in the destination key.
+ """
+ srclen = len(srckeys)
+ if srclen == 0:
+ return defer.fail(RedisError("no ``srckeys`` specified"))
+ if isinstance(operation, (str, unicode)):
+ operation = operation.upper()
+ elif operation is operator.and_ or operation is operator.__and__:
+ operation = 'AND'
+ elif operation is operator.or_ or operation is operator.__or__:
+ operation = 'OR'
+ elif operation is operator.__xor__ or operation is operator.xor:
+ operation = 'XOR'
+ elif operation is operator.__not__ or operation is operator.not_:
+ operation = 'NOT'
+ if operation not in ('AND', 'OR', 'XOR', 'NOT'):
+ return defer.fail(InvalidData(
+ "Invalid operation: %s" % operation))
+ if operation == 'NOT' and srclen > 1:
+ return defer.fail(RedisError(
+ "bitop NOT takes only one ``srckey``"))
+ return self.execute_command('BITOP', operation, destkey, *srckeys)
+
+ def bitcount(self, key, start=None, end=None):
+ if (end is None and start is not None) or \
+ (start is None and end is not None):
+ raise RedisError("``start`` and ``end`` must both be specified")
+ if start is not None:
+ t = (start, end)
+ else:
+ t = ()
+ return self.execute_command("BITCOUNT", key, *t)
+
def incr(self, key, amount=1):
"""
Increment the integer value of key
Please sign in to comment.
Something went wrong with that request. Please try again.