diff options
-rw-r--r-- | redis/client.py | 245 | ||||
-rw-r--r-- | tests/server_commands.py | 223 |
2 files changed, 248 insertions, 220 deletions
diff --git a/redis/client.py b/redis/client.py index ee3f87a..f9357d3 100644 --- a/redis/client.py +++ b/redis/client.py @@ -249,28 +249,44 @@ class Redis(threading.local): def pipeline(self): return Pipeline(self.connection, self.encoding, self.errors) + #### COMMAND EXECUTION AND PROTOCOL PARSING #### def _execute_command(self, command_name, command, **options): + "Sends the command to the Redis server and returns it's response" subscription_command = command_name in self.SUBSCRIPTION_COMMANDS if self.subscribed and not subscription_command: raise RedisError("Cannot issue commands other than SUBSCRIBE and " "UNSUBSCRIBE while channels are open") - self.connection.send(command, self) - if subscription_command: - return None - return self.parse_response(command_name, **options) - - def execute_command(self, command_name, command, **options): - "Sends the command to the Redis server and returns it's response" try: - return self._execute_command(command_name, command, **options) + self.connection.send(command, self) + response = self.parse_response(command_name, **options) + if subscription_command: + return None + return response except ConnectionError: self.connection.disconnect() - return self._execute_command(command_name, command, **options) + self.connection.send(command, self) + response = self.parse_response(command_name, **options) + if subscription_command: + return None + return response + + def execute_command(self, *args, **options): + "Sends command to redis server and returns response" + cmd_count = len(args) + cmds = [] + for i in args: + enc_value = self.encode(i) + cmds.append('$%s\r\n%s\r\n' % (len(enc_value), enc_value)) + return self._execute_command( + args[0], + '*%s\r\n%s' % (cmd_count, ''.join(cmds)), + **options + ) def _parse_response(self, command_name, catch_errors): conn = self.connection - response = conn.read().strip() + response = conn.read()[:-2] # strip last two characters (\r\n) if not response: self.connection.disconnect() raise ConnectionError("Socket closed on remote end") @@ -338,34 +354,6 @@ class Redis(threading.local): # not a string or unicode, attempt to convert to a string return str(value) - def format_inline(self, *args, **options): - "Formats a request with the inline protocol" - cmd = '%s\r\n' % ' '.join([self.encode(a) for a in args]) - return self.execute_command(args[0], cmd, **options) - - def format_bulk(self, *args, **options): - "Formats a request with the bulk protocol" - bulk_value = self.encode(args[-1]) - cmd = '%s %s\r\n%s\r\n' % ( - ' '.join([self.encode(a) for a in args[:-1]]), - len(bulk_value), - bulk_value, - ) - return self.execute_command(args[0], cmd, **options) - - def format_multi_bulk(self, *args, **options): - "Formats the request with the multi-bulk protocol" - cmd_count = len(args) - cmds = [] - for i in args: - enc_value = self.encode(i) - cmds.append('$%s\r\n%s\r\n' % (len(enc_value), enc_value)) - return self.execute_command( - args[0], - '*%s\r\n%s' % (cmd_count, ''.join(cmds)), - **options - ) - #### CONNECTION HANDLING #### def get_connection(self, host, port, db, password, socket_timeout): "Returns a connection object" @@ -384,9 +372,9 @@ class Redis(threading.local): the appropriate database. """ if self.connection.password: - if not self.format_inline('AUTH', self.connection.password): + if not self.execute_command('AUTH', self.connection.password): raise AuthenticationError("Invalid Password") - self.format_inline('SELECT', self.connection.db) + self.execute_command('SELECT', self.connection.db) def select(self, db, host=None, port=None, password=None, socket_timeout=None): @@ -419,15 +407,15 @@ class Redis(threading.local): Tell the Redis server to save its data to disk. Unlike save(), this method is asynchronous and returns immediately. """ - return self.format_inline('BGSAVE') + return self.execute_command('BGSAVE') def dbsize(self): "Returns the number of keys in the current database" - return self.format_inline('DBSIZE') + return self.execute_command('DBSIZE') def delete(self, *names): "Delete one or more keys specified by ``names``" - return self.format_inline('DEL', *names) + return self.execute_command('DEL', *names) __delitem__ = delete def flush(self, all_dbs=False): @@ -440,33 +428,33 @@ class Redis(threading.local): def flushall(self): "Delete all keys in all databases on the current host" - return self.format_inline('FLUSHALL') + return self.execute_command('FLUSHALL') def flushdb(self): "Delete all keys in the current database" - return self.format_inline('FLUSHDB') + return self.execute_command('FLUSHDB') def info(self): "Returns a dictionary containing information about the Redis server" - return self.format_inline('INFO') + return self.execute_command('INFO') def lastsave(self): """ Return a Python datetime object representing the last time the Redis database was saved to disk """ - return self.format_inline('LASTSAVE') + return self.execute_command('LASTSAVE') def ping(self): "Ping the Redis server" - return self.format_inline('PING') + return self.execute_command('PING') def save(self): """ Tell the Redis server to save its data to disk, blocking until the save is complete """ - return self.format_inline('SAVE') + return self.execute_command('SAVE') #### BASIC KEY COMMANDS #### def decr(self, name, amount=1): @@ -474,22 +462,22 @@ class Redis(threading.local): Decrements the value of ``key`` by ``amount``. If no key exists, the value will be initialized as 0 - ``amount`` """ - return self.format_inline('DECRBY', name, amount) + return self.execute_command('DECRBY', name, amount) def exists(self, name): "Returns a boolean indicating whether key ``name`` exists" - return self.format_inline('EXISTS', name) + return self.execute_command('EXISTS', name) __contains__ = exists def expire(self, name, time): "Set an expire on key ``name`` for ``time`` seconds" - return self.format_inline('EXPIRE', name, time) + return self.execute_command('EXPIRE', name, time) def get(self, name): """ Return the value at key ``name``, or None of the key doesn't exist """ - return self.format_inline('GET', name) + return self.execute_command('GET', name) __getitem__ = get def getset(self, name, value): @@ -497,18 +485,18 @@ class Redis(threading.local): Set the value at key ``name`` to ``value`` if key doesn't exist Return the value at key ``name`` atomically """ - return self.format_bulk('GETSET', name, value) + return self.execute_command('GETSET', name, value) def incr(self, name, amount=1): """ Increments the value of ``key`` by ``amount``. If no key exists, the value will be initialized as ``amount`` """ - return self.format_inline('INCRBY', name, amount) + return self.execute_command('INCRBY', name, amount) def keys(self, pattern='*'): "Returns a list of keys matching ``pattern``" - return self.format_inline('KEYS', pattern) + return self.execute_command('KEYS', pattern) def mget(self, keys, *args): """ @@ -517,13 +505,13 @@ class Redis(threading.local): * Passing *args to this method has been deprecated * """ keys = list_or_args('mget', keys, args) - return self.format_inline('MGET', *keys) + return self.execute_command('MGET', *keys) def mset(self, mapping): "Sets each key in the ``mapping`` dict to its corresponding value" items = [] [items.extend(pair) for pair in mapping.iteritems()] - return self.format_multi_bulk('MSET', *items) + return self.execute_command('MSET', *items) def msetnx(self, mapping): """ @@ -532,15 +520,15 @@ class Redis(threading.local): """ items = [] [items.extend(pair) for pair in mapping.iteritems()] - return self.format_multi_bulk('MSETNX', *items) + return self.execute_command('MSETNX', *items) def move(self, name, db): "Moves the key ``name`` to a different Redis database ``db``" - return self.format_inline('MOVE', name, db) + return self.execute_command('MOVE', name, db) def randomkey(self): "Returns the name of a random key" - return self.format_inline('RANDOMKEY') + return self.execute_command('RANDOMKEY') def rename(self, src, dst, **kwargs): """ @@ -557,11 +545,11 @@ class Redis(threading.local): "use Redis.renamenx instead")) if kwargs['preserve']: return self.renamenx(src, dst) - return self.format_inline('RENAME', src, dst) + return self.execute_command('RENAME', src, dst) def renamenx(self, src, dst): "Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist" - return self.format_inline('RENAMENX', src, dst) + return self.execute_command('RENAMENX', src, dst) def set(self, name, value, **kwargs): @@ -587,20 +575,20 @@ class Redis(threading.local): "use Redis.setnx() instead")) if kwargs['preserve']: return self.setnx(name, value) - return self.format_bulk('SET', name, value) + return self.execute_command('SET', name, value) __setitem__ = set def setnx(self, name, value): "Set the value of key ``name`` to ``value`` if key doesn't exist" - return self.format_bulk('SETNX', name, value) + return self.execute_command('SETNX', name, value) def ttl(self, name): "Returns the number of seconds until the key ``name`` will expire" - return self.format_inline('TTL', name) + return self.execute_command('TTL', name) def type(self, name): "Returns the type of key ``name``" - return self.format_inline('TYPE', name) + return self.execute_command('TYPE', name) #### LIST COMMANDS #### @@ -617,7 +605,7 @@ class Redis(threading.local): """ keys = list(keys) keys.append(timeout) - return self.format_inline('BLPOP', *keys) + return self.execute_command('BLPOP', *keys) def brpop(self, keys, timeout=0): """ @@ -632,7 +620,7 @@ class Redis(threading.local): """ keys = list(keys) keys.append(timeout) - return self.format_inline('BRPOP', *keys) + return self.execute_command('BRPOP', *keys) def lindex(self, name, index): """ @@ -641,19 +629,19 @@ class Redis(threading.local): Negative indexes are supported and will return an item at the end of the list """ - return self.format_inline('LINDEX', name, index) + return self.execute_command('LINDEX', name, index) def llen(self, name): "Return the length of the list ``name``" - return self.format_inline('LLEN', name) + return self.execute_command('LLEN', name) def lpop(self, name): "Remove and return the first item of the list ``name``" - return self.format_inline('LPOP', name) + return self.execute_command('LPOP', name) def lpush(self, name, value): "Push ``value`` onto the head of the list ``name``" - return self.format_bulk('LPUSH', name, value) + return self.execute_command('LPUSH', name, value) def lrange(self, name, start, end): """ @@ -663,7 +651,7 @@ class Redis(threading.local): ``start`` and ``end`` can be negative numbers just like Python slicing notation """ - return self.format_inline('LRANGE', name, start, end) + return self.execute_command('LRANGE', name, start, end) def lrem(self, name, value, num=0): """ @@ -671,11 +659,11 @@ class Redis(threading.local): If ``num`` is 0, then all occurrences will be removed """ - return self.format_bulk('LREM', name, num, value) + return self.execute_command('LREM', name, num, value) def lset(self, name, index, value): "Set ``position`` of list ``name`` to ``value``" - return self.format_bulk('LSET', name, index, value) + return self.execute_command('LSET', name, index, value) def ltrim(self, name, start, end): """ @@ -685,7 +673,7 @@ class Redis(threading.local): ``start`` and ``end`` can be negative numbers just like Python slicing notation """ - return self.format_inline('LTRIM', name, start, end) + return self.execute_command('LTRIM', name, start, end) def pop(self, name, tail=False): """ @@ -717,18 +705,18 @@ class Redis(threading.local): def rpop(self, name): "Remove and return the last item of the list ``name``" - return self.format_inline('RPOP', name) + return self.execute_command('RPOP', name) def rpoplpush(self, src, dst): """ RPOP a value off of the ``src`` list and atomically LPUSH it on to the ``dst`` list. Returns the value. """ - return self.format_inline('RPOPLPUSH', src, dst) + return self.execute_command('RPOPLPUSH', src, dst) def rpush(self, name, value): "Push ``value`` onto the tail of the list ``name``" - return self.format_bulk('RPUSH', name, value) + return self.execute_command('RPUSH', name, value) def sort(self, name, start=None, num=None, by=None, get=None, desc=False, alpha=False, store=None): @@ -757,33 +745,38 @@ class Redis(threading.local): pieces = [name] if by is not None: - pieces.append('BY %s' % by) + pieces.append('BY') + pieces.append(by) if start is not None and num is not None: - pieces.append('LIMIT %s %s' % (start, num)) + pieces.append('LIMIT') + pieces.append(start) + pieces.append(num) if get is not None: - pieces.append('GET %s' % get) + pieces.append('GET') + pieces.append(get) if desc: pieces.append('DESC') if alpha: pieces.append('ALPHA') if store is not None: - pieces.append('STORE %s' % store) - return self.format_inline('SORT', *pieces) + pieces.append('STORE') + pieces.append(store) + return self.execute_command('SORT', *pieces) #### SET COMMANDS #### def sadd(self, name, value): "Add ``value`` to set ``name``" - return self.format_bulk('SADD', name, value) + return self.execute_command('SADD', name, value) def scard(self, name): "Return the number of elements in set ``name``" - return self.format_inline('SCARD', name) + return self.execute_command('SCARD', name) def sdiff(self, keys, *args): "Return the difference of sets specified by ``keys``" keys = list_or_args('sdiff', keys, args) - return self.format_inline('SDIFF', *keys) + return self.execute_command('SDIFF', *keys) def sdiffstore(self, dest, keys, *args): """ @@ -791,12 +784,12 @@ class Redis(threading.local): set named ``dest``. Returns the number of keys in the new set. """ keys = list_or_args('sdiffstore', keys, args) - return self.format_inline('SDIFFSTORE', dest, *keys) + return self.execute_command('SDIFFSTORE', dest, *keys) def sinter(self, keys, *args): "Return the intersection of sets specified by ``keys``" keys = list_or_args('sinter', keys, args) - return self.format_inline('SINTER', *keys) + return self.execute_command('SINTER', *keys) def sinterstore(self, dest, keys, *args): """ @@ -804,36 +797,36 @@ class Redis(threading.local): set named ``dest``. Returns the number of keys in the new set. """ keys = list_or_args('sinterstore', keys, args) - return self.format_inline('SINTERSTORE', dest, *keys) + return self.execute_command('SINTERSTORE', dest, *keys) def sismember(self, name, value): "Return a boolean indicating if ``value`` is a member of set ``name``" - return self.format_bulk('SISMEMBER', name, value) + return self.execute_command('SISMEMBER', name, value) def smembers(self, name): "Return all members of the set ``name``" - return self.format_inline('SMEMBERS', name) + return self.execute_command('SMEMBERS', name) def smove(self, src, dst, value): "Move ``value`` from set ``src`` to set ``dst`` atomically" - return self.format_bulk('SMOVE', src, dst, value) + return self.execute_command('SMOVE', src, dst, value) def spop(self, name): "Remove and return a random member of set ``name``" - return self.format_inline('SPOP', name) + return self.execute_command('SPOP', name) def srandmember(self, name): "Return a random member of set ``name``" - return self.format_inline('SRANDMEMBER', name) + return self.execute_command('SRANDMEMBER', name) def srem(self, name, value): "Remove ``value`` from set ``name``" - return self.format_bulk('SREM', name, value) + return self.execute_command('SREM', name, value) def sunion(self, keys, *args): "Return the union of sets specifiued by ``keys``" keys = list_or_args('sunion', keys, args) - return self.format_inline('SUNION', *keys) + return self.execute_command('SUNION', *keys) def sunionstore(self, dest, keys, *args): """ @@ -841,17 +834,17 @@ class Redis(threading.local): set named ``dest``. Returns the number of keys in the new set. """ keys = list_or_args('sunionstore', keys, args) - return self.format_inline('SUNIONSTORE', dest, *keys) + return self.execute_command('SUNIONSTORE', dest, *keys) #### SORTED SET COMMANDS #### def zadd(self, name, value, score): "Add member ``value`` with score ``score`` to sorted set ``name``" - return self.format_bulk('ZADD', name, score, value) + return self.execute_command('ZADD', name, score, value) def zcard(self, name): "Return the number of elements in the sorted set ``name``" - return self.format_inline('ZCARD', name) + return self.execute_command('ZCARD', name) def zincr(self, key, member, value=1): "This has been deprecated, use zincrby instead" @@ -862,7 +855,7 @@ class Redis(threading.local): def zincrby(self, name, value, amount=1): "Increment the score of ``value`` in sorted set ``name`` by ``amount``" - return self.format_bulk('ZINCRBY', name, amount, value) + return self.execute_command('ZINCRBY', name, amount, value) def zrange(self, name, start, end, desc=False, withscores=False): """ @@ -881,7 +874,7 @@ class Redis(threading.local): pieces = ['ZRANGE', name, start, end] if withscores: pieces.append('withscores') - return self.format_inline(*pieces, **{'withscores': withscores}) + return self.execute_command(*pieces, **{'withscores': withscores}) def zrangebyscore(self, name, min, max, start=None, num=None, withscores=False): @@ -902,25 +895,25 @@ class Redis(threading.local): pieces.extend(['LIMIT', start, num]) if withscores: pieces.append('withscores') - return self.format_inline(*pieces, **{'withscores': withscores}) + return self.execute_command(*pieces, **{'withscores': withscores}) def zrank(self, name, value): """ Returns a 0-based value indicating the rank of ``value`` in sorted set ``name`` """ - return self.format_bulk('ZRANK', name, value) + return self.execute_command('ZRANK', name, value) def zrem(self, name, value): "Remove member ``value`` from sorted set ``name``" - return self.format_bulk('ZREM', name, value) + return self.execute_command('ZREM', name, value) def zremrangebyscore(self, name, min, max): """ Remove all elements in the sorted set ``name`` with scores between ``min`` and ``max`` """ - return self.format_inline('ZREMRANGEBYSCORE', name, min, max) + return self.execute_command('ZREMRANGEBYSCORE', name, min, max) def zrevrange(self, name, start, num, withscores=False): """ @@ -935,59 +928,59 @@ class Redis(threading.local): pieces = ['ZREVRANGE', name, start, num] if withscores: pieces.append('withscores') - return self.format_inline(*pieces, **{'withscores': withscores}) + return self.execute_command(*pieces, **{'withscores': withscores}) def zrevrank(self, name, value): """ Returns a 0-based value indicating the descending rank of ``value`` in sorted set ``name`` """ - return self.format_bulk('ZREVRANK', name, value) + return self.execute_command('ZREVRANK', name, value) def zscore(self, name, value): "Return the score of element ``value`` in sorted set ``name``" - return self.format_bulk('ZSCORE', name, value) + return self.execute_command('ZSCORE', name, value) #### HASH COMMANDS #### def hdel(self, name, key): "Delete ``key`` from hash ``name``" - return self.format_bulk('HDEL', name, key) + return self.execute_command('HDEL', name, key) def hexists(self, name, key): "Returns a boolean indicating if ``key`` exists within hash ``name``" - return self.format_bulk('HEXISTS', name, key) + return self.execute_command('HEXISTS', name, key) def hget(self, name, key): "Return the value of ``key`` within the hash ``name``" - return self.format_bulk('HGET', name, key) + return self.execute_command('HGET', name, key) def hgetall(self, name): "Return a Python dict of the hash's name/value pairs" - return self.format_inline('HGETALL', name) + return self.execute_command('HGETALL', name) def hincrby(self, name, key, amount=1): "Increment the value of ``key`` in hash ``name`` by ``amount``" - return self.format_inline('HINCRBY', name, key, amount) + return self.execute_command('HINCRBY', name, key, amount) def hkeys(self, name): "Return the list of keys within hash ``name``" - return self.format_inline('HKEYS', name) + return self.execute_command('HKEYS', name) def hlen(self, name): "Return the number of elements in hash ``name``" - return self.format_inline('HLEN', name) + return self.execute_command('HLEN', name) def hset(self, name, key, value): """ Set ``key`` to ``value`` within hash ``name`` Returns 1 if HSET created a new field, otherwise 0 """ - return self.format_multi_bulk('HSET', name, key, value) + return self.execute_command('HSET', name, key, value) def hvals(self, name): "Return the list of values within hash ``name``" - return self.format_inline('HVALS', name) + return self.execute_command('HVALS', name) # channels @@ -995,7 +988,7 @@ class Redis(threading.local): "Subscribe to ``channels``, waiting for messages to be published" if isinstance(channels, basestring): channels = [channels] - response = self.format_inline('SUBSCRIBE', *channels) + response = self.execute_command('SUBSCRIBE', *channels) # this is *after* the SUBSCRIBE in order to allow for lazy and broken # connections that need to issue AUTH and SELECT commands self.subscribed = True @@ -1005,14 +998,14 @@ class Redis(threading.local): "Unsubscribe to ``channels``. If empty, unsubscribe from all channels" if isinstance(channels, basestring): channels = [channels] - return self.format_inline('UNSUBSCRIBE', *channels) + return self.execute_command('UNSUBSCRIBE', *channels) def publish(self, channel, message): """ Publish ``message`` on ``channel``. Returns the number of subscribers the message was delivered to. """ - return self.format_bulk('PUBLISH', channel, message) + return self.execute_command('PUBLISH', channel, message) def listen(self): "Listen for messages on channels this client has been subscribed to" @@ -1051,9 +1044,9 @@ class Pipeline(Redis): def reset(self): self.command_stack = [] - self.format_inline('MULTI') + self.execute_command('MULTI') - def execute_command(self, command_name, command, **options): + def _execute_command(self, command_name, command, **options): """ Stage a command to be executed when execute() is next called @@ -1070,7 +1063,7 @@ class Pipeline(Redis): # _setup_connection(). run these commands immediately without # buffering them. if command_name in ('AUTH', 'SELECT'): - return super(Pipeline, self).execute_command( + return super(Pipeline, self)._execute_command( command_name, command, **options) else: self.command_stack.append((command_name, command, options)) @@ -1103,7 +1096,7 @@ class Pipeline(Redis): def execute(self): "Execute all the commands in the current pipeline" - self.format_inline('EXEC') + self.execute_command('EXEC') stack = self.command_stack self.reset() try: diff --git a/tests/server_commands.py b/tests/server_commands.py index 2811ee9..da328ac 100644 --- a/tests/server_commands.py +++ b/tests/server_commands.py @@ -4,20 +4,20 @@ import datetime from distutils.version import StrictVersion class ServerCommandsTestCase(unittest.TestCase): - + def setUp(self): self.client = redis.Redis(host='localhost', port=6379, db=9) self.client.flushdb() - + def tearDown(self): self.client.flushdb() - + # GENERAL SERVER COMMANDS def test_dbsize(self): self.client['a'] = 'foo' self.client['b'] = 'bar' self.assertEquals(self.client.dbsize(), 2) - + def test_get_and_set(self): # get and set can't be tested independently of each other self.assertEquals(self.client.get('a'), None) @@ -30,35 +30,35 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.get('byte_string'), byte_string) self.assertEquals(self.client.get('integer'), str(integer)) self.assertEquals(self.client.get('unicode_string').decode('utf-8'), unicode_string) - + def test_getitem_and_setitem(self): self.client['a'] = 'bar' self.assertEquals(self.client['a'], 'bar') - + def test_delete(self): self.assertEquals(self.client.delete('a'), False) self.client['a'] = 'foo' self.assertEquals(self.client.delete('a'), True) - + def test_delitem(self): self.client['a'] = 'foo' del self.client['a'] self.assertEquals(self.client['a'], None) - + def test_info(self): self.client['a'] = 'foo' self.client['b'] = 'bar' info = self.client.info() self.assert_(isinstance(info, dict)) self.assertEquals(info['db9']['keys'], 2) - + def test_lastsave(self): self.assert_(isinstance(self.client.lastsave(), datetime.datetime)) - + def test_ping(self): self.assertEquals(self.client.ping(), True) - - + + # KEYS def test_decr(self): self.assertEquals(self.client.decr('a'), -1) @@ -67,21 +67,21 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client['a'], '-2') self.assertEquals(self.client.decr('a', amount=5), -7) self.assertEquals(self.client['a'], '-7') - + def test_exists(self): self.assertEquals(self.client.exists('a'), False) self.client['a'] = 'foo' self.assertEquals(self.client.exists('a'), True) - + def expire(self): self.assertEquals(self.client.expire('a'), False) self.client['a'] = 'foo' self.assertEquals(self.client.expire('a'), True) - + def test_getset(self): self.assertEquals(self.client.getset('a', 'foo'), None) self.assertEquals(self.client.getset('a', 'bar'), 'foo') - + def test_incr(self): self.assertEquals(self.client.incr('a'), 1) self.assertEquals(self.client['a'], '1') @@ -89,30 +89,30 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client['a'], '2') self.assertEquals(self.client.incr('a', amount=5), 7) self.assertEquals(self.client['a'], '7') - + def test_keys(self): self.assertEquals(self.client.keys(), []) keys = set(['test_a', 'test_b', 'testc']) for key in keys: self.client[key] = 1 - self.assertEquals(set(self.client.keys(pattern='test_*')), + self.assertEquals(set(self.client.keys(pattern='test_*')), keys - set(['testc'])) self.assertEquals(set(self.client.keys(pattern='test*')), keys) - + def test_mget(self): self.assertEquals(self.client.mget(['a', 'b']), [None, None]) self.client['a'] = '1' self.client['b'] = '2' self.client['c'] = '3' - self.assertEquals(self.client.mget(['a', 'other', 'b', 'c']), + self.assertEquals(self.client.mget(['a', 'other', 'b', 'c']), ['1', None, '2', '3']) - + def test_mset(self): d = {'a': '1', 'b': '2', 'c': '3'} self.assert_(self.client.mset(d)) for k,v in d.iteritems(): self.assertEquals(self.client[k], v) - + def test_msetnx(self): d = {'a': '1', 'b': '2', 'c': '3'} self.assert_(self.client.msetnx(d)) @@ -121,33 +121,33 @@ class ServerCommandsTestCase(unittest.TestCase): for k,v in d.iteritems(): self.assertEquals(self.client[k], v) self.assertEquals(self.client['d'], None) - + def test_randomkey(self): self.assertEquals(self.client.randomkey(), None) self.client['a'] = '1' self.client['b'] = '2' self.client['c'] = '3' self.assert_(self.client.randomkey() in ('a', 'b', 'c')) - + def test_rename(self): self.client['a'] = '1' self.assert_(self.client.rename('a', 'b')) self.assertEquals(self.client['a'], None) self.assertEquals(self.client['b'], '1') - + def test_renamenx(self): self.client['a'] = '1' self.client['b'] = '2' self.assert_(not self.client.renamenx('a', 'b')) self.assertEquals(self.client['a'], '1') self.assertEquals(self.client['b'], '2') - + def test_setnx(self): self.assert_(self.client.setnx('a', '1')) self.assertEquals(self.client['a'], '1') self.assert_(not self.client.setnx('a', '2')) self.assertEquals(self.client['a'], '1') - + def test_ttl(self): self.assertEquals(self.client.ttl('a'), None) self.client['a'] = '1' @@ -156,7 +156,7 @@ class ServerCommandsTestCase(unittest.TestCase): # this could potentially fail if for some reason there's a gap of # time between these commands. self.assertEquals(self.client.ttl('a'), 10) - + def test_type(self): self.assertEquals(self.client.type('a'), 'none') self.client['a'] = '1' @@ -170,12 +170,12 @@ class ServerCommandsTestCase(unittest.TestCase): del self.client['a'] self.client.zadd('a', '1', 1) self.assertEquals(self.client.type('a'), 'zset') - + # LISTS def make_list(self, name, l): for i in l: self.client.rpush(name, i) - + def test_blpop(self): self.make_list('a', 'ab') self.make_list('b', 'cd') @@ -184,7 +184,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['a', 'a']) self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), ['a', 'b']) self.assertEquals(self.client.blpop(['b', 'a'], timeout=1), None) - + def test_brpop(self): self.make_list('a', 'ab') self.make_list('b', 'cd') @@ -193,7 +193,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['a', 'b']) self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), ['a', 'a']) self.assertEquals(self.client.brpop(['b', 'a'], timeout=1), None) - + def test_lindex(self): # no key self.assertEquals(self.client.lindex('a', '0'), None) @@ -206,7 +206,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lindex('a', '0'), 'a') self.assertEquals(self.client.lindex('a', '1'), 'b') self.assertEquals(self.client.lindex('a', '2'), 'c') - + def test_llen(self): # no key self.assertEquals(self.client.llen('a'), 0) @@ -217,7 +217,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_list('a', 'abc') self.assertEquals(self.client.llen('a'), 3) - + def test_lpop(self): # no key self.assertEquals(self.client.lpop('a'), None) @@ -231,7 +231,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lpop('a'), 'b') self.assertEquals(self.client.lpop('a'), 'c') self.assertEquals(self.client.lpop('a'), None) - + def test_lpush(self): # key is not a list self.client['a'] = 'b' @@ -247,7 +247,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assert_(self.client.lpush('a', 'a')) self.assertEquals(self.client.lindex('a', 0), 'a') self.assertEquals(self.client.lindex('a', 1), 'b') - + def test_lrange(self): # no key self.assertEquals(self.client.lrange('a', 0, 1), None) @@ -259,7 +259,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_list('a', 'abcde') self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'b', 'c']) self.assertEquals(self.client.lrange('a', 2, 10), ['c', 'd', 'e']) - + def test_lrem(self): # no key self.assertEquals(self.client.lrem('a', 'foo'), 0) @@ -274,7 +274,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lrem('a', 'a'), 3) # remove all the elements in the list means the key is deleted self.assertEquals(self.client.lrange('a', 0, 1), None) - + def test_lset(self): # no key self.assertRaises(redis.ResponseError, self.client.lset, 'a', 1, 'b') @@ -287,7 +287,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'b', 'c']) self.assert_(self.client.lset('a', 1, 'd')) self.assertEquals(self.client.lrange('a', 0, 2), ['a', 'd', 'c']) - + def test_ltrim(self): # no key -- TODO: Not sure why this is actually true. self.assert_(self.client.ltrim('a', 0, 2)) @@ -299,7 +299,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_list('a', 'abc') self.assert_(self.client.ltrim('a', 0, 1)) self.assertEquals(self.client.lrange('a', 0, 5), ['a', 'b']) - + def test_lpop(self): # no key self.assertEquals(self.client.lpop('a'), None) @@ -313,7 +313,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lpop('a'), 'b') self.assertEquals(self.client.lpop('a'), 'c') self.assertEquals(self.client.lpop('a'), None) - + def test_rpop(self): # no key self.assertEquals(self.client.rpop('a'), None) @@ -327,7 +327,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.rpop('a'), 'b') self.assertEquals(self.client.rpop('a'), 'a') self.assertEquals(self.client.rpop('a'), None) - + def test_rpoplpush(self): # no src key self.make_list('b', ['b1']) @@ -354,7 +354,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.lrange('a', 0, 2), ['a1', 'a2']) self.assertEquals(self.client.lrange('b', 0, 4), ['a3', 'b1', 'b2', 'b3']) - + def test_rpush(self): # key is not a list self.client['a'] = 'b' @@ -370,12 +370,12 @@ class ServerCommandsTestCase(unittest.TestCase): self.assert_(self.client.rpush('a', 'b')) self.assertEquals(self.client.lindex('a', 0), 'a') self.assertEquals(self.client.lindex('a', 1), 'b') - + # Set commands def make_set(self, name, l): for i in l: self.client.sadd(name, i) - + def test_sadd(self): # key is not a set self.client['a'] = 'a' @@ -385,7 +385,7 @@ class ServerCommandsTestCase(unittest.TestCase): members = set(['a1', 'a2', 'a3']) self.make_set('a', members) self.assertEquals(self.client.smembers('a'), members) - + def test_scard(self): # key is not a set self.client['a'] = 'a' @@ -394,7 +394,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_set('a', 'abc') self.assertEquals(self.client.scard('a'), 3) - + def test_sdiff(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -404,7 +404,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_set('b', ['b1', 'a2', 'b3']) self.assertEquals(self.client.sdiff(['a', 'b']), set(['a1', 'a3'])) - + def test_sdiffstore(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -418,7 +418,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.assertEquals(self.client.sdiffstore('c', ['a', 'b']), 2) self.assertEquals(self.client.smembers('c'), set(['a1', 'a3'])) - + def test_sinter(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -428,7 +428,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_set('b', ['a1', 'b2', 'a3']) self.assertEquals(self.client.sinter(['a', 'b']), set(['a1', 'a3'])) - + def test_sinterstore(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -442,7 +442,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.assertEquals(self.client.sinterstore('c', ['a', 'b']), 2) self.assertEquals(self.client.smembers('c'), set(['a1', 'a3'])) - + def test_sismember(self): # key is not a set self.client['a'] = 'a' @@ -454,7 +454,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.sismember('a', 'b'), True) self.assertEquals(self.client.sismember('a', 'c'), True) self.assertEquals(self.client.sismember('a', 'd'), False) - + def test_smembers(self): # key is not a set self.client['a'] = 'a' @@ -463,7 +463,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_set('a', 'abc') self.assertEquals(self.client.smembers('a'), set(['a', 'b', 'c'])) - + def test_smove(self): # src key is not set self.make_set('b', ['b1', 'b2']) @@ -485,7 +485,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assert_(self.client.smove('a', 'b', 'a1')) self.assertEquals(self.client.smembers('a'), set(['a2'])) self.assertEquals(self.client.smembers('b'), set(['b1', 'b2', 'a1'])) - + def test_spop(self): # key is not set self.assertEquals(self.client.spop('a'), None) @@ -498,7 +498,7 @@ class ServerCommandsTestCase(unittest.TestCase): value = self.client.spop('a') self.assert_(value in 'abc') self.assertEquals(self.client.smembers('a'), set('abc') - set(value)) - + def test_srandmember(self): # key is not set self.assertEquals(self.client.srandmember('a'), None) @@ -509,7 +509,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_set('a', 'abc') self.assert_(self.client.srandmember('a') in 'abc') - + def test_srem(self): # key is not set self.assertEquals(self.client.srem('a', 'a'), False) @@ -522,7 +522,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.srem('a', 'd'), False) self.assertEquals(self.client.srem('a', 'b'), True) self.assertEquals(self.client.smembers('a'), set('ac')) - + def test_sunion(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -533,7 +533,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_set('b', ['a1', 'b2', 'a3']) self.assertEquals(self.client.sunion(['a', 'b']), set(['a1', 'a2', 'a3', 'b2'])) - + def test_sunionstore(self): # some key is not a set self.make_set('a', ['a1', 'a2', 'a3']) @@ -548,16 +548,16 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.sunionstore('c', ['a', 'b']), 4) self.assertEquals(self.client.smembers('c'), set(['a1', 'a2', 'a3', 'b2'])) - + # SORTED SETS def make_zset(self, name, d): for k,v in d.items(): self.client.zadd(name, k, v) - + def test_zadd(self): self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) self.assertEquals(self.client.zrange('a', 0, 3), ['a1', 'a2', 'a3']) - + def test_zcard(self): # key is not a zset self.client['a'] = 'a' @@ -566,7 +566,7 @@ class ServerCommandsTestCase(unittest.TestCase): # real logic self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) self.assertEquals(self.client.zcard('a'), 3) - + def test_zincrby(self): # key is not a zset self.client['a'] = 'a' @@ -578,7 +578,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zincrby('a', 'a3', amount=5), 8.0) self.assertEquals(self.client.zscore('a', 'a2'), 3.0) self.assertEquals(self.client.zscore('a', 'a3'), 8.0) - + def test_zrange(self): # key is not a zset self.client['a'] = 'a' @@ -594,8 +594,8 @@ class ServerCommandsTestCase(unittest.TestCase): [('a2', 2.0), ('a3', 3.0)]) # a non existant key should return None self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), None) - - + + def test_zrangebyscore(self): # key is not a zset self.client['a'] = 'a' @@ -612,7 +612,7 @@ class ServerCommandsTestCase(unittest.TestCase): [('a2', 2.0), ('a3', 3.0), ('a4', 4.0)]) # a non existant key should return None self.assertEquals(self.client.zrangebyscore('b', 0, 1, withscores=True), None) - + def test_zrank(self): # key is not a zset self.client['a'] = 'a' @@ -625,7 +625,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zrank('a', 'a3'), 2) self.assertEquals(self.client.zrank('a', 'a4'), 3) self.assertEquals(self.client.zrank('a', 'a5'), 4) - + def test_zrem(self): # key is not a zset self.client['a'] = 'a' @@ -637,7 +637,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a3']) self.assertEquals(self.client.zrem('a', 'b'), False) self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a3']) - + def test_zremrangebyscore(self): # key is not a zset self.client['a'] = 'a' @@ -650,7 +650,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a5']) self.assertEquals(self.client.zremrangebyscore('a', 2, 4), 0) self.assertEquals(self.client.zrange('a', 0, 5), ['a1', 'a5']) - + def test_zrevrange(self): # key is not a zset self.client['a'] = 'a' @@ -667,7 +667,7 @@ class ServerCommandsTestCase(unittest.TestCase): [('a2', 2.0), ('a1', 1.0)]) # a non existant key should return None self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), None) - + def test_zrevrank(self): # key is not a zset self.client['a'] = 'a' @@ -680,7 +680,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zrevrank('a', 'a3'), 2) self.assertEquals(self.client.zrevrank('a', 'a4'), 3) self.assertEquals(self.client.zrevrank('a', 'a5'), 4) - + def test_zscore(self): # key is not a zset self.client['a'] = 'a' @@ -691,12 +691,12 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.zscore('a', 'a2'), 2.0) # test a non-existant member self.assertEquals(self.client.zscore('a', 'a4'), None) - + # HASHES def make_hash(self, key, d): for k,v in d.iteritems(): self.client.hset(key, k, v) - + def test_hget_and_hset(self): # key is not a hash self.client['a'] = 'a' @@ -715,7 +715,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.hget('a', 'a4'), '4') # key inside of hash that doesn't exist returns null value self.assertEquals(self.client.hget('a', 'b'), None) - + def test_hdel(self): # key is not a hash self.client['a'] = 'a' @@ -728,7 +728,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.hget('a', 'a2'), '2') self.assert_(self.client.hdel('a', 'a2')) self.assertEquals(self.client.hget('a', 'a2'), None) - + def test_hexists(self): # key is not a hash self.client['a'] = 'a' @@ -742,7 +742,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.hexists('a', 'a4'), False) self.client.hdel('a', 'a1') self.assertEquals(self.client.hexists('a', 'a1'), False) - + def test_hgetall(self): # key is not a hash self.client['a'] = 'a' @@ -755,7 +755,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_hash('a', h) remote_hash = self.client.hgetall('a') self.assertEquals(h, remote_hash) - + def test_hincrby(self): # key is not a hash self.client['a'] = 'a' @@ -773,9 +773,9 @@ class ServerCommandsTestCase(unittest.TestCase): # finally a key that's not an int self.client.hset('a', 'a3', 'foo') self.assertEquals(self.client.hincrby('a', 'a3'), 1) - - - + + + def test_hkeys(self): # key is not a hash self.client['a'] = 'a' @@ -791,7 +791,7 @@ class ServerCommandsTestCase(unittest.TestCase): remote_keys = self.client.hkeys('a') remote_keys.sort() self.assertEquals(keys, remote_keys) - + def test_hlen(self): # key is not a hash self.client['a'] = 'a' @@ -804,7 +804,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.assertEquals(self.client.hlen('a'), 3) self.client.hdel('a', 'a3') self.assertEquals(self.client.hlen('a'), 2) - + def test_hvals(self): # key is not a hash self.client['a'] = 'a' @@ -820,7 +820,7 @@ class ServerCommandsTestCase(unittest.TestCase): remote_vals = self.client.hvals('a') remote_vals.sort() self.assertEquals(vals, remote_vals) - + # SORT def test_sort_bad_key(self): # key is not set @@ -829,15 +829,15 @@ class ServerCommandsTestCase(unittest.TestCase): self.client['a'] = 'a' self.assertRaises(redis.ResponseError, self.client.sort, 'a') del self.client['a'] - + def test_sort_basic(self): self.make_list('a', '3214') self.assertEquals(self.client.sort('a'), ['1', '2', '3', '4']) - + def test_sort_limited(self): self.make_list('a', '3214') self.assertEquals(self.client.sort('a', start=1, num=2), ['2', '3']) - + def test_sort_by(self): self.client['score:1'] = 8 self.client['score:2'] = 3 @@ -845,7 +845,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_list('a_values', '123') self.assertEquals(self.client.sort('a_values', by='score:*'), ['2', '3', '1']) - + def test_sort_get(self): self.client['user:1'] = 'u1' self.client['user:2'] = 'u2' @@ -853,22 +853,22 @@ class ServerCommandsTestCase(unittest.TestCase): self.make_list('a', '231') self.assertEquals(self.client.sort('a', get='user:*'), ['u1', 'u2', 'u3']) - + def test_sort_desc(self): self.make_list('a', '231') self.assertEquals(self.client.sort('a', desc=True), ['3', '2', '1']) - + def test_sort_alpha(self): self.make_list('a', 'ecbda') self.assertEquals(self.client.sort('a', alpha=True), ['a', 'b', 'c', 'd', 'e']) - + def test_sort_store(self): self.make_list('a', '231') self.assertEquals(self.client.sort('a', store='sorted_values'), 3) self.assertEquals(self.client.lrange('sorted_values', 0, 5), ['1', '2', '3']) - + def test_sort_all_options(self): self.client['user:1:username'] = 'zeus' self.client['user:2:username'] = 'titan' @@ -878,7 +878,7 @@ class ServerCommandsTestCase(unittest.TestCase): self.client['user:6:username'] = 'athena' self.client['user:7:username'] = 'hades' self.client['user:8:username'] = 'dionysus' - + self.client['user:1:favorite_drink'] = 'yuengling' self.client['user:2:favorite_drink'] = 'rum' self.client['user:3:favorite_drink'] = 'vodka' @@ -887,11 +887,46 @@ class ServerCommandsTestCase(unittest.TestCase): self.client['user:6:favorite_drink'] = 'water' self.client['user:7:favorite_drink'] = 'gin' self.client['user:8:favorite_drink'] = 'apple juice' - + self.make_list('gods', '12345678') num = self.client.sort('gods', start=2, num=4, by='user:*:username', get='user:*:favorite_drink', desc=True, alpha=True, store='sorted') self.assertEquals(num, 4) self.assertEquals(self.client.lrange('sorted', 0, 10), ['vodka', 'milk', 'gin', 'apple juice']) - + + + ## BINARY SAFE + # TODO add more tests + def test_binary_get_set(self): + self.assertTrue(self.client.set(' foo bar ', '123')) + self.assertEqual(self.client.get(' foo bar '), '123') + + self.assertTrue(self.client.set(' foo\r\nbar\r\n ', '456')) + self.assertEqual(self.client.get(' foo\r\nbar\r\n '), '456') + + self.assertTrue(self.client.set(' \r\n\t\x07\x13 ', '789')) + self.assertEqual(self.client.get(' \r\n\t\x07\x13 '), '789') + + self.assertEqual(sorted(self.client.keys('*')), [' \r\n\t\x07\x13 ', ' foo\r\nbar\r\n ', ' foo bar ']) + + self.assertTrue(self.client.delete(' foo bar ')) + self.assertTrue(self.client.delete(' foo\r\nbar\r\n ')) + self.assertTrue(self.client.delete(' \r\n\t\x07\x13 ')) + + def test_binary_lists(self): + mapping = {'foo bar': '123', + 'foo\r\nbar\r\n': '456', + 'foo\tbar\x07': '789', + } + # fill in lists + for key, value in mapping.iteritems(): + for c in value: + self.assertTrue(self.client.rpush(key, c)) + + # check that KEYS returns all the keys as they are + self.assertEqual(sorted(self.client.keys('*')), sorted(mapping.keys())) + + # check that it is possible to get list content by key name + for key in mapping.keys(): + self.assertEqual(self.client.lrange(key, 0, -1), list(mapping[key])) |