├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── bin ├── redis │ ├── redis-common.conf │ ├── redis-node-1.conf │ ├── redis-node-2.conf │ ├── redis-node-5.conf │ └── redis-node-6.conf └── travis-init.sh ├── pypi-build.sh ├── rediscluster ├── .travis.yml ├── __init__.py └── cluster_client.py ├── requirements.txt ├── run_tests ├── setup.py └── tests ├── __init__.py ├── cluster_commands.py └── config.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | redis.egg-info 3 | build/ 4 | dist/ 5 | dump.rdb 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | env: 4 | global: 5 | - REPO="salimane/rediscluster-py" 6 | - CI_HOME=`pwd`/$REPO 7 | 8 | python: 9 | - "2.6" 10 | - "2.7" 11 | - "3.2" 12 | # - "pypy" 13 | 14 | install: 15 | - pip install -r requirements.txt --use-mirrors 16 | 17 | before_script: 18 | - sh bin/travis-init.sh 19 | 20 | services: 21 | - redis-server 22 | 23 | script: ./run_tests 24 | 25 | notifications: 26 | email: 27 | recipients: me@salimane.com 28 | on_success: change 29 | on_failure: always 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Salimane Adjao Moustapha 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | rediscluster-py 2 | =============== 3 | 4 | a Python interface to a Cluster(*) of Redis key-value stores. 5 | 6 | NOTE: This client is not (yet) compatible with the Redis Cluster Spec found here: http://redis.io/topics/cluster-spec 7 | 8 | Project Goals 9 | ------------- 10 | 11 | The goal of ``rediscluster-py``, together with `rediscluster-php `_, 12 | is to have a consistent, compatible client libraries accross programming languages 13 | when sharding among different Redis instances in a transparent, fast, and 14 | fault tolerant way. ``rediscluster-py`` is based on the awesome 15 | `redis-py `_ StrictRedis 16 | Api, thus the original api commands would work without problems within 17 | the context of a cluster of redis servers 18 | 19 | Continuous Integration 20 | ------------------------------ 21 | 22 | Currently, ``rediscluster-py`` is being tested via travis/drone.io ci for python 23 | version 2.6, 2.7 and 3.2: |Travis Status| |Drone.io Status| 24 | 25 | Installation 26 | ------------ 27 | 28 | :: 29 | 30 | $ sudo pip install rediscluster 31 | 32 | or alternatively (you really should be using pip though): 33 | 34 | :: 35 | 36 | $ sudo easy_install rediscluster 37 | 38 | From source: 39 | 40 | :: 41 | 42 | $ sudo python setup.py install 43 | 44 | Running Tests 45 | ------------- 46 | 47 | :: 48 | 49 | $ git clone https://github.com/salimane/rediscluster-py.git 50 | $ cd rediscluster-py 51 | $ vi tests/config.py 52 | $ ./run_tests 53 | 54 | Getting Started 55 | --------------- 56 | 57 | :: 58 | 59 | >>> import rediscluster 60 | >>> cluster = { 61 | ... # node names 62 | ... 'nodes' : { # masters 63 | ... 'node_1' : {'host' : '127.0.0.1', 'port' : 63791}, 64 | ... 'node_2' : {'host' : '127.0.0.1', 'port' : 63792}, 65 | ... } 66 | ... } 67 | >>> r = rediscluster.StrictRedisCluster(cluster=cluster, db=0) 68 | >>> r.set('foo', 'bar') 69 | True 70 | >>> r.get('foo') 71 | 'bar' 72 | 73 | Cluster Configuration 74 | --------------------- 75 | 76 | The cluster configuration is a hash that is mostly based on the idea of a node, which is simply a host:port pair 77 | that points to a single redis-server instance. This is to make sure it doesn’t get tied it 78 | to a specific host (or port). 79 | The advantage of this is that it is easy to add or remove nodes from 80 | the system to adjust the capacity while the system is running. 81 | 82 | Read Slaves & Write Masters 83 | --------------------------- 84 | 85 | ``rediscluster`` uses the master servers stored in the cluster hash passed during instantiation to auto discover 86 | if any slave is attached to them. It then transparently relay read redis commands to slaves and writes commands to masters. 87 | 88 | There is also support to only use masters even if read redis commands are issued, just specify it at client instantiation like : 89 | 90 | :: 91 | 92 | >>> r = rediscluster.StrictRedisCluster(cluster=cluster, db=0) # read redis commands are routed to slaves 93 | >>> 94 | >>> r = rediscluster.StrictRedisCluster(cluster=cluster, db=0, mastersonly=True) # read redis commands are routed to masters 95 | 96 | Partitioning Algorithm 97 | ---------------------- 98 | 99 | ``rediscluster`` doesn't used a consistent hashing like some other libraries. In order to map every given key to the appropriate Redis node, the algorithm used, 100 | based on crc32 and modulo, is : 101 | 102 | :: 103 | 104 | (abs(binascii.crc32() & 0xffffffff) % ) + 1 105 | 106 | 107 | this is used to ensure some compatibility with other languages, php in particular. 108 | A function ``getnodefor`` is provided to get the node a particular key will be/has been stored to. 109 | 110 | :: 111 | 112 | >>> r.getnodefor('foo') 113 | {'node_2': {'host': '127.0.0.1', 'port': 63792}} 114 | >>> 115 | 116 | Hash Tags 117 | ----------- 118 | 119 | In order to specify your own hash key (so that related keys can all land 120 | on a given node), ``rediscluster`` allows you to pass a string in the form "a{b}" where you’d normally pass a scalar. 121 | The first element of the list is the key to use for the hash and the 122 | second is the real key that should be fetched/modify: 123 | 124 | :: 125 | 126 | >>> r.get("bar{foo}") 127 | >>> 128 | >>> r.mset({"bar{foo}": "bar", "foo": "foo"}) 129 | >>> 130 | >>> r.mget(["bar{foo}", "foo"]) 131 | 132 | In that case “foo” is the hash key but “bar” is still the name of 133 | the key that is fetched from the redis node that “foo” hashes to. 134 | 135 | Multiple Keys Redis Commands 136 | ---------------------------- 137 | 138 | In the context of storing an application data accross many redis servers, commands taking multiple keys 139 | as arguments are harder to use since, if the two keys will hash to two different 140 | instances, the operation can not be performed. Fortunately, rediscluster is a little fault tolerant 141 | in that it still fetches the right result for those multi keys operations as far as the client is concerned. 142 | To do so it processes the related involved redis servers at interface level. 143 | 144 | :: 145 | 146 | >>> r.sadd('foo', *['a1', 'a2', 'a3']) 147 | 3 148 | >>> r.sadd('bar', *['b1', 'a2', 'b3']) 149 | 3 150 | >>> r.sdiffstore('foobar', 'foo', 'bar') 151 | 2 152 | >>> r.smembers('foobar') 153 | set(['a1', 'a3']) 154 | >>> r.getnodefor('foo') 155 | {'node_2': {'host': '127.0.0.1', 'port': 63792}} 156 | >>> r.getnodefor('bar') 157 | {'node_1': {'host': '127.0.0.1', 'port': 63791}} 158 | >>> r.getnodefor('foobar') 159 | {'node_2': {'host': '127.0.0.1', 'port': 63792}} 160 | >>> 161 | 162 | Redis-Sharding & Redis-Copy 163 | --------------------------- 164 | 165 | In order to help with moving an application with a single redis server to a cluster of redis servers 166 | that could take advantage of ``rediscluster``, i wrote `redis-sharding `_ 167 | and `redis-copy `_ 168 | 169 | Information 170 | ----------- 171 | 172 | - Code: ``git clone git://github.com/salimane/rediscluster-py.git`` 173 | - Home: http://github.com/salimane/rediscluster-py 174 | - Bugs: http://github.com/salimane/rediscluster-py/issues 175 | 176 | Author 177 | ------ 178 | 179 | ``rediscluster-py`` is developed and maintained by Salimane Adjao Moustapha 180 | (me@salimane.com). It can be found here: 181 | http://github.com/salimane/rediscluster-py 182 | 183 | .. |Travis Status| image:: https://secure.travis-ci.org/salimane/rediscluster-py.png?branch=master 184 | :target: http://travis-ci.org/salimane/rediscluster-py 185 | .. |Drone.io Status| image:: https://drone.io/github.com/salimane/rediscluster-py/status.png 186 | :target: https://drone.io/github.com/salimane/rediscluster-py/latest 187 | 188 | 189 | .. image:: https://d2weczhvl823v0.cloudfront.net/salimane/rediscluster-py/trend.png 190 | :alt: Bitdeli badge 191 | :target: https://bitdeli.com/free 192 | 193 | -------------------------------------------------------------------------------- /bin/redis/redis-common.conf: -------------------------------------------------------------------------------- 1 | 2 | daemonize yes 3 | timeout 300 4 | loglevel verbose 5 | databases 16 6 | rdbcompression yes 7 | appendonly no 8 | appendfsync everysec 9 | activerehashing yes 10 | save 900 1 11 | save 300 10 12 | save 60 10000 13 | -------------------------------------------------------------------------------- /bin/redis/redis-node-1.conf: -------------------------------------------------------------------------------- 1 | 2 | pidfile redis-node-1.pid 3 | dbfilename dump-node-1.rdb 4 | logfile redis-node-1.log 5 | port 63791 6 | bind 127.0.0.1 7 | -------------------------------------------------------------------------------- /bin/redis/redis-node-2.conf: -------------------------------------------------------------------------------- 1 | 2 | pidfile redis-node-2.pid 3 | dbfilename dump-node-2.rdb 4 | logfile redis-node-2.log 5 | port 63792 6 | bind 127.0.0.1 -------------------------------------------------------------------------------- /bin/redis/redis-node-5.conf: -------------------------------------------------------------------------------- 1 | 2 | pidfile redis-node-5.pid 3 | dbfilename dump-node-5.rdb 4 | logfile redis-node-5.log 5 | port 63795 6 | bind 127.0.0.1 7 | slaveof 127.0.0.1 63792 -------------------------------------------------------------------------------- /bin/redis/redis-node-6.conf: -------------------------------------------------------------------------------- 1 | 2 | pidfile redis-node-6.pid 3 | dbfilename dump-node-6.rdb 4 | logfile redis-node-6.log 5 | port 63796 6 | bind 127.0.0.1 7 | slaveof 127.0.0.1 63791 8 | -------------------------------------------------------------------------------- /bin/travis-init.sh: -------------------------------------------------------------------------------- 1 | sh -c "`which redis-server` $CI_HOME/bin/redis/redis-node-1.conf --dir ${CI_HOME}/bin/redis --include ${CI_HOME}/bin/redis/redis-common.conf" 2 | sh -c "`which redis-server` $CI_HOME/bin/redis/redis-node-2.conf --dir ${CI_HOME}/bin/redis --include ${CI_HOME}/bin/redis/redis-common.conf" 3 | sh -c "`which redis-server` $CI_HOME/bin/redis/redis-node-5.conf --dir ${CI_HOME}/bin/redis --include ${CI_HOME}/bin/redis/redis-common.conf" 4 | sh -c "`which redis-server` $CI_HOME/bin/redis/redis-node-6.conf --dir ${CI_HOME}/bin/redis --include ${CI_HOME}/bin/redis/redis-common.conf" 5 | -------------------------------------------------------------------------------- /pypi-build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | . /home/salimane/htdocs/env/rediscluster2.7/bin/activate 3 | python setup.py sdist upload 4 | python setup.py bdist_egg upload --quiet 5 | python setup.py bdist_wininst --target-version=2.7 --plat-name=win32 register upload --quiet 6 | deactivate 7 | . /home/salimane/htdocs/env/rediscluster3.2/bin/activate 8 | python setup.py bdist_egg upload --quiet 9 | python setup.py bdist_wininst --target-version=3.2 --plat-name=win32 register upload --quiet 10 | deactivate 11 | -------------------------------------------------------------------------------- /rediscluster/.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "2.5" 6 | - "2.6" 7 | - "2.7" 8 | - "3" 9 | - "3.2" 10 | - "3.3" 11 | # - "pypy" 12 | # command to install dependencies 13 | install: 14 | - pip install -r requirements.txt --use-mirrors 15 | # command to run tests 16 | script: run_tests 17 | -------------------------------------------------------------------------------- /rediscluster/__init__.py: -------------------------------------------------------------------------------- 1 | from redis.exceptions import ( 2 | AuthenticationError, 3 | ConnectionError, 4 | DataError, 5 | InvalidResponse, 6 | PubSubError, 7 | RedisError, 8 | ResponseError, 9 | WatchError, 10 | ) 11 | 12 | from rediscluster.cluster_client import StrictRedisCluster 13 | 14 | __version__ = '0.5.3' 15 | VERSION = tuple(map(int, __version__.split('.'))) 16 | 17 | __all__ = [ 18 | 'StrictRedisCluster', 'RedisError', 'ConnectionError', 'ResponseError', 'AuthenticationError', 19 | 'InvalidResponse', 'DataError', 'PubSubError', 'WatchError' 20 | ] 21 | -------------------------------------------------------------------------------- /rediscluster/cluster_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import binascii 3 | 4 | import redis 5 | from redis._compat import ( 6 | b, iteritems, iterkeys, itervalues, basestring, bytes) 7 | from redis.client import list_or_args 8 | 9 | 10 | class StrictRedisCluster: 11 | """ 12 | Implementation of the Redis Cluster Client using redis.StrictRedis 13 | 14 | This abstract class provides a Python interface to all Redis commands on the cluster of redis servers. 15 | and implementing how the commands are sent to and received from the cluster. 16 | 17 | """ 18 | 19 | _read_keys = { 20 | 'debug': 'debug', 'getbit': 'getbit', 21 | 'get': 'get', 'getrange': 'getrange', 'hget': 'hget', 22 | 'hgetall': 'hgetall', 'hkeys': 'hkeys', 'hlen': 'hlen', 'hmget': 'hmget', 23 | 'hvals': 'hvals', 'lindex': 'lindex', 'llen': 'llen', 24 | 'lrange': 'lrange', 'object': 'object', 25 | 'scard': 'scard', 'sismember': 'sismember', 'smembers': 'smembers', 26 | 'srandmember': 'srandmember', 'strlen': 'strlen', 'type': 'type', 27 | 'zcard': 'zcard', 'zcount': 'zcount', 'zrange': 'zrange', 'zrangebyscore': 'zrangebyscore', 28 | 'zrank': 'zrank', 'zrevrange': 'zrevrange', 'zrevrangebyscore': 'zrevrangebyscore', 29 | 'zrevrank': 'zrevrank', 'zscore': 'zscore', 30 | 'mget': 'mget', 'bitcount': 'bitcount', 'echo': 'echo', 'debug_object': 'debug_object', 31 | 'substr': 'substr', 'keys': 'keys', 'randomkey': 'randomkey', 32 | } 33 | 34 | _write_keys = { 35 | 'append': 'append', 'blpop': 'blpop', 'brpop': 'brpop', 'brpoplpush': 'brpoplpush', 36 | 'decr': 'decr', 'decrby': 'decrby', 'del': 'del', 'exists': 'exists', 'hexists': 'hexists', 37 | 'expire': 'expire', 'expireat': 'expireat', 'pexpire': 'pexpire', 'pexpireat': 'pexpireat', 'getset': 'getset', 'hdel': 'hdel', 38 | 'hincrby': 'hincrby', 'hincrbyfloat': 'hincrbyfloat', 'hset': 'hset', 'hsetnx': 'hsetnx', 'hmset': 'hmset', 39 | 'incr': 'incr', 'incrby': 'incrby', 'incrbyfloat': 'incrbyfloat', 'linsert': 'linsert', 'lpop': 'lpop', 40 | 'lpush': 'lpush', 'lpushx': 'lpushx', 'lrem': 'lrem', 'lset': 'lset', 41 | 'ltrim': 'ltrim', 'move': 'move', 'bitop': 'bitop', 42 | 'persist': 'persist', 'publish': 'publish', 'psubscribe': 'psubscribe', 'punsubscribe': 'punsubscribe', 43 | 'rpop': 'rpop', 'rpoplpush': 'rpoplpush', 'rpush': 'rpush', 44 | 'rpushx': 'rpushx', 'sadd': 'sadd', 'sdiff': 'sdiff', 'sdiffstore': 'sdiffstore', 45 | 'set': 'set', 'setbit': 'setbit', 'setex': 'setex', 'setnx': 'setnx', 46 | 'setrange': 'setrange', 'sinter': 'sinter', 'sinterstore': 'sinterstore', 'smove': 'smove', 47 | 'sort': 'sort', 'spop': 'spop', 'srem': 'srem', 'subscribe': 'subscribe', 48 | 'sunion': 'sunion', 'sunionstore': 'sunionstore', 'unsubscribe': 'unsubscribe', 'unwatch': 'unwatch', 49 | 'watch': 'watch', 'zadd': 'zadd', 'zincrby': 'zincrby', 'zinterstore': 'zinterstore', 50 | 'zrem': 'zrem', 'zremrangebyrank': 'zremrangebyrank', 'zremrangebyscore': 'zremrangebyscore', 'zunionstore': 'zunionstore', 51 | 'mset': 'mset', 'msetnx': 'msetnx', 'rename': 'rename', 'renamenx': 'renamenx', 52 | 'del': 'del', 'delete': 'delete', 'ttl': 'ttl', 'pttl': 'pttl', 'flushall': 'flushall', 'flushdb': 'flushdb', 53 | } 54 | 55 | _dont_hash = { 56 | 'auth': 'auth', 'monitor': 'monitor', 'quit': 'quit', 57 | 'shutdown': 'shutdown', 'slaveof': 'slaveof', 'slowlog': 'slowlog', 'sync': 'sync', 58 | 'discard': 'discard', 'exec': 'exec', 'multi': 'multi', 59 | } 60 | 61 | _tag_keys = { 62 | 'mget': 'mget', 'rename': 'rename', 'renamenx': 'renamenx', 63 | 'mset': 'mset', 'msetnx': 'msetnx', 64 | 'brpoplpush': 'brpoplpush', 'rpoplpush': 'rpoplpush', 65 | 'sdiff': 'sdiff', 'sdiffstore': 'sdiffstore', 66 | 'sinter': 'sinter', 'sinterstore': 'sinterstore', 67 | 'sunion': 'sunion', 'sunionstore': 'sunionstore', 68 | 'smove': 'smove', 'zinterstore': 'zinterstore', 69 | 'zunionstore': 'zunionstore', 'sort': 'sort' 70 | } 71 | 72 | _loop_keys = { 73 | 'keys': 'keys', 'dbsize': 'dbsize', 74 | 75 | 'save': 'save', 'bgsave': 'bgsave', 76 | 'bgrewriteaof': 'bgrewriteaof', 77 | 'dbsize': 'dbsize', 'info': 'info', 78 | 'lastsave': 'lastsave', 'ping': 'ping', 79 | 'flushall': 'flushall', 'flushdb': 'flushdb', 80 | 'sync': 'sync', 81 | 'config_set': 'config_set', 'config_get': 'config_get', 82 | 'time': 'time', 'client_list': 'client_list' 83 | } 84 | 85 | _loop_keys_admin = { 86 | 'save': 'save', 'bgsave': 'bgsave', 87 | 'bgrewriteaof': 'bgrewriteaof', 88 | 'info': 'info', 89 | 'lastsave': 'lastsave', 'ping': 'ping', 90 | 'flushall': 'flushall', 'flushdb': 'flushdb', 91 | 'sync': 'sync', 92 | 'config_set': 'config_set', 'config_get': 'config_get', 93 | 'time': 'time', 'client_list': 'client_list' 94 | } 95 | 96 | def __init__(self, cluster={}, db=0, mastersonly=False): 97 | # raise exception when wrong server hash 98 | if 'nodes' not in cluster: 99 | raise Exception( 100 | "rediscluster: Please set a correct array of redis cluster.") 101 | 102 | self.cluster = cluster 103 | have_master_of = 'master_of' in self.cluster 104 | self.no_servers = len(self.cluster['master_of']) if have_master_of else len(self.cluster['nodes']) 105 | 106 | self.redises = {} 107 | redises_cons = {} 108 | self.cluster['slaves'] = {} 109 | 110 | # connect to all servers 111 | for alias, server in iteritems(self.cluster['nodes']): 112 | 113 | if have_master_of and alias not in self.cluster['master_of']: 114 | continue 115 | 116 | server_str = str(server) 117 | if server_str in redises_cons: 118 | self.redises[alias] = redises_cons[server_str]['master'] 119 | self.redises[alias + 120 | '_slave'] = redises_cons[server_str]['slave'] 121 | self.cluster['slaves'][alias + 122 | '_slave'] = redises_cons[server_str]['slave_node'] 123 | else: 124 | try: 125 | # connect to master 126 | self.__redis = redis.StrictRedis(db=db, **server) 127 | if not mastersonly and not have_master_of: 128 | info = self.__redis.info() 129 | if info['role'] != 'master': 130 | raise redis.DataError( 131 | "rediscluster: server %s is not a master." % (server,)) 132 | 133 | self.redises[alias] = self.__redis 134 | redises_cons[server_str] = {} 135 | redises_cons[server_str]['master'] = self.redises[alias] 136 | 137 | # connect to slave 138 | slave_connected = False 139 | slave = {} 140 | if not mastersonly: 141 | if have_master_of: 142 | slave = self.cluster[ 143 | 'nodes'][self.cluster['master_of'][alias]] 144 | elif 'connected_slaves' in info and info['connected_slaves'] > 0: 145 | slave_host, slave_port, slave_online = info[ 146 | 'slave0'].split(',') 147 | if slave_online == 'online': 148 | slave = {'host': slave_host, 'port': slave_port} 149 | 150 | if slave : 151 | try: 152 | redis_slave = redis.StrictRedis(host=slave['host'], port=int(slave['port']), db=db) 153 | self.redises[alias + '_slave'] = redis_slave 154 | self.cluster['slaves'][alias + '_slave'] = { 155 | 'host': slave['host'], 'port': slave['port']} 156 | redises_cons[server_str][ 157 | 'slave'] = self.redises[alias + '_slave'] 158 | redises_cons[server_str]['slave_node'] = self.cluster['slaves'][alias + '_slave'] 159 | slave_connected = True 160 | except redis.RedisError as e: 161 | pass 162 | # "RedisCluster cannot connect to: " + slave_host +':'+ slave_port 163 | 164 | if not slave_connected: 165 | self.redises[alias + '_slave'] = self.redises[alias] 166 | self.cluster['slaves'][alias + '_slave'] = server 167 | redises_cons[server_str][ 168 | 'slave'] = self.redises[alias + '_slave'] 169 | redises_cons[server_str]['slave_node'] = self.cluster[ 170 | 'slaves'][alias + '_slave'] 171 | 172 | except redis.RedisError as e: 173 | raise redis.ConnectionError( 174 | "rediscluster cannot connect to: %s %s" % (server, e)) 175 | 176 | def __getattr__(self, name, *args, **kwargs): 177 | """ 178 | Magic method to handle all redis commands 179 | - string name The name of the command called. 180 | - tuple args of supplied arguments to the command. 181 | """ 182 | def function(*args, **kwargs): 183 | if name not in StrictRedisCluster._loop_keys: 184 | # take care of hash tags 185 | tag_start = None 186 | key_type = hash_tag = '' 187 | # since we don't have "first item" in dict, 188 | # this list is needed in order to check hash_tag in mset({"a{a}": "a", "b":"b"}) 189 | list_ht = [] 190 | if isinstance(args[0], (basestring, bytes)): 191 | key_type = 'string' 192 | list_ht.append(args[0]) 193 | else: 194 | if isinstance(args[0], list): 195 | key_type = 'list' 196 | list_ht.append(args[0][0]) 197 | else: 198 | key_type = 'dict' 199 | list_ht = iterkeys(args[0]) 200 | 201 | # check for hash tags 202 | for k in list_ht: 203 | try: 204 | tag_start = k.index('{') 205 | hash_tag = k 206 | break 207 | except Exception as e: 208 | tag_start = None 209 | 210 | # trigger error msg on tag keys unless we have hash tags e.g. "bar{zap}" 211 | if name in StrictRedisCluster._tag_keys and not tag_start: 212 | try: 213 | return getattr(self, '_rc_' + name)(*args, **kwargs) 214 | except AttributeError: 215 | raise redis.DataError("rediscluster: Command %s Not Supported (each key name has its own node)" % name) 216 | 217 | # get the hash key 218 | hkey = args[0] 219 | # take care of hash tags names for forcing multiple keys on the same node, 220 | # e.g. r.set("bar{zap}", "bar"), r.mget(["foo{foo}","bar"]) 221 | if tag_start is not None: 222 | L = list(args) 223 | if key_type != 'string': 224 | if key_type == 'list': 225 | hkey = L[0][0][tag_start + 1:-1] 226 | L[0][0] = L[0][0][0:tag_start] 227 | else: 228 | hkey = hash_tag[tag_start + 1:-1] 229 | L[0][hash_tag[0:tag_start]] = L[0][hash_tag] 230 | del L[0][hash_tag] 231 | else: 232 | hkey = L[0][tag_start + 1:-1] 233 | L[0] = L[0][0:tag_start] 234 | 235 | args = tuple(L) 236 | 237 | # get the node number 238 | node = self._getnodenamefor(hkey) 239 | if name in StrictRedisCluster._write_keys: 240 | redisent = self.redises[node] 241 | elif name in StrictRedisCluster._read_keys: 242 | redisent = self.redises[node + '_slave'] 243 | else: 244 | raise redis.DataError("rediscluster: Command %s Not Supported (each key name has its own node)" % name) 245 | 246 | # Execute the command on the server 247 | return getattr(redisent, name)(*args, **kwargs) 248 | 249 | else: 250 | 251 | # take care of keys that don't need to go through master and slaves redis servers 252 | if name not in self._loop_keys_admin: 253 | try: 254 | return getattr(self, '_rc_' + name)(*args, **kwargs) 255 | except AttributeError: 256 | raise redis.DataError("rediscluster: Command %s Not Supported (each key name has its own node)" % name) 257 | 258 | result = {} 259 | for alias, redisent in iteritems(self.redises): 260 | if (name in StrictRedisCluster._write_keys and alias.find('_slave') >= 0) or (name in StrictRedisCluster._read_keys and alias.find('_slave') == -1): 261 | res = None 262 | else: 263 | res = getattr(redisent, name)(*args, **kwargs) 264 | 265 | result[alias] = res 266 | 267 | return result 268 | 269 | return function 270 | 271 | def _getnodenamefor(self, name): 272 | "Return the node name where the ``name`` would land to" 273 | return 'node_' + str( 274 | (abs(binascii.crc32(b(name)) & 0xffffffff) % self.no_servers) + 1) 275 | 276 | def getnodefor(self, name): 277 | "Return the node where the ``name`` would land to" 278 | node = self._getnodenamefor(name) 279 | return {node: self.cluster['nodes'][node]} 280 | 281 | def __setitem__(self, name, value): 282 | "Set the value at key ``name`` to ``value``" 283 | return self.set(name, value) 284 | 285 | def __getitem__(self, name): 286 | """ 287 | Return the value at key ``name``, raises a KeyError if the key 288 | doesn't exist. 289 | """ 290 | value = self.get(name) 291 | if value: 292 | return value 293 | raise KeyError(name) 294 | 295 | def __delitem__(self, *names): 296 | "Delete one or more keys specified by ``names``" 297 | return self.delete(*names) 298 | 299 | def object(self, infotype, key): 300 | "Return the encoding, idletime, or refcount about the key" 301 | redisent = self.redises[self._getnodenamefor(key) + '_slave'] 302 | return getattr(redisent, 'object')(infotype, key) 303 | 304 | def _rc_brpoplpush(self, src, dst, timeout=0): 305 | """ 306 | Pop a value off the tail of ``src``, push it on the head of ``dst`` 307 | and then return it. 308 | 309 | This command blocks until a value is in ``src`` or until ``timeout`` 310 | seconds elapse, whichever is first. A ``timeout`` value of 0 blocks 311 | forever. 312 | Not atomic 313 | """ 314 | rpop = self.brpop(src, timeout) 315 | if rpop is not None: 316 | self.lpush(dst, rpop[1]) 317 | return rpop[1] 318 | return None 319 | 320 | def _rc_rpoplpush(self, src, dst): 321 | """ 322 | RPOP a value off of the ``src`` list and LPUSH it 323 | on to the ``dst`` list. Returns the value. 324 | """ 325 | rpop = self.rpop(src) 326 | if rpop is not None: 327 | self.lpush(dst, rpop) 328 | return rpop 329 | return None 330 | 331 | def _rc_sdiff(self, src, *args): 332 | """ 333 | Returns the members of the set resulting from the difference between 334 | the first set and all the successive sets. 335 | """ 336 | args = list_or_args(src, args) 337 | src_set = self.smembers(args.pop(0)) 338 | if src_set is not set([]): 339 | for key in args: 340 | src_set.difference_update(self.smembers(key)) 341 | return src_set 342 | 343 | def _rc_sdiffstore(self, dst, src, *args): 344 | """ 345 | Store the difference of sets ``src``, ``args`` into a new 346 | set named ``dest``. Returns the number of keys in the new set. 347 | """ 348 | args = list_or_args(src, args) 349 | result = self.sdiff(*args) 350 | if result is not set([]): 351 | return self.sadd(dst, *list(result)) 352 | return 0 353 | 354 | def _rc_sinter(self, src, *args): 355 | """ 356 | Returns the members of the set resulting from the difference between 357 | the first set and all the successive sets. 358 | """ 359 | args = list_or_args(src, args) 360 | src_set = self.smembers(args.pop(0)) 361 | if src_set is not set([]): 362 | for key in args: 363 | src_set.intersection_update(self.smembers(key)) 364 | return src_set 365 | 366 | def _rc_sinterstore(self, dst, src, *args): 367 | """ 368 | Store the difference of sets ``src``, ``args`` into a new 369 | set named ``dest``. Returns the number of keys in the new set. 370 | """ 371 | args = list_or_args(src, args) 372 | result = self.sinter(*args) 373 | if result is not set([]): 374 | return self.sadd(dst, *list(result)) 375 | return 0 376 | 377 | def _rc_smove(self, src, dst, value): 378 | """ 379 | Move ``value`` from set ``src`` to set ``dst`` 380 | not atomic 381 | """ 382 | if self.type(src) != b("set"): 383 | return self.smove(src + "{" + src + "}", dst, value) 384 | if self.type(dst) != b("set"): 385 | return self.smove(dst + "{" + dst + "}", src, value) 386 | if self.srem(src, value): 387 | return 1 if self.sadd(dst, value) else 0 388 | return 0 389 | 390 | def _rc_sunion(self, src, *args): 391 | """ 392 | Returns the members of the set resulting from the union between 393 | the first set and all the successive sets. 394 | """ 395 | args = list_or_args(src, args) 396 | src_set = self.smembers(args.pop(0)) 397 | if src_set is not set([]): 398 | for key in args: 399 | src_set.update(self.smembers(key)) 400 | return src_set 401 | 402 | def _rc_sunionstore(self, dst, src, *args): 403 | """ 404 | Store the union of sets ``src``, ``args`` into a new 405 | set named ``dest``. Returns the number of keys in the new set. 406 | """ 407 | args = list_or_args(src, args) 408 | result = self.sunion(*args) 409 | if result is not set([]): 410 | return self.sadd(dst, *list(result)) 411 | return 0 412 | 413 | def _rc_mset(self, mapping): 414 | "Sets each key in the ``mapping`` dict to its corresponding value" 415 | result = True 416 | for k, v in iteritems(mapping): 417 | result = result and self.set(k, v) 418 | return result 419 | 420 | def _rc_msetnx(self, mapping): 421 | """ 422 | Sets each key in the ``mapping`` dict to its corresponding value if 423 | none of the keys are already set 424 | """ 425 | for k in iterkeys(mapping): 426 | if self.exists(k): 427 | return False 428 | 429 | return self._rc_mset(mapping) 430 | 431 | def _rc_mget(self, keys, *args): 432 | """ 433 | Returns a list of values ordered identically to ``*args`` 434 | """ 435 | args = list_or_args(keys, args) 436 | result = [] 437 | for key in args: 438 | result.append(self.get(key)) 439 | return result 440 | 441 | def _rc_rename(self, src, dst): 442 | """ 443 | Rename key ``src`` to ``dst`` 444 | """ 445 | if src == dst: 446 | return self.rename(src + "{" + src + "}", src) 447 | if not self.exists(src): 448 | return self.rename(src + "{" + src + "}", src) 449 | 450 | self.delete(dst) 451 | ktype = self.type(src) 452 | kttl = self.ttl(src) 453 | 454 | if ktype == b('none'): 455 | return False 456 | 457 | if ktype == b('string'): 458 | self.set(dst, self.get(src)) 459 | elif ktype == b('hash'): 460 | self.hmset(dst, self.hgetall(src)) 461 | elif ktype == b('list'): 462 | for k in self.lrange(src, 0, -1): 463 | self.rpush(dst, k) 464 | elif ktype == b('set'): 465 | for k in self.smembers(src): 466 | self.sadd(dst, k) 467 | elif ktype == b('zset'): 468 | for k, v in self.zrange(src, 0, -1, withscores=True): 469 | self.zadd(dst, v, k) 470 | 471 | # Handle keys with an expire time set 472 | kttl = -1 if kttl is None or kttl < 0 else int(kttl) 473 | if kttl != -1: 474 | self.expire(dst, kttl) 475 | 476 | return self.delete(src) 477 | 478 | def _rc_renamenx(self, src, dst): 479 | "Rename key ``src`` to ``dst`` if ``dst`` doesn't already exist" 480 | if self.exists(dst): 481 | return False 482 | 483 | return self._rc_rename(src, dst) 484 | 485 | def _rc_keys(self, pattern='*'): 486 | "Returns a list of keys matching ``pattern``" 487 | 488 | result = [] 489 | for alias, redisent in iteritems(self.redises): 490 | if alias.find('_slave') == -1: 491 | continue 492 | 493 | result.extend(redisent.keys(pattern)) 494 | 495 | return result 496 | 497 | def _rc_dbsize(self): 498 | "Returns the number of keys in the current database" 499 | 500 | result = 0 501 | for alias, redisent in iteritems(self.redises): 502 | if alias.find('_slave') == -1: 503 | continue 504 | 505 | result += redisent.dbsize() 506 | 507 | return result 508 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis>=2.4.0 2 | hiredis -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | 5 | from tests import all_tests 6 | 7 | if __name__ == "__main__": 8 | tests = all_tests() 9 | results = unittest.TextTestRunner().run(tests) 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from rediscluster import __version__ 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | # Work around mbcs bug in distutils for py3k. 10 | # http://bugs.python.org/issue10945 11 | import codecs 12 | try: 13 | codecs.lookup('mbcs') 14 | except LookupError: 15 | ascii = codecs.lookup('ascii') 16 | func = lambda name, enc = ascii: {True: enc}.get(name == 'mbcs') 17 | codecs.register(func) 18 | 19 | with open('README.rst') as f: 20 | long_description = f.read() 21 | 22 | with open('LICENSE') as f: 23 | license = f.read() 24 | 25 | setup( 26 | name='rediscluster', 27 | version=__version__, 28 | description='a Python interface to a Cluster of Redis key-value store', 29 | long_description=long_description, 30 | url='http://github.com/salimane/rediscluster-py', 31 | download_url=('http://pypi.python.org/packages/source/r/rediscluster/rediscluster-%s.tar.gz' % __version__), 32 | install_requires=[ 33 | 'redis>=2.4.0', 34 | 'hiredis', 35 | ], 36 | author='Salimane Adjao Moustapha', 37 | author_email='me@salimane.com', 38 | maintainer='Salimane Adjao Moustapha', 39 | maintainer_email='me@salimane.com', 40 | keywords=['rediscluster', 'redis', 'nosql', 'cluster', 'key value', 41 | 'data store', 'sharding'], 42 | license=license, 43 | packages=['rediscluster'], 44 | test_suite='tests.all_tests', 45 | classifiers=[ 46 | 'Development Status :: 4 - Beta', 47 | 'Environment :: Console', 48 | 'Intended Audience :: Developers', 49 | 'License :: OSI Approved :: MIT License', 50 | 'Operating System :: OS Independent', 51 | 'Programming Language :: Python', 52 | 'Programming Language :: Python :: 2.6', 53 | 'Programming Language :: Python :: 2.7', 54 | 'Programming Language :: Python :: 3', 55 | 'Programming Language :: Python :: 3.2', 56 | 'Programming Language :: Python :: 3.3', 57 | ] 58 | ) 59 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from tests.cluster_commands import ClusterCommandsTestCase 4 | 5 | 6 | def all_tests(): 7 | suite = unittest.TestSuite() 8 | suite.addTest(unittest.makeSuite(ClusterCommandsTestCase)) 9 | return suite 10 | -------------------------------------------------------------------------------- /tests/cluster_commands.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import unittest 3 | import datetime 4 | import time 5 | import binascii 6 | 7 | from redis._compat import (unichr, u, b, ascii_letters, iteritems, iterkeys, 8 | itervalues) 9 | from redis.client import parse_info 10 | import rediscluster 11 | from tests import config 12 | 13 | 14 | class ClusterCommandsTestCase(unittest.TestCase): 15 | def get_client(self, cls=rediscluster.StrictRedisCluster, mastersonly=False): 16 | return cls(cluster=config.cluster, db=4, mastersonly=mastersonly) 17 | 18 | def setUp(self): 19 | self.client = self.get_client() 20 | self.client.flushdb() 21 | 22 | def tearDown(self): 23 | self.client.flushdb() 24 | # self.client.connection_pool.disconnect() 25 | 26 | def test_response_callbacks(self): 27 | try: 28 | raise unittest.SkipTest() 29 | except AttributeError: 30 | return 31 | self.assertEquals( 32 | self.client.response_callbacks, 33 | redis.Redis.RESPONSE_CALLBACKS) 34 | self.assertNotEquals( 35 | id(self.client.response_callbacks), 36 | id(redis.Redis.RESPONSE_CALLBACKS)) 37 | self.client.set_response_callback('GET', lambda x: 'static') 38 | self.client.set('a', 'foo') 39 | self.assertEquals(self.client.get('a'), 'static') 40 | 41 | # GENERAL SERVER COMMANDS 42 | def test_dbsize(self): 43 | self.client['a'] = 'foo' 44 | self.client['b'] = 'bar' 45 | self.assertEquals(self.client.dbsize(), 2) 46 | 47 | def test_masters_only(self): 48 | client = self.get_client(mastersonly=True) 49 | for alias, server in iteritems(client.cluster['nodes']): 50 | if 'master_of' in client.cluster and alias not in client.cluster['master_of']: 51 | continue 52 | self.assertEquals(client.cluster['nodes'][alias], client.cluster['slaves'][alias + '_slave']) 53 | 54 | def test_getnodefor(self): 55 | self.client['bar'] = 'foo' 56 | node = self.client.getnodefor('bar') 57 | from redis import StrictRedis 58 | rd = StrictRedis(db=4, **config.cluster['nodes'][iterkeys(node)[0]]) 59 | self.assertEquals(self.client['bar'], rd['bar']) 60 | 61 | def test_get_and_set(self): 62 | # get and set can't be tested independently of each other 63 | client = self.get_client() 64 | self.assertEquals(client.get('a'), None) 65 | byte_string = b('value') 66 | integer = 5 67 | unicode_string = unichr(3456) + u('abcd') + unichr(3421) 68 | self.assert_(client.set('byte_string', byte_string)) 69 | self.assert_(client.set('integer', 5)) 70 | self.assert_(client.set('unicode_string', unicode_string)) 71 | self.assertEquals(client.get('byte_string'), byte_string) 72 | self.assertEquals(client.get('integer'), b(str(integer))) 73 | self.assertEquals( 74 | client.get('unicode_string').decode('utf-8'), 75 | unicode_string) 76 | 77 | def test_hash_tag(self): 78 | self.client['bar{foo}'] = 'bar' 79 | if itervalues(self.client.getnodefor('foo')) != itervalues(self.client.getnodefor('bar')): 80 | self.assertEquals(self.client.get('bar'), None) 81 | self.assertEquals(self.client['bar{foo}'], b('bar')) 82 | # checking bar on the right node 83 | from redis import StrictRedis 84 | rd = StrictRedis(db=4, **itervalues(self.client.getnodefor('foo'))[0]) 85 | self.assertEquals(rd['bar'], self.client['bar{foo}']) 86 | 87 | def test_getitem_and_setitem(self): 88 | self.client['a'] = 'bar' 89 | self.assertEquals(self.client['a'], b('bar')) 90 | self.assertRaises(KeyError, self.client.__getitem__, 'b') 91 | 92 | def test_delete(self): 93 | self.assertEquals(self.client.delete('a'), False) 94 | self.client['a'] = 'foo' 95 | self.assertEquals(self.client.delete('a'), True) 96 | 97 | def test_delete_multiple_keys(self): 98 | try: 99 | raise unittest.SkipTest() 100 | except AttributeError: 101 | return 102 | self.client['a'] = 'foo' 103 | self.client['b'] = 'bar' 104 | self.assertEquals(self.client.delete('a', 'b'), 2) 105 | self.assertEquals(self.client.get('a'), None) 106 | self.assertEquals(self.client.get('b'), None) 107 | 108 | def test_delitem(self): 109 | self.client['a'] = 'foo' 110 | del self.client['a'] 111 | self.assertEquals(self.client.get('a'), None) 112 | 113 | def test_client_list(self): 114 | for clients in itervalues(self.client.client_list()): 115 | self.assert_(isinstance(clients[0], dict)) 116 | self.assert_('addr' in clients[0]) 117 | 118 | def test_client_getname(self): 119 | try: 120 | raise unittest.SkipTest() 121 | except AttributeError: 122 | return 123 | version = self.client.info()['redis_version'] 124 | if StrictVersion(version) < StrictVersion('2.6.9'): 125 | try: 126 | raise unittest.SkipTest() 127 | except AttributeError: 128 | return 129 | 130 | name = self.client.client_getname() 131 | self.assertEquals(name, None) 132 | 133 | def test_client_setname(self): 134 | try: 135 | raise unittest.SkipTest() 136 | except AttributeError: 137 | return 138 | version = self.client.info()['redis_version'] 139 | if StrictVersion(version) < StrictVersion('2.6.9'): 140 | try: 141 | raise unittest.SkipTest() 142 | except AttributeError: 143 | return 144 | 145 | self.assert_(self.client.client_setname('redis_py_test')) 146 | self.assertEquals( 147 | self.client.client_getname(), 148 | 'redis_py_test' 149 | ) 150 | 151 | def test_config_get(self): 152 | for data in itervalues(self.client.config_get()): 153 | self.assert_('maxmemory' in data) 154 | self.assert_(data['maxmemory'].isdigit()) 155 | 156 | def test_config_set(self): 157 | self.assert_(self.client.config_set('timeout', '868')) 158 | for data in itervalues(self.client.config_get()): 159 | self.assertEquals( 160 | data['timeout'], 161 | '868' 162 | ) 163 | self.assert_(self.client.config_set('timeout', '300')) 164 | for data in itervalues(self.client.config_get()): 165 | self.assertEquals( 166 | data['timeout'], 167 | '300' 168 | ) 169 | 170 | def test_debug_object(self): 171 | self.client['a'] = 'foo' 172 | debug_info = self.client.debug_object('a') 173 | self.assert_(len(debug_info) > 0) 174 | self.assertEquals(debug_info['refcount'], 1) 175 | self.assert_(debug_info['serializedlength'] > 0) 176 | self.client.rpush('b', 'a1') 177 | debug_info = self.client.debug_object('a') 178 | 179 | def test_echo(self): 180 | self.assertEquals(self.client.echo('foo bar'), b('foo bar')) 181 | 182 | def test_info(self): 183 | self.client['a'] = 'foo' 184 | self.client['b'] = 'bar' 185 | kno = 0 186 | knohash = {} 187 | for node, info in iteritems(self.client.info()): 188 | self.assert_(isinstance(info, dict)) 189 | try: 190 | k = info['db4']['keys'] 191 | if k and node in self.client.cluster['nodes'] and str(self.client.cluster['nodes'][node]) not in knohash: 192 | kno += k 193 | knohash[str(self.client.cluster['nodes'][node])] = k 194 | except KeyError: 195 | pass 196 | 197 | self.assertEquals(kno, 2) 198 | 199 | def test_lastsave(self): 200 | for data in itervalues(self.client.lastsave()): 201 | self.assert_(isinstance(data, datetime.datetime)) 202 | 203 | def test_object(self): 204 | self.client['a'] = 'foo' 205 | self.assert_(isinstance(self.client.object('refcount', 'a'), int)) 206 | self.assert_(isinstance(self.client.object('idletime', 'a'), int)) 207 | self.assertEquals(self.client.object('encoding', 'a'), b('raw')) 208 | 209 | def test_ping(self): 210 | for data in itervalues(self.client.ping()): 211 | self.assertEquals(data, True) 212 | 213 | def test_time(self): 214 | for info in itervalues(self.client.info()): 215 | version = info['redis_version'] 216 | if StrictVersion(version) < StrictVersion('2.5.0'): 217 | try: 218 | raise unittest.SkipTest() 219 | except AttributeError: 220 | return 221 | for t in itervalues(self.client.time()): 222 | self.assertEquals(len(t), 2) 223 | self.assert_(isinstance(t[0], int)) 224 | self.assert_(isinstance(t[1], int)) 225 | 226 | # KEYS 227 | def test_append(self): 228 | # invalid key type 229 | self.client.rpush('a', 'a1') 230 | self.assertRaises( 231 | rediscluster.ResponseError, self.client.append, 'a', 'a1') 232 | del self.client['a'] 233 | # real logic 234 | self.assertEquals(self.client.append('a', 'a1'), 2) 235 | self.assertEquals(self.client['a'], b('a1')) 236 | self.assert_(self.client.append('a', 'a2'), 4) 237 | self.assertEquals(self.client['a'], b('a1a2')) 238 | 239 | def test_getrange(self): 240 | self.client['a'] = 'foo' 241 | self.assertEquals(self.client.getrange('a', 0, 0), b('f')) 242 | self.assertEquals(self.client.getrange('a', 0, 2), b('foo')) 243 | self.assertEquals(self.client.getrange('a', 3, 4), b('')) 244 | 245 | def test_decr(self): 246 | self.assertEquals(self.client.decr('a'), -1) 247 | self.assertEquals(self.client['a'], b('-1')) 248 | self.assertEquals(self.client.decr('a'), -2) 249 | self.assertEquals(self.client['a'], b('-2')) 250 | self.assertEquals(self.client.decr('a', amount=5), -7) 251 | self.assertEquals(self.client['a'], b('-7')) 252 | 253 | def test_exists(self): 254 | self.assertEquals(self.client.exists('a'), False) 255 | self.client['a'] = 'foo' 256 | self.assertEquals(self.client.exists('a'), True) 257 | 258 | def test_expire(self): 259 | self.assertEquals(self.client.expire('a', 10), False) 260 | self.client['a'] = 'foo' 261 | self.assertEquals(self.client.expire('a', 10), True) 262 | self.assertEquals(self.client.ttl('a'), 10) 263 | self.assertEquals(self.client.persist('a'), True) 264 | self.assertEquals(self.client.ttl('a'), -1) 265 | 266 | def test_expireat(self): 267 | expire_at = datetime.datetime.now() + datetime.timedelta(minutes=1) 268 | self.assertEquals(self.client.expireat('a', expire_at), False) 269 | self.client['a'] = 'foo' 270 | # expire at in unix time 271 | expire_at_seconds = int(time.mktime(expire_at.timetuple())) 272 | self.assertEquals(self.client.expireat('a', expire_at_seconds), True) 273 | self.assertAlmostEquals(self.client.ttl('a'), 60, delta=2) 274 | # expire at given a datetime object 275 | self.client['b'] = 'bar' 276 | self.assertEquals(self.client.expireat('b', expire_at), True) 277 | self.assertEquals(self.client.ttl('b'), 60) 278 | 279 | def test_pexpire(self): 280 | for info in itervalues(self.client.info()): 281 | version = info['redis_version'] 282 | if StrictVersion(version) < StrictVersion('2.5.0'): 283 | try: 284 | raise unittest.SkipTest() 285 | except AttributeError: 286 | return 287 | 288 | self.assertEquals(self.client.pexpire('a', 10000), False) 289 | self.client['a'] = 'foo' 290 | self.assertEquals(self.client.pexpire('a', 10000), True) 291 | self.assert_(self.client.pttl('a') <= 10000) 292 | self.assertEquals(self.client.persist('a'), True) 293 | self.assertEquals(self.client.pttl('a'), -1) 294 | 295 | def test_pexpireat(self): 296 | for info in itervalues(self.client.info()): 297 | version = info['redis_version'] 298 | if StrictVersion(version) < StrictVersion('2.5.0'): 299 | try: 300 | raise unittest.SkipTest() 301 | except AttributeError: 302 | return 303 | 304 | expire_at = datetime.datetime.now() + datetime.timedelta(minutes=1) 305 | self.assertEquals(self.client.pexpireat('a', expire_at), False) 306 | self.client['a'] = 'foo' 307 | # expire at in unix time (milliseconds) 308 | expire_at_seconds = int(time.mktime(expire_at.timetuple())) * 1000 309 | self.assertEquals(self.client.pexpireat('a', expire_at_seconds), True) 310 | self.assert_(self.client.ttl('a') <= 60) 311 | # expire at given a datetime object 312 | self.client['b'] = 'bar' 313 | self.assertEquals(self.client.pexpireat('b', expire_at), True) 314 | self.assert_(self.client.ttl('b') <= 60) 315 | 316 | def test_psetex(self): 317 | try: 318 | raise unittest.SkipTest() 319 | except AttributeError: 320 | return 321 | self.assertEquals(self.client.psetex('a', 1000, 'value'), True) 322 | self.assertEquals(self.client['a'], b('value')) 323 | self.assert_(0 < self.client.pttl('a') <= 1000) 324 | # expire given a timeelta 325 | expire_at = datetime.timedelta(milliseconds=1000) 326 | self.assertEquals(self.client.psetex('a', expire_at, 'value'), True) 327 | self.assertEquals(self.client['a'], b('value')) 328 | self.assert_(0 < self.client.pttl('a') <= 1000) 329 | 330 | def test_get_set_bit(self): 331 | self.assertEquals(self.client.getbit('a', 5), False) 332 | self.assertEquals(self.client.setbit('a', 5, True), False) 333 | self.assertEquals(self.client.getbit('a', 5), True) 334 | self.assertEquals(self.client.setbit('a', 4, False), False) 335 | self.assertEquals(self.client.getbit('a', 4), False) 336 | self.assertEquals(self.client.setbit('a', 4, True), False) 337 | self.assertEquals(self.client.setbit('a', 5, True), True) 338 | self.assertEquals(self.client.getbit('a', 4), True) 339 | self.assertEquals(self.client.getbit('a', 5), True) 340 | 341 | def test_bitcount(self): 342 | for info in itervalues(self.client.info()): 343 | version = info['redis_version'] 344 | if StrictVersion(version) < StrictVersion('2.5.0'): 345 | try: 346 | raise unittest.SkipTest() 347 | except AttributeError: 348 | return 349 | 350 | self.client.setbit('a', 5, True) 351 | self.assertEquals(self.client.bitcount('a'), 1) 352 | self.client.setbit('a', 6, True) 353 | self.assertEquals(self.client.bitcount('a'), 2) 354 | self.client.setbit('a', 5, False) 355 | self.assertEquals(self.client.bitcount('a'), 1) 356 | self.client.setbit('a', 9, True) 357 | self.client.setbit('a', 17, True) 358 | self.client.setbit('a', 25, True) 359 | self.client.setbit('a', 33, True) 360 | self.assertEquals(self.client.bitcount('a'), 5) 361 | self.assertEquals(self.client.bitcount('a', 2, 3), 2) 362 | self.assertEquals(self.client.bitcount('a', 2, -1), 3) 363 | self.assertEquals(self.client.bitcount('a', -2, -1), 2) 364 | self.assertEquals(self.client.bitcount('a', 1, 1), 1) 365 | 366 | def test_bitop_not_empty_string(self): 367 | for info in itervalues(self.client.info()): 368 | version = info['redis_version'] 369 | if StrictVersion(version) < StrictVersion('2.6.0'): 370 | try: 371 | raise unittest.SkipTest() 372 | except AttributeError: 373 | return 374 | 375 | self.client.set('a', '') 376 | self.client.bitop('not', 'r', 'a') 377 | self.assertEquals(self.client.get('r'), None) 378 | 379 | def test_bitop_not(self): 380 | try: 381 | raise unittest.SkipTest() 382 | except AttributeError: 383 | return 384 | for info in itervalues(self.client.info()): 385 | version = info['redis_version'] 386 | if StrictVersion(version) < StrictVersion('2.6.0'): 387 | try: 388 | raise unittest.SkipTest() 389 | except AttributeError: 390 | return 391 | 392 | test_str = b('\xAA\x00\xFF\x55') 393 | correct = ~0xAA00FF55 & 0xFFFFFFFF 394 | self.client.set('a', test_str) 395 | self.client.bitop('not', 'r', 'a') 396 | self.assertEquals( 397 | int(binascii.hexlify(self.client.get('r')), 16), 398 | correct) 399 | 400 | def test_bitop_not_in_place(self): 401 | try: 402 | raise unittest.SkipTest() 403 | except AttributeError: 404 | return 405 | for info in itervalues(self.client.info()): 406 | version = info['redis_version'] 407 | if StrictVersion(version) < StrictVersion('2.6.0'): 408 | try: 409 | raise unittest.SkipTest() 410 | except AttributeError: 411 | return 412 | 413 | test_str = b('\xAA\x00\xFF\x55') 414 | correct = ~0xAA00FF55 & 0xFFFFFFFF 415 | self.client.set('a', test_str) 416 | self.client.bitop('not', 'a', 'a') 417 | self.assertEquals( 418 | int(binascii.hexlify(self.client.get('a')), 16), 419 | correct) 420 | 421 | def test_bitop_single_string(self): 422 | try: 423 | raise unittest.SkipTest() 424 | except AttributeError: 425 | return 426 | for info in itervalues(self.client.info()): 427 | version = info['redis_version'] 428 | if StrictVersion(version) < StrictVersion('2.6.0'): 429 | try: 430 | raise unittest.SkipTest() 431 | except AttributeError: 432 | return 433 | 434 | test_str = b('\x01\x02\xFF') 435 | self.client.set('a', test_str) 436 | self.client.bitop('and', 'res1', 'a') 437 | self.client.bitop('or', 'res2', 'a') 438 | self.client.bitop('xor', 'res3', 'a') 439 | self.assertEquals(self.client.get('res1'), test_str) 440 | self.assertEquals(self.client.get('res2'), test_str) 441 | self.assertEquals(self.client.get('res3'), test_str) 442 | 443 | def test_bitop_string_operands(self): 444 | try: 445 | raise unittest.SkipTest() 446 | except AttributeError: 447 | return 448 | for info in itervalues(self.client.info()): 449 | version = info['redis_version'] 450 | if StrictVersion(version) < StrictVersion('2.6.0'): 451 | try: 452 | raise unittest.SkipTest() 453 | except AttributeError: 454 | return 455 | 456 | self.client.set('a', b('\x01\x02\xFF\xFF')) 457 | self.client.set('b', b('\x01\x02\xFF')) 458 | self.client.bitop('and', 'res1', 'a', 'b') 459 | self.client.bitop('or', 'res2', 'a', 'b') 460 | self.client.bitop('xor', 'res3', 'a', 'b') 461 | self.assertEquals( 462 | int(binascii.hexlify(self.client.get('res1')), 16), 463 | 0x0102FF00) 464 | self.assertEquals( 465 | int(binascii.hexlify(self.client.get('res2')), 16), 466 | 0x0102FFFF) 467 | self.assertEquals( 468 | int(binascii.hexlify(self.client.get('res3')), 16), 469 | 0x000000FF) 470 | 471 | def test_getset(self): 472 | self.assertEquals(self.client.getset('a', 'foo'), None) 473 | self.assertEquals(self.client.getset('a', 'bar'), b('foo')) 474 | 475 | def test_incr(self): 476 | self.assertEquals(self.client.incr('a'), 1) 477 | self.assertEquals(self.client['a'], b('1')) 478 | self.assertEquals(self.client.incr('a'), 2) 479 | self.assertEquals(self.client['a'], b('2')) 480 | self.assertEquals(self.client.incr('a', amount=5), 7) 481 | self.assertEquals(self.client['a'], b('7')) 482 | 483 | def test_incrbyfloat(self): 484 | for info in itervalues(self.client.info()): 485 | version = info['redis_version'] 486 | if StrictVersion(version) < StrictVersion('2.5.0'): 487 | try: 488 | raise unittest.SkipTest() 489 | except AttributeError: 490 | return 491 | 492 | self.assertEquals(self.client.incrbyfloat('a'), 1.0) 493 | self.assertEquals(self.client['a'], b('1')) 494 | self.assertEquals(self.client.incrbyfloat('a', 1.1), 2.1) 495 | self.assertEquals(float(self.client['a']), float(2.1)) 496 | 497 | def test_keys(self): 498 | self.assertEquals(self.client.keys(), []) 499 | keys = set([b('test_a'), b('test_b'), b('testc')]) 500 | for key in keys: 501 | self.client[key] = 1 502 | self.assertEquals( 503 | set(self.client.keys(pattern='test_*')), 504 | keys - set([b('testc')])) 505 | self.assertEquals(set(self.client.keys(pattern='test*')), keys) 506 | 507 | def test_mget(self): 508 | self.assertEquals(self.client.mget(['a', 'b']), [None, None]) 509 | self.client['a'] = '1' 510 | self.client['b'] = '2' 511 | self.client['c'] = '3' 512 | self.assertEquals( 513 | self.client.mget(['a', 'other', 'b', 'c']), 514 | [b('1'), None, b('2'), b('3')]) 515 | 516 | def test_mget_hash_tag(self): 517 | self.assertEquals(self.client.mget(['foo{foo}', 'bar']), [None, None]) 518 | self.client['foo'] = '1' 519 | self.client['bar{foo}'] = '2' 520 | self.client['other{foo}'] = '3' 521 | self.assertEquals( 522 | self.client.mget(['foo{foo}', 'c', 'bar', 'other']), 523 | [b('1'), None, b('2'), b('3')]) 524 | 525 | def test_mset(self): 526 | d = {'a': '1', 'b': '2', 'c': '3'} 527 | self.assert_(self.client.mset(d)) 528 | for k, v in iteritems(d): 529 | self.assertEquals(self.client[k], b(v)) 530 | 531 | def test_mset_mget_hash_tag(self): 532 | self.assert_( 533 | self.client.mset({'foo{foo}': '1', 'bar': '2', 'other': '3'})) 534 | self.assertEquals(self.client.mget( 535 | ['foo{foo}', 'bar', 'other']), [b('1'), b('2'), b('3')]) 536 | self.assertEquals(self.client['foo'], b('1')) 537 | self.assertEquals(self.client['bar{foo}'], b('2')) 538 | self.assertEquals(self.client['other{foo}'], b('3')) 539 | 540 | def test_msetnx(self): 541 | d = {'a': '1', 'b': '2', 'c': '3'} 542 | self.assert_(self.client.msetnx(d)) 543 | d2 = {'a': 'x', 'd': '4'} 544 | self.assert_(not self.client.msetnx(d2)) 545 | for k, v in iteritems(d): 546 | self.assertEquals(self.client[k], b(v)) 547 | self.assertEquals(self.client.get('d'), None) 548 | 549 | def test_randomkey(self): 550 | # CLUSTER 551 | try: 552 | raise unittest.SkipTest() 553 | except AttributeError: 554 | return 555 | self.assertEquals(self.client.randomkey(), None) 556 | self.client['a'] = '1' 557 | self.client['b'] = '2' 558 | self.client['c'] = '3' 559 | self.assert_(self.client.randomkey() in (b('a'), b('b'), b('c'))) 560 | 561 | def test_rename(self): 562 | self.client['a'] = '1' 563 | self.assert_(self.client.rename('a', 'b')) 564 | self.assertEquals(self.client.get('a'), None) 565 | self.assertEquals(self.client['b'], b('1')) 566 | 567 | def test_renamenx(self): 568 | self.client['a'] = '1' 569 | self.client['b'] = '2' 570 | self.assert_(not self.client.renamenx('a', 'b')) 571 | self.assertEquals(self.client['a'], b('1')) 572 | self.assertEquals(self.client['b'], b('2')) 573 | 574 | def test_set_nx(self): 575 | for info in itervalues(self.client.info()): 576 | version = info['redis_version'] 577 | if StrictVersion(version) < StrictVersion('2.6.12'): 578 | try: 579 | raise unittest.SkipTest() 580 | except AttributeError: 581 | return 582 | 583 | self.assertEquals(self.client.set('foo', '1', nx=True), True) 584 | self.assertEquals(self.client.set('foo', '2', nx=True), None) 585 | self.assertEquals(self.client.get('foo'), b('1')) 586 | 587 | def test_set_xx(self): 588 | for info in itervalues(self.client.info()): 589 | version = info['redis_version'] 590 | if StrictVersion(version) < StrictVersion('2.6.12'): 591 | try: 592 | raise unittest.SkipTest() 593 | except AttributeError: 594 | return 595 | 596 | self.assertEquals(self.client.set('foo', '1', xx=True), None) 597 | self.assertEquals(self.client.get('foo'), None) 598 | self.client.set('foo', 'bar') 599 | self.assertEquals(self.client.set('foo', '2', xx=True), True) 600 | self.assertEquals(self.client.get('foo'), b('2')) 601 | 602 | def test_set_px(self): 603 | for info in itervalues(self.client.info()): 604 | version = info['redis_version'] 605 | if StrictVersion(version) < StrictVersion('2.6.12'): 606 | try: 607 | raise unittest.SkipTest() 608 | except AttributeError: 609 | return 610 | 611 | self.assertEquals(self.client.set('foo', '1', px=10000), True) 612 | self.assertEquals(self.client['foo'], b('1')) 613 | self.assert_(0 < self.client.pttl('foo') <= 10000) 614 | self.assert_(0 < self.client.ttl('foo') <= 10) 615 | # expire given a timedelta 616 | expire_at = datetime.timedelta(milliseconds=1000) 617 | self.assertEquals(self.client.set('foo', '1', px=expire_at), True) 618 | self.assert_(0 < self.client.pttl('foo') <= 1000) 619 | self.assert_(0 < self.client.ttl('foo') <= 1) 620 | 621 | def test_set_ex(self): 622 | for info in itervalues(self.client.info()): 623 | version = info['redis_version'] 624 | if StrictVersion(version) < StrictVersion('2.6.12'): 625 | try: 626 | raise unittest.SkipTest() 627 | except AttributeError: 628 | return 629 | 630 | self.assertEquals(self.client.set('foo', '1', ex=10), True) 631 | self.assertEquals(self.client.ttl('foo'), 10) 632 | # expire given a timedelta 633 | expire_at = datetime.timedelta(seconds=60) 634 | self.assertEquals(self.client.set('foo', '1', ex=expire_at), True) 635 | self.assertEquals(self.client.ttl('foo'), 60) 636 | 637 | def test_set_multipleoptions(self): 638 | for info in itervalues(self.client.info()): 639 | version = info['redis_version'] 640 | if StrictVersion(version) < StrictVersion('2.6.12'): 641 | try: 642 | raise unittest.SkipTest() 643 | except AttributeError: 644 | return 645 | 646 | self.client['foo'] = 'val' 647 | self.assertEquals( 648 | self.client.set('foo', 'bar', xx=True, px=10000), 649 | True) 650 | self.assertEquals(self.client.ttl('foo'), 10) 651 | 652 | def test_setex(self): 653 | self.assertEquals(self.client.setex('a', 60, '1'), True) 654 | self.assertEquals(self.client['a'], b('1')) 655 | self.assertEquals(self.client.ttl('a'), 60) 656 | 657 | def test_setnx(self): 658 | self.assert_(self.client.setnx('a', '1')) 659 | self.assertEquals(self.client['a'], b('1')) 660 | self.assert_(not self.client.setnx('a', '2')) 661 | self.assertEquals(self.client['a'], b('1')) 662 | 663 | def test_setrange(self): 664 | self.assertEquals(self.client.setrange('a', 5, 'abcdef'), 11) 665 | self.assertEquals(self.client['a'], b('\0\0\0\0\0abcdef')) 666 | self.client['a'] = 'Hello World' 667 | self.assertEquals(self.client.setrange('a', 6, 'Redis'), 11) 668 | self.assertEquals(self.client['a'], b('Hello Redis')) 669 | 670 | def test_strlen(self): 671 | self.client['a'] = 'abcdef' 672 | self.assertEquals(self.client.strlen('a'), 6) 673 | 674 | def test_substr(self): 675 | # invalid key type 676 | self.client.rpush('a', 'a1') 677 | self.assertRaises( 678 | rediscluster.ResponseError, self.client.substr, 'a', 0) 679 | del self.client['a'] 680 | # real logic 681 | self.client['a'] = 'abcdefghi' 682 | self.assertEquals(self.client.substr('a', 0), b('abcdefghi')) 683 | self.assertEquals(self.client.substr('a', 2), b('cdefghi')) 684 | self.assertEquals(self.client.substr('a', 3, 5), b('def')) 685 | self.assertEquals(self.client.substr('a', 3, -2), b('defgh')) 686 | self.client['a'] = 123456 # does substr work with ints? 687 | self.assertEquals(self.client.substr('a', 2, -2), b('345')) 688 | 689 | def test_type(self): 690 | self.assertEquals(self.client.type('a'), b('none')) 691 | self.client['a'] = '1' 692 | self.assertEquals(self.client.type('a'), b('string')) 693 | del self.client['a'] 694 | self.client.lpush('a', '1') 695 | self.assertEquals(self.client.type('a'), b('list')) 696 | del self.client['a'] 697 | self.client.sadd('a', '1') 698 | self.assertEquals(self.client.type('a'), b('set')) 699 | del self.client['a'] 700 | self.client.zadd('a', **{'1': 1}) 701 | self.assertEquals(self.client.type('a'), b('zset')) 702 | 703 | # LISTS 704 | def make_list(self, name, l): 705 | for i in l: 706 | self.client.rpush(name, i) 707 | 708 | def test_blpop(self): 709 | # CLUSTER 710 | self.make_list('a', 'ab') 711 | self.make_list('b', 'cd') 712 | self.assertEquals( 713 | self.client.blpop('b', timeout=1), 714 | (b('b'), b('c'))) 715 | self.assertEquals( 716 | self.client.blpop('b', timeout=1), 717 | (b('b'), b('d'))) 718 | self.assertEquals( 719 | self.client.blpop('a', timeout=1), 720 | (b('a'), b('a'))) 721 | self.assertEquals( 722 | self.client.blpop('a', timeout=1), 723 | (b('a'), b('b'))) 724 | self.assertEquals(self.client.blpop('b', timeout=1), None) 725 | self.assertEquals(self.client.blpop('a', timeout=1), None) 726 | self.make_list('c', 'a') 727 | self.assertEquals(self.client.blpop('c', timeout=1), (b('c'), b('a'))) 728 | 729 | def test_brpop(self): 730 | self.make_list('a', 'ab') 731 | self.make_list('b', 'cd') 732 | self.assertEquals( 733 | self.client.brpop('b', timeout=1), 734 | (b('b'), b('d'))) 735 | self.assertEquals( 736 | self.client.brpop('b', timeout=1), 737 | (b('b'), b('c'))) 738 | self.assertEquals( 739 | self.client.brpop('a', timeout=1), 740 | (b('a'), b('b'))) 741 | self.assertEquals( 742 | self.client.brpop('a', timeout=1), 743 | (b('a'), b('a'))) 744 | self.assertEquals(self.client.brpop('b', timeout=1), None) 745 | self.assertEquals(self.client.brpop('a', timeout=1), None) 746 | self.make_list('c', 'a') 747 | self.assertEquals(self.client.brpop('c', timeout=1), (b('c'), b('a'))) 748 | 749 | def test_brpoplpush(self): 750 | self.make_list('a', '12') 751 | self.make_list('b', '34') 752 | self.assertEquals(self.client.brpoplpush('a', 'b'), b('2')) 753 | self.assertEquals(self.client.brpoplpush('a', 'b'), b('1')) 754 | self.assertEquals(self.client.brpoplpush('a', 'b', timeout=1), None) 755 | self.assertEquals(self.client.lrange('a', 0, -1), []) 756 | self.assertEquals( 757 | self.client.lrange('b', 0, -1), 758 | [b('1'), b('2'), b('3'), b('4')]) 759 | 760 | def test_brpoplpush_empty_string(self): 761 | try: 762 | raise unittest.SkipTest() 763 | except AttributeError: 764 | return 765 | self.client.lpush('a', '') 766 | self.assertEquals(self.client.brpoplpush('a', 'b'), b('')) 767 | 768 | def test_lindex(self): 769 | # no key 770 | self.assertEquals(self.client.lindex('a', '0'), None) 771 | # key is not a list 772 | self.client['a'] = 'b' 773 | self.assertRaises( 774 | rediscluster.ResponseError, self.client.lindex, 'a', '0') 775 | del self.client['a'] 776 | # real logic 777 | self.make_list('a', 'abc') 778 | self.assertEquals(self.client.lindex('a', '0'), b('a')) 779 | self.assertEquals(self.client.lindex('a', '1'), b('b')) 780 | self.assertEquals(self.client.lindex('a', '2'), b('c')) 781 | 782 | def test_linsert(self): 783 | # no key 784 | self.assertEquals(self.client.linsert('a', 'after', 'x', 'y'), 0) 785 | # key is not a list 786 | self.client['a'] = 'b' 787 | self.assertRaises( 788 | rediscluster.ResponseError, self.client.linsert, 'a', 'after', 'x', 'y' 789 | ) 790 | del self.client['a'] 791 | # real logic 792 | self.make_list('a', 'abc') 793 | self.assertEquals(self.client.linsert('a', 'after', 'b', 'b1'), 4) 794 | self.assertEquals( 795 | self.client.lrange('a', 0, -1), 796 | [b('a'), b('b'), b('b1'), b('c')]) 797 | self.assertEquals(self.client.linsert('a', 'before', 'b', 'a1'), 5) 798 | self.assertEquals( 799 | self.client.lrange('a', 0, -1), 800 | [b('a'), b('a1'), b('b'), b('b1'), b('c')]) 801 | 802 | def test_llen(self): 803 | # no key 804 | self.assertEquals(self.client.llen('a'), 0) 805 | # key is not a list 806 | self.client['a'] = 'b' 807 | self.assertRaises(rediscluster.ResponseError, self.client.llen, 'a') 808 | del self.client['a'] 809 | # real logic 810 | self.make_list('a', 'abc') 811 | self.assertEquals(self.client.llen('a'), 3) 812 | 813 | def test_lpop(self): 814 | # no key 815 | self.assertEquals(self.client.lpop('a'), None) 816 | # key is not a list 817 | self.client['a'] = 'b' 818 | self.assertRaises(rediscluster.ResponseError, self.client.lpop, 'a') 819 | del self.client['a'] 820 | # real logic 821 | self.make_list('a', 'abc') 822 | self.assertEquals(self.client.lpop('a'), b('a')) 823 | self.assertEquals(self.client.lpop('a'), b('b')) 824 | self.assertEquals(self.client.lpop('a'), b('c')) 825 | self.assertEquals(self.client.lpop('a'), None) 826 | 827 | def test_lpush(self): 828 | # key is not a list 829 | self.client['a'] = 'b' 830 | self.assertRaises( 831 | rediscluster.ResponseError, self.client.lpush, 'a', 'a') 832 | del self.client['a'] 833 | # real logic 834 | self.assertEqual(1, self.client.lpush('a', 'b')) 835 | self.assertEqual(2, self.client.lpush('a', 'a')) 836 | for info in itervalues(self.client.info()): 837 | version = info['redis_version'] 838 | if StrictVersion(version) >= StrictVersion('2.4.0'): 839 | self.assertEqual(4, self.client.lpush('a', 'b', 'a')) 840 | break 841 | 842 | self.assertEquals(self.client.lindex('a', 0), b('a')) 843 | self.assertEquals(self.client.lindex('a', 1), b('b')) 844 | 845 | def test_lpushx(self): 846 | # key is not a list 847 | self.client['a'] = 'b' 848 | self.assertRaises( 849 | rediscluster.ResponseError, self.client.lpushx, 'a', 'a') 850 | del self.client['a'] 851 | # real logic 852 | self.assertEquals(self.client.lpushx('a', 'b'), 0) 853 | self.assertEquals(self.client.lrange('a', 0, -1), []) 854 | self.make_list('a', 'abc') 855 | self.assertEquals(self.client.lpushx('a', 'd'), 4) 856 | self.assertEquals( 857 | self.client.lrange('a', 0, -1), 858 | [b('d'), b('a'), b('b'), b('c')]) 859 | 860 | def test_lrange(self): 861 | # no key 862 | self.assertEquals(self.client.lrange('a', 0, 1), []) 863 | # key is not a list 864 | self.client['a'] = 'b' 865 | self.assertRaises( 866 | rediscluster.ResponseError, self.client.lrange, 'a', 0, 1) 867 | del self.client['a'] 868 | # real logic 869 | self.make_list('a', 'abcde') 870 | self.assertEquals( 871 | self.client.lrange('a', 0, 2), 872 | [b('a'), b('b'), b('c')]) 873 | self.assertEquals( 874 | self.client.lrange('a', 2, 10), 875 | [b('c'), b('d'), b('e')]) 876 | 877 | def test_lrem(self): 878 | # no key 879 | self.assertEquals(self.client.lrem('a', 0, 'foo'), 0) 880 | # key is not a list 881 | self.client['a'] = 'b' 882 | self.assertRaises( 883 | rediscluster.ResponseError, self.client.lrem, 'a', 0, 'b') 884 | del self.client['a'] 885 | # real logic 886 | self.make_list('a', 'aaaa') 887 | self.assertEquals(self.client.lrem('a', 1, 'a'), 1) 888 | self.assertEquals( 889 | self.client.lrange('a', 0, 3), 890 | [b('a'), b('a'), b('a')]) 891 | self.assertEquals(self.client.lrem('a', 0, 'a'), 3) 892 | # remove all the elements in the list means the key is deleted 893 | self.assertEquals(self.client.lrange('a', 0, 1), []) 894 | 895 | def test_lset(self): 896 | # no key 897 | self.assertRaises( 898 | rediscluster.ResponseError, self.client.lset, 'a', 1, 'b') 899 | # key is not a list 900 | self.client['a'] = 'b' 901 | self.assertRaises( 902 | rediscluster.ResponseError, self.client.lset, 'a', 1, 'b') 903 | del self.client['a'] 904 | # real logic 905 | self.make_list('a', 'abc') 906 | self.assertEquals( 907 | self.client.lrange('a', 0, 2), 908 | [b('a'), b('b'), b('c')]) 909 | self.assert_(self.client.lset('a', 1, 'd')) 910 | self.assertEquals( 911 | self.client.lrange('a', 0, 2), 912 | [b('a'), b('d'), b('c')]) 913 | 914 | def test_ltrim(self): 915 | # no key -- TODO: Not sure why this is actually true. 916 | self.assert_(self.client.ltrim('a', 0, 2)) 917 | # key is not a list 918 | self.client['a'] = 'b' 919 | self.assertRaises( 920 | rediscluster.ResponseError, self.client.ltrim, 'a', 0, 2) 921 | del self.client['a'] 922 | # real logic 923 | self.make_list('a', 'abc') 924 | self.assert_(self.client.ltrim('a', 0, 1)) 925 | self.assertEquals(self.client.lrange('a', 0, 5), [b('a'), b('b')]) 926 | 927 | def test_rpop(self): 928 | # no key 929 | self.assertEquals(self.client.rpop('a'), None) 930 | # key is not a list 931 | self.client['a'] = 'b' 932 | self.assertRaises(rediscluster.ResponseError, self.client.rpop, 'a') 933 | del self.client['a'] 934 | # real logic 935 | self.make_list('a', 'abc') 936 | self.assertEquals(self.client.rpop('a'), b('c')) 937 | self.assertEquals(self.client.rpop('a'), b('b')) 938 | self.assertEquals(self.client.rpop('a'), b('a')) 939 | self.assertEquals(self.client.rpop('a'), None) 940 | 941 | def test_rpoplpush(self): 942 | # no src key 943 | self.make_list('b', ['b1']) 944 | self.assertEquals(self.client.rpoplpush('a', 'b'), None) 945 | # no dest key 946 | self.assertEquals(self.client.rpoplpush('b', 'a'), b('b1')) 947 | self.assertEquals(self.client.lindex('a', 0), b('b1')) 948 | del self.client['a'] 949 | del self.client['b'] 950 | # src key is not a list 951 | self.client['a'] = 'a1' 952 | self.assertRaises( 953 | rediscluster.ResponseError, self.client.rpoplpush, 'a', 'b') 954 | del self.client['a'] 955 | # dest key is not a list 956 | self.make_list('a', ['a1']) 957 | self.client['b'] = 'b' 958 | self.assertRaises( 959 | rediscluster.ResponseError, self.client.rpoplpush, 'a', 'b') 960 | del self.client['a'] 961 | del self.client['b'] 962 | # real logic 963 | self.make_list('a', ['a1', 'a2', 'a3']) 964 | self.make_list('b', ['b1', 'b2', 'b3']) 965 | self.assertEquals(self.client.rpoplpush('a', 'b'), b('a3')) 966 | self.assertEquals(self.client.lrange('a', 0, 2), [b('a1'), b('a2')]) 967 | self.assertEquals( 968 | self.client.lrange('b', 0, 4), 969 | [b('a3'), b('b1'), b('b2'), b('b3')]) 970 | 971 | def test_rpush(self): 972 | # key is not a list 973 | self.client['a'] = 'b' 974 | self.assertRaises( 975 | rediscluster.ResponseError, self.client.rpush, 'a', 'a') 976 | del self.client['a'] 977 | # real logic 978 | self.assertEqual(1, self.client.rpush('a', 'a')) 979 | self.assertEqual(2, self.client.rpush('a', 'b')) 980 | for info in itervalues(self.client.info()): 981 | version = info['redis_version'] 982 | if StrictVersion(version) >= StrictVersion('2.4.0'): 983 | self.assertEqual(4, self.client.rpush('a', 'a', 'b')) 984 | break 985 | 986 | self.assertEquals(self.client.lindex('a', 0), b('a')) 987 | self.assertEquals(self.client.lindex('a', 1), b('b')) 988 | 989 | def test_rpushx(self): 990 | # key is not a list 991 | self.client['a'] = 'b' 992 | self.assertRaises( 993 | rediscluster.ResponseError, self.client.rpushx, 'a', 'a') 994 | del self.client['a'] 995 | # real logic 996 | self.assertEquals(self.client.rpushx('a', 'b'), 0) 997 | self.assertEquals(self.client.lrange('a', 0, -1), []) 998 | self.make_list('a', 'abc') 999 | self.assertEquals(self.client.rpushx('a', 'd'), 4) 1000 | self.assertEquals( 1001 | self.client.lrange('a', 0, -1), 1002 | [b('a'), b('b'), b('c'), b('d')]) 1003 | 1004 | # Set commands 1005 | def make_set(self, name, l): 1006 | for i in l: 1007 | self.client.sadd(name, i) 1008 | 1009 | def test_sadd(self): 1010 | # key is not a set 1011 | self.client['a'] = 'a' 1012 | self.assertRaises( 1013 | rediscluster.ResponseError, self.client.sadd, 'a', 'a1') 1014 | del self.client['a'] 1015 | # real logic 1016 | members = set([b('a1'), b('a2'), b('a3')]) 1017 | self.make_set('a', members) 1018 | self.assertEquals(self.client.smembers('a'), members) 1019 | 1020 | def test_scard(self): 1021 | # key is not a set 1022 | self.client['a'] = 'a' 1023 | self.assertRaises(rediscluster.ResponseError, self.client.scard, 'a') 1024 | del self.client['a'] 1025 | # real logic 1026 | self.make_set('a', 'abc') 1027 | self.assertEquals(self.client.scard('a'), 3) 1028 | 1029 | def test_sdiff(self): 1030 | # some key is not a set 1031 | self.make_set('a', ['a1', 'a2', 'a3']) 1032 | self.client['b'] = 'b' 1033 | self.assertRaises( 1034 | rediscluster.ResponseError, self.client.sdiff, ['a', 'b']) 1035 | del self.client['b'] 1036 | # real logic 1037 | self.make_set('b', ['b1', 'a2', 'b3']) 1038 | self.assertEquals( 1039 | self.client.sdiff(['a', 'b']), 1040 | set([b('a1'), b('a3')])) 1041 | 1042 | def test_sdiffstore(self): 1043 | # some key is not a set 1044 | self.make_set('a', ['a1', 'a2', 'a3']) 1045 | self.client['b'] = 'b' 1046 | self.assertRaises( 1047 | rediscluster.ResponseError, self.client.sdiffstore, 1048 | 'c', ['a', 'b']) 1049 | del self.client['b'] 1050 | self.make_set('b', ['b1', 'a2', 'b3']) 1051 | # dest key always gets overwritten, even if it's not a set, so don't 1052 | # test for that 1053 | # real logic 1054 | self.assertEquals(self.client.sdiffstore('c', ['a', 'b']), 2) 1055 | self.assertEquals(self.client.smembers('c'), set([b('a1'), b('a3')])) 1056 | 1057 | def test_sinter(self): 1058 | # some key is not a set 1059 | self.make_set('a', ['a1', 'a2', 'a3']) 1060 | self.client['b'] = 'b' 1061 | self.assertRaises( 1062 | rediscluster.ResponseError, self.client.sinter, ['a', 'b']) 1063 | del self.client['b'] 1064 | # real logic 1065 | self.make_set('b', ['a1', 'b2', 'a3']) 1066 | self.assertEquals( 1067 | self.client.sinter(['a', 'b']), 1068 | set([b('a1'), b('a3')])) 1069 | 1070 | def test_sinterstore(self): 1071 | # some key is not a set 1072 | self.make_set('a', ['a1', 'a2', 'a3']) 1073 | self.client['b'] = 'b' 1074 | self.assertRaises( 1075 | rediscluster.ResponseError, self.client.sinterstore, 1076 | 'c', ['a', 'b']) 1077 | del self.client['b'] 1078 | self.make_set('b', ['a1', 'b2', 'a3']) 1079 | # dest key always gets overwritten, even if it's not a set, so don't 1080 | # test for that 1081 | # real logic 1082 | self.assertEquals(self.client.sinterstore('c', ['a', 'b']), 2) 1083 | self.assertEquals(self.client.smembers('c'), set([b('a1'), b('a3')])) 1084 | 1085 | def test_sismember(self): 1086 | # key is not a set 1087 | self.client['a'] = 'a' 1088 | self.assertRaises( 1089 | rediscluster.ResponseError, self.client.sismember, 'a', 'a') 1090 | del self.client['a'] 1091 | # real logic 1092 | self.make_set('a', 'abc') 1093 | self.assertEquals(self.client.sismember('a', 'a'), True) 1094 | self.assertEquals(self.client.sismember('a', 'b'), True) 1095 | self.assertEquals(self.client.sismember('a', 'c'), True) 1096 | self.assertEquals(self.client.sismember('a', 'd'), False) 1097 | 1098 | def test_smembers(self): 1099 | # key is not a set 1100 | self.client['a'] = 'a' 1101 | self.assertRaises( 1102 | rediscluster.ResponseError, self.client.smembers, 'a') 1103 | del self.client['a'] 1104 | # set doesn't exist 1105 | self.assertEquals(self.client.smembers('a'), set()) 1106 | # real logic 1107 | self.make_set('a', 'abc') 1108 | self.assertEquals( 1109 | self.client.smembers('a'), 1110 | set([b('a'), b('b'), b('c')])) 1111 | 1112 | def test_smove(self): 1113 | # src key is not set 1114 | self.make_set('b', ['b1', 'b2']) 1115 | self.assertEquals(self.client.smove('a', 'b', 'a1'), 0) 1116 | # src key is not a set 1117 | self.client['a'] = 'a' 1118 | self.assertRaises( 1119 | rediscluster.ResponseError, self.client.smove, 1120 | 'a', 'b', 'a1') 1121 | del self.client['a'] 1122 | self.make_set('a', ['a1', 'a2']) 1123 | # dest key is not a set 1124 | del self.client['b'] 1125 | self.client['b'] = 'b' 1126 | self.assertRaises( 1127 | rediscluster.ResponseError, self.client.smove, 1128 | 'a', 'b', 'a1') 1129 | del self.client['b'] 1130 | self.make_set('b', ['b1', 'b2']) 1131 | # real logic 1132 | self.assert_(self.client.smove('a', 'b', 'a1')) 1133 | self.assertEquals(self.client.smembers('a'), set([b('a2')])) 1134 | self.assertEquals( 1135 | self.client.smembers('b'), 1136 | set([b('b1'), b('b2'), b('a1')])) 1137 | 1138 | def test_spop(self): 1139 | # key is not set 1140 | self.assertEquals(self.client.spop('a'), None) 1141 | # key is not a set 1142 | self.client['a'] = 'a' 1143 | self.assertRaises(rediscluster.ResponseError, self.client.spop, 'a') 1144 | del self.client['a'] 1145 | # real logic 1146 | s = [b('a'), b('b'), b('c')] 1147 | self.make_set('a', s) 1148 | value = self.client.spop('a') 1149 | self.assert_(value in s) 1150 | self.assertEquals(self.client.smembers('a'), set(s) - set([value])) 1151 | 1152 | def test_srandmember(self): 1153 | # key is not set 1154 | self.assertEquals(self.client.srandmember('a'), None) 1155 | # key is not a set 1156 | self.client['a'] = 'a' 1157 | self.assertRaises( 1158 | rediscluster.ResponseError, self.client.srandmember, 'a') 1159 | del self.client['a'] 1160 | # real logic 1161 | self.make_set('a', 'abc') 1162 | self.assert_(self.client.srandmember('a') in b('abc')) 1163 | 1164 | for info in itervalues(self.client.info()): 1165 | version = info['redis_version'] 1166 | if StrictVersion(version) >= StrictVersion('2.5.0'): 1167 | randoms = self.client.srandmember('a', number=2) 1168 | self.assertEquals(len(randoms), 2) 1169 | for r in randoms: 1170 | self.assert_(r in b('abc')) 1171 | break 1172 | 1173 | def test_srem(self): 1174 | # key is not set 1175 | self.assertEquals(self.client.srem('a', 'a'), False) 1176 | # key is not a set 1177 | self.client['a'] = 'a' 1178 | self.assertRaises( 1179 | rediscluster.ResponseError, self.client.srem, 'a', 'a') 1180 | del self.client['a'] 1181 | # real logic 1182 | self.make_set('a', 'abc') 1183 | self.assertEquals(self.client.srem('a', 'd'), False) 1184 | self.assertEquals(self.client.srem('a', 'b'), True) 1185 | self.assertEquals(self.client.smembers('a'), set([b('a'), b('c')])) 1186 | 1187 | def test_sunion(self): 1188 | # some key is not a set 1189 | self.make_set('a', ['a1', 'a2', 'a3']) 1190 | self.client['b'] = 'b' 1191 | self.assertRaises( 1192 | rediscluster.ResponseError, self.client.sunion, ['a', 'b']) 1193 | del self.client['b'] 1194 | # real logic 1195 | self.make_set('b', ['a1', 'b2', 'a3']) 1196 | self.assertEquals( 1197 | self.client.sunion(['a', 'b']), 1198 | set([b('a1'), b('a2'), b('a3'), b('b2')])) 1199 | 1200 | def test_sunionstore(self): 1201 | # some key is not a set 1202 | self.make_set('a', ['a1', 'a2', 'a3']) 1203 | self.client['b'] = 'b' 1204 | self.assertRaises( 1205 | rediscluster.ResponseError, self.client.sunionstore, 1206 | 'c', ['a', 'b']) 1207 | del self.client['b'] 1208 | self.make_set('b', ['a1', 'b2', 'a3']) 1209 | # dest key always gets overwritten, even if it's not a set, so don't 1210 | # test for that 1211 | # real logic 1212 | self.assertEquals(self.client.sunionstore('c', ['a', 'b']), 4) 1213 | self.assertEquals( 1214 | self.client.smembers('c'), 1215 | set([b('a1'), b('a2'), b('a3'), b('b2')])) 1216 | 1217 | # SORTED SETS 1218 | def make_zset(self, name, d): 1219 | for k, v in d.items(): 1220 | self.client.zadd(name, **{k: v}) 1221 | 1222 | def test_zadd(self): 1223 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1224 | self.assertEquals( 1225 | self.client.zrange('a', 0, 3), 1226 | [b('a1'), b('a2'), b('a3')]) 1227 | 1228 | def test_zcard(self): 1229 | # key is not a zset 1230 | self.client['a'] = 'a' 1231 | self.assertRaises(rediscluster.ResponseError, self.client.zcard, 'a') 1232 | del self.client['a'] 1233 | # real logic 1234 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1235 | self.assertEquals(self.client.zcard('a'), 3) 1236 | 1237 | def test_zcount(self): 1238 | # key is not a zset 1239 | self.client['a'] = 'a' 1240 | self.assertRaises( 1241 | rediscluster.ResponseError, self.client.zcount, 'a', 0, 0) 1242 | del self.client['a'] 1243 | # real logic 1244 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1245 | self.assertEquals(self.client.zcount('a', '-inf', '+inf'), 3) 1246 | self.assertEquals(self.client.zcount('a', 1, 2), 2) 1247 | self.assertEquals(self.client.zcount('a', 10, 20), 0) 1248 | 1249 | def test_zincrby(self): 1250 | # key is not a zset 1251 | self.client['a'] = 'a' 1252 | self.assertRaises( 1253 | rediscluster.ResponseError, self.client.zincrby, 'a', 'a1') 1254 | del self.client['a'] 1255 | # real logic 1256 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1257 | self.assertEquals(self.client.zincrby('a', 'a2'), 3.0) 1258 | self.assertEquals(self.client.zincrby('a', 'a3', amount=5), 8.0) 1259 | self.assertEquals(self.client.zscore('a', 'a2'), 3.0) 1260 | self.assertEquals(self.client.zscore('a', 'a3'), 8.0) 1261 | 1262 | def test_zinterstore(self): 1263 | # CLUSTER 1264 | try: 1265 | raise unittest.SkipTest() 1266 | except AttributeError: 1267 | return 1268 | self.make_zset('a', {'a1': 1, 'a2': 1, 'a3': 1}) 1269 | self.make_zset('b', {'a1': 2, 'a3': 2, 'a4': 2}) 1270 | self.make_zset('c', {'a1': 6, 'a3': 5, 'a4': 4}) 1271 | 1272 | # sum, no weight 1273 | self.assert_(self.client.zinterstore('z', ['a', 'b', 'c'])) 1274 | self.assertEquals( 1275 | self.client.zrange('z', 0, -1, withscores=True), 1276 | [(b('a3'), 8), (b('a1'), 9)] 1277 | ) 1278 | 1279 | # max, no weight 1280 | self.assert_( 1281 | self.client.zinterstore('z', ['a', 'b', 'c'], aggregate='MAX') 1282 | ) 1283 | self.assertEquals( 1284 | self.client.zrange('z', 0, -1, withscores=True), 1285 | [(b('a3'), 5), (b('a1'), 6)] 1286 | ) 1287 | 1288 | # with weight 1289 | self.assert_(self.client.zinterstore('z', {'a': 1, 'b': 2, 'c': 3})) 1290 | self.assertEquals( 1291 | self.client.zrange('z', 0, -1, withscores=True), 1292 | [(b('a3'), 20), (b('a1'), 23)] 1293 | ) 1294 | 1295 | def test_zrange(self): 1296 | # key is not a zset 1297 | self.client['a'] = 'a' 1298 | self.assertRaises( 1299 | rediscluster.ResponseError, self.client.zrange, 'a', 0, 1) 1300 | del self.client['a'] 1301 | # real logic 1302 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1303 | self.assertEquals(self.client.zrange('a', 0, 1), [b('a1'), b('a2')]) 1304 | self.assertEquals(self.client.zrange('a', 1, 2), [b('a2'), b('a3')]) 1305 | self.assertEquals( 1306 | self.client.zrange('a', 0, 1, withscores=True), 1307 | [(b('a1'), 1.0), (b('a2'), 2.0)]) 1308 | self.assertEquals( 1309 | self.client.zrange('a', 1, 2, withscores=True), 1310 | [(b('a2'), 2.0), (b('a3'), 3.0)]) 1311 | # test a custom score casting function returns the correct value 1312 | self.assertEquals( 1313 | self.client.zrange('a', 0, 1, withscores=True, 1314 | score_cast_func=int), 1315 | [(b('a1'), 1), (b('a2'), 2)]) 1316 | # a non existant key should return empty list 1317 | self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), []) 1318 | 1319 | def test_zrangebyscore(self): 1320 | # key is not a zset 1321 | self.client['a'] = 'a' 1322 | self.assertRaises( 1323 | rediscluster.ResponseError, self.client.zrangebyscore, 1324 | 'a', 0, 1) 1325 | del self.client['a'] 1326 | # real logic 1327 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) 1328 | self.assertEquals( 1329 | self.client.zrangebyscore('a', 2, 4), 1330 | [b('a2'), b('a3'), b('a4')]) 1331 | self.assertEquals( 1332 | self.client.zrangebyscore('a', 2, 4, start=1, num=2), 1333 | [b('a3'), b('a4')]) 1334 | self.assertEquals( 1335 | self.client.zrangebyscore('a', 2, 4, withscores=True), 1336 | [(b('a2'), 2.0), (b('a3'), 3.0), (b('a4'), 4.0)]) 1337 | # a non existant key should return empty list 1338 | self.assertEquals( 1339 | self.client.zrangebyscore('b', 0, 1, withscores=True), []) 1340 | 1341 | def test_zrank(self): 1342 | # key is not a zset 1343 | self.client['a'] = 'a' 1344 | self.assertRaises( 1345 | rediscluster.ResponseError, self.client.zrank, 'a', 'a4') 1346 | del self.client['a'] 1347 | # real logic 1348 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) 1349 | self.assertEquals(self.client.zrank('a', 'a1'), 0) 1350 | self.assertEquals(self.client.zrank('a', 'a2'), 1) 1351 | self.assertEquals(self.client.zrank('a', 'a3'), 2) 1352 | self.assertEquals(self.client.zrank('a', 'a4'), 3) 1353 | self.assertEquals(self.client.zrank('a', 'a5'), 4) 1354 | # non-existent value in zset 1355 | self.assertEquals(self.client.zrank('a', 'a6'), None) 1356 | 1357 | def test_zrem(self): 1358 | # key is not a zset 1359 | self.client['a'] = 'a' 1360 | self.assertRaises( 1361 | rediscluster.ResponseError, self.client.zrem, 'a', 'a1') 1362 | del self.client['a'] 1363 | # real logic 1364 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1365 | self.assertEquals(self.client.zrem('a', 'a2'), True) 1366 | self.assertEquals(self.client.zrange('a', 0, 5), [b('a1'), b('a3')]) 1367 | self.assertEquals(self.client.zrem('a', 'b'), False) 1368 | self.assertEquals(self.client.zrange('a', 0, 5), [b('a1'), b('a3')]) 1369 | 1370 | def test_zremrangebyrank(self): 1371 | # key is not a zset 1372 | self.client['a'] = 'a' 1373 | self.assertRaises( 1374 | rediscluster.ResponseError, self.client.zremrangebyscore, 1375 | 'a', 0, 1) 1376 | del self.client['a'] 1377 | # real logic 1378 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) 1379 | self.assertEquals(self.client.zremrangebyrank('a', 1, 3), 3) 1380 | self.assertEquals(self.client.zrange('a', 0, 5), [b('a1'), b('a5')]) 1381 | 1382 | def test_zremrangebyscore(self): 1383 | # key is not a zset 1384 | self.client['a'] = 'a' 1385 | self.assertRaises( 1386 | rediscluster.ResponseError, self.client.zremrangebyscore, 1387 | 'a', 0, 1) 1388 | del self.client['a'] 1389 | # real logic 1390 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) 1391 | self.assertEquals(self.client.zremrangebyscore('a', 2, 4), 3) 1392 | self.assertEquals(self.client.zrange('a', 0, 5), [b('a1'), b('a5')]) 1393 | self.assertEquals(self.client.zremrangebyscore('a', 2, 4), 0) 1394 | self.assertEquals(self.client.zrange('a', 0, 5), [b('a1'), b('a5')]) 1395 | 1396 | def test_zrevrange(self): 1397 | # key is not a zset 1398 | self.client['a'] = 'a' 1399 | self.assertRaises( 1400 | rediscluster.ResponseError, self.client.zrevrange, 1401 | 'a', 0, 1) 1402 | del self.client['a'] 1403 | # real logic 1404 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1405 | self.assertEquals(self.client.zrevrange('a', 0, 1), [b('a3'), b('a2')]) 1406 | self.assertEquals(self.client.zrevrange('a', 1, 2), [b('a2'), b('a1')]) 1407 | self.assertEquals( 1408 | self.client.zrevrange('a', 0, 1, withscores=True), 1409 | [(b('a3'), 3.0), (b('a2'), 2.0)]) 1410 | self.assertEquals( 1411 | self.client.zrevrange('a', 1, 2, withscores=True), 1412 | [(b('a2'), 2.0), (b('a1'), 1.0)]) 1413 | # a non existant key should return empty list 1414 | self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), []) 1415 | 1416 | def test_zrevrangebyscore(self): 1417 | # key is not a zset 1418 | self.client['a'] = 'a' 1419 | self.assertRaises( 1420 | rediscluster.ResponseError, self.client.zrevrangebyscore, 1421 | 'a', 0, 1) 1422 | del self.client['a'] 1423 | # real logic 1424 | self.make_zset('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) 1425 | self.assertEquals( 1426 | self.client.zrevrangebyscore('a', 4, 2), 1427 | [b('a4'), b('a3'), b('a2')]) 1428 | self.assertEquals( 1429 | self.client.zrevrangebyscore('a', 4, 2, start=1, num=2), 1430 | [b('a3'), b('a2')]) 1431 | self.assertEquals( 1432 | self.client.zrevrangebyscore('a', 4, 2, withscores=True), 1433 | [(b('a4'), 4.0), (b('a3'), 3.0), (b('a2'), 2.0)]) 1434 | # a non existant key should return empty list 1435 | self.assertEquals( 1436 | self.client.zrevrangebyscore('b', 1, 0, withscores=True), 1437 | []) 1438 | 1439 | def test_zrevrank(self): 1440 | # key is not a zset 1441 | self.client['a'] = 'a' 1442 | self.assertRaises( 1443 | rediscluster.ResponseError, self.client.zrevrank, 'a', 'a4') 1444 | del self.client['a'] 1445 | # real logic 1446 | self.make_zset('a', {'a1': 5, 'a2': 4, 'a3': 3, 'a4': 2, 'a5': 1}) 1447 | self.assertEquals(self.client.zrevrank('a', 'a1'), 0) 1448 | self.assertEquals(self.client.zrevrank('a', 'a2'), 1) 1449 | self.assertEquals(self.client.zrevrank('a', 'a3'), 2) 1450 | self.assertEquals(self.client.zrevrank('a', 'a4'), 3) 1451 | self.assertEquals(self.client.zrevrank('a', 'a5'), 4) 1452 | self.assertEquals(self.client.zrevrank('a', 'b'), None) 1453 | 1454 | def test_zscore(self): 1455 | # key is not a zset 1456 | self.client['a'] = 'a' 1457 | self.assertRaises( 1458 | rediscluster.ResponseError, self.client.zscore, 'a', 'a1') 1459 | del self.client['a'] 1460 | # real logic 1461 | self.make_zset('a', {'a1': 0, 'a2': 1, 'a3': 2}) 1462 | self.assertEquals(self.client.zscore('a', 'a1'), 0.0) 1463 | self.assertEquals(self.client.zscore('a', 'a2'), 1.0) 1464 | # test a non-existant member 1465 | self.assertEquals(self.client.zscore('a', 'a4'), None) 1466 | 1467 | def test_zunionstore(self): 1468 | # CLUSTER 1469 | try: 1470 | raise unittest.SkipTest() 1471 | except AttributeError: 1472 | return 1473 | self.make_zset('a', {'a1': 1, 'a2': 1, 'a3': 1}) 1474 | self.make_zset('b', {'a1': 2, 'a3': 2, 'a4': 2}) 1475 | self.make_zset('c', {'a1': 6, 'a4': 5, 'a5': 4}) 1476 | 1477 | # sum, no weight 1478 | self.assert_(self.client.zunionstore('z', ['a', 'b', 'c'])) 1479 | self.assertEquals( 1480 | self.client.zrange('z', 0, -1, withscores=True), 1481 | [ 1482 | (b('a2'), 1), 1483 | (b('a3'), 3), 1484 | (b('a5'), 4), 1485 | (b('a4'), 7), 1486 | (b('a1'), 9) 1487 | ] 1488 | ) 1489 | 1490 | # max, no weight 1491 | self.assert_( 1492 | self.client.zunionstore('z', ['a', 'b', 'c'], aggregate='MAX') 1493 | ) 1494 | self.assertEquals( 1495 | self.client.zrange('z', 0, -1, withscores=True), 1496 | [ 1497 | (b('a2'), 1), 1498 | (b('a3'), 2), 1499 | (b('a5'), 4), 1500 | (b('a4'), 5), 1501 | (b('a1'), 6) 1502 | ] 1503 | ) 1504 | 1505 | # with weight 1506 | self.assert_(self.client.zunionstore('z', {'a': 1, 'b': 2, 'c': 3})) 1507 | self.assertEquals( 1508 | self.client.zrange('z', 0, -1, withscores=True), 1509 | [ 1510 | (b('a2'), 1), 1511 | (b('a3'), 5), 1512 | (b('a5'), 12), 1513 | (b('a4'), 19), 1514 | (b('a1'), 23) 1515 | ] 1516 | ) 1517 | 1518 | # HASHES 1519 | def make_hash(self, key, d): 1520 | for k, v in iteritems(d): 1521 | self.client.hset(key, k, v) 1522 | 1523 | def test_hget_and_hset(self): 1524 | # key is not a hash 1525 | self.client['a'] = 'a' 1526 | self.assertRaises( 1527 | rediscluster.ResponseError, self.client.hget, 'a', 'a1') 1528 | del self.client['a'] 1529 | # no key 1530 | self.assertEquals(self.client.hget('a', 'a1'), None) 1531 | # real logic 1532 | self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1533 | self.assertEquals(self.client.hget('a', 'a1'), b('1')) 1534 | self.assertEquals(self.client.hget('a', 'a2'), b('2')) 1535 | self.assertEquals(self.client.hget('a', 'a3'), b('3')) 1536 | # field was updated, redis returns 0 1537 | self.assertEquals(self.client.hset('a', 'a2', 5), 0) 1538 | self.assertEquals(self.client.hget('a', 'a2'), b('5')) 1539 | # field is new, redis returns 1 1540 | self.assertEquals(self.client.hset('a', 'a4', 4), 1) 1541 | self.assertEquals(self.client.hget('a', 'a4'), b('4')) 1542 | # key inside of hash that doesn't exist returns null value 1543 | self.assertEquals(self.client.hget('a', 'b'), None) 1544 | 1545 | def test_hsetnx(self): 1546 | # Initially set the hash field 1547 | self.client.hsetnx('a', 'a1', 1) 1548 | self.assertEqual(self.client.hget('a', 'a1'), b('1')) 1549 | # Try and set the existing hash field to a different value 1550 | self.client.hsetnx('a', 'a1', 2) 1551 | self.assertEqual(self.client.hget('a', 'a1'), b('1')) 1552 | 1553 | def test_hmset(self): 1554 | d = {b('a'): b('1'), b('b'): b('2'), b('c'): b('3')} 1555 | self.assert_(self.client.hmset('foo', d)) 1556 | self.assertEqual(self.client.hgetall('foo'), d) 1557 | self.assertRaises(rediscluster.DataError, self.client.hmset, 'foo', {}) 1558 | 1559 | def test_hmset_empty_value(self): 1560 | d = {b('a'): b('1'), b('b'): b('2'), b('c'): b('')} 1561 | self.assert_(self.client.hmset('foo', d)) 1562 | self.assertEqual(self.client.hgetall('foo'), d) 1563 | 1564 | def test_hmget(self): 1565 | d = {'a': 1, 'b': 2, 'c': 3} 1566 | self.assert_(self.client.hmset('foo', d)) 1567 | self.assertEqual( 1568 | self.client.hmget('foo', ['a', 'b', 'c']), [b('1'), b('2'), b('3')]) 1569 | self.assertEqual( 1570 | self.client.hmget('foo', ['a', 'c']), [b('1'), b('3')]) 1571 | # using *args type args 1572 | self.assertEquals(self.client.hmget('foo', 'a', 'c'), [b('1'), b('3')]) 1573 | 1574 | def test_hmget_empty(self): 1575 | self.assertEqual(self.client.hmget('foo', ['a', 'b']), [None, None]) 1576 | 1577 | def test_hmget_no_keys(self): 1578 | self.assertRaises( 1579 | rediscluster.ResponseError, self.client.hmget, 'foo', []) 1580 | 1581 | def test_hdel(self): 1582 | # key is not a hash 1583 | self.client['a'] = 'a' 1584 | self.assertRaises( 1585 | rediscluster.ResponseError, self.client.hdel, 'a', 'a1') 1586 | del self.client['a'] 1587 | # no key 1588 | self.assertEquals(self.client.hdel('a', 'a1'), False) 1589 | # real logic 1590 | self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1591 | self.assertEquals(self.client.hget('a', 'a2'), b('2')) 1592 | self.assert_(self.client.hdel('a', 'a2')) 1593 | self.assertEquals(self.client.hget('a', 'a2'), None) 1594 | 1595 | def test_hdel_multiple_keys(self): 1596 | try: 1597 | raise unittest.SkipTest() 1598 | except AttributeError: 1599 | return 1600 | self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1601 | self.assertEquals(self.client.hdel('a', 'a1', 'a2'), 2) 1602 | self.assertEquals(self.client.hget('a', 'a1'), None) 1603 | self.assertEquals(self.client.hget('a', 'a2'), None) 1604 | self.assertEquals(self.client.hget('a', 'a3'), b('3')) 1605 | 1606 | def test_hexists(self): 1607 | # key is not a hash 1608 | self.client['a'] = 'a' 1609 | self.assertRaises( 1610 | rediscluster.ResponseError, self.client.hexists, 'a', 'a1') 1611 | del self.client['a'] 1612 | # no key 1613 | self.assertEquals(self.client.hexists('a', 'a1'), False) 1614 | # real logic 1615 | self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1616 | self.assertEquals(self.client.hexists('a', 'a1'), True) 1617 | self.assertEquals(self.client.hexists('a', 'a4'), False) 1618 | self.client.hdel('a', 'a1') 1619 | self.assertEquals(self.client.hexists('a', 'a1'), False) 1620 | 1621 | def test_hgetall(self): 1622 | # key is not a hash 1623 | self.client['a'] = 'a' 1624 | self.assertRaises(rediscluster.ResponseError, self.client.hgetall, 'a') 1625 | del self.client['a'] 1626 | # no key 1627 | self.assertEquals(self.client.hgetall('a'), {}) 1628 | # real logic 1629 | h = {b('a1'): b('1'), b('a2'): b('2'), b('a3'): b('3')} 1630 | self.make_hash('a', h) 1631 | remote_hash = self.client.hgetall('a') 1632 | self.assertEquals(h, remote_hash) 1633 | 1634 | def test_hincrby(self): 1635 | # key is not a hash 1636 | self.client['a'] = 'a' 1637 | self.assertRaises( 1638 | rediscluster.ResponseError, self.client.hincrby, 'a', 'a1') 1639 | del self.client['a'] 1640 | # no key should create the hash and incr the key's value to 1 1641 | self.assertEquals(self.client.hincrby('a', 'a1'), 1) 1642 | # real logic 1643 | self.assertEquals(self.client.hincrby('a', 'a1'), 2) 1644 | self.assertEquals(self.client.hincrby('a', 'a1', amount=2), 4) 1645 | # negative values decrement 1646 | self.assertEquals(self.client.hincrby('a', 'a1', amount= -3), 1) 1647 | # hash that exists, but key that doesn't 1648 | self.assertEquals(self.client.hincrby('a', 'a2', amount=3), 3) 1649 | # finally a key that's not an int 1650 | self.client.hset('a', 'a3', 'foo') 1651 | self.assertRaises( 1652 | rediscluster.ResponseError, self.client.hincrby, 'a', 'a3') 1653 | 1654 | def test_hincrbyfloat(self): 1655 | for info in itervalues(self.client.info()): 1656 | version = info['redis_version'] 1657 | if StrictVersion(version) < StrictVersion('2.5.0'): 1658 | try: 1659 | raise unittest.SkipTest() 1660 | except AttributeError: 1661 | return 1662 | 1663 | # key is not a hash 1664 | self.client['a'] = 'a' 1665 | self.assertRaises(rediscluster.ResponseError, 1666 | self.client.hincrbyfloat, 'a', 'a1') 1667 | del self.client['a'] 1668 | # no key should create the hash and incr the key's value to 1 1669 | self.assertEquals(self.client.hincrbyfloat('a', 'a1'), 1.0) 1670 | self.assertEquals(self.client.hincrbyfloat('a', 'a1'), 2.0) 1671 | self.assertEquals(self.client.hincrbyfloat('a', 'a1', 1.2), 3.2) 1672 | 1673 | def test_hkeys(self): 1674 | # key is not a hash 1675 | self.client['a'] = 'a' 1676 | self.assertRaises(rediscluster.ResponseError, self.client.hkeys, 'a') 1677 | del self.client['a'] 1678 | # no key 1679 | self.assertEquals(self.client.hkeys('a'), []) 1680 | # real logic 1681 | h = {b('a1'): b('1'), b('a2'): b('2'), b('a3'): b('3')} 1682 | self.make_hash('a', h) 1683 | keys = iterkeys(h) 1684 | keys.sort() 1685 | remote_keys = self.client.hkeys('a') 1686 | remote_keys.sort() 1687 | self.assertEquals(keys, remote_keys) 1688 | 1689 | def test_hlen(self): 1690 | # key is not a hash 1691 | self.client['a'] = 'a' 1692 | self.assertRaises(rediscluster.ResponseError, self.client.hlen, 'a') 1693 | del self.client['a'] 1694 | # no key 1695 | self.assertEquals(self.client.hlen('a'), 0) 1696 | # real logic 1697 | self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3}) 1698 | self.assertEquals(self.client.hlen('a'), 3) 1699 | self.client.hdel('a', 'a3') 1700 | self.assertEquals(self.client.hlen('a'), 2) 1701 | 1702 | def test_hvals(self): 1703 | # key is not a hash 1704 | self.client['a'] = 'a' 1705 | self.assertRaises(rediscluster.ResponseError, self.client.hvals, 'a') 1706 | del self.client['a'] 1707 | # no key 1708 | self.assertEquals(self.client.hvals('a'), []) 1709 | # real logic 1710 | h = {b('a1'): b('1'), b('a2'): b('2'), b('a3'): b('3')} 1711 | self.make_hash('a', h) 1712 | vals = itervalues(h) 1713 | vals.sort() 1714 | remote_vals = self.client.hvals('a') 1715 | remote_vals.sort() 1716 | self.assertEquals(vals, remote_vals) 1717 | 1718 | # SORT 1719 | def test_sort_bad_key(self): 1720 | # CLUSTER 1721 | try: 1722 | raise unittest.SkipTest() 1723 | except AttributeError: 1724 | return 1725 | # key is not set 1726 | self.assertEquals(self.client.sort('a'), []) 1727 | # key is a string value 1728 | self.client['a'] = 'a' 1729 | self.assertRaises(rediscluster.ResponseError, self.client.sort, 'a') 1730 | del self.client['a'] 1731 | 1732 | def test_sort_basic(self): 1733 | # CLUSTER 1734 | try: 1735 | raise unittest.SkipTest() 1736 | except AttributeError: 1737 | return 1738 | self.make_list('a', '3214') 1739 | self.assertEquals( 1740 | self.client.sort('a'), 1741 | [b('1'), b('2'), b('3'), b('4')]) 1742 | 1743 | def test_sort_limited(self): 1744 | # CLUSTER 1745 | try: 1746 | raise unittest.SkipTest() 1747 | except AttributeError: 1748 | return 1749 | self.make_list('a', '3214') 1750 | self.assertEquals( 1751 | self.client.sort('a', start=1, num=2), 1752 | [b('2'), b('3')]) 1753 | 1754 | def test_sort_by(self): 1755 | # CLUSTER 1756 | try: 1757 | raise unittest.SkipTest() 1758 | except AttributeError: 1759 | return 1760 | self.client['score:1'] = 8 1761 | self.client['score:2'] = 3 1762 | self.client['score:3'] = 5 1763 | self.make_list('a_values', '123') 1764 | self.assertEquals( 1765 | self.client.sort('a_values', by='score:*'), 1766 | [b('2'), b('3'), b('1')]) 1767 | 1768 | def test_sort_get(self): 1769 | # CLUSTER 1770 | try: 1771 | raise unittest.SkipTest() 1772 | except AttributeError: 1773 | return 1774 | self.client['user:1'] = 'u1' 1775 | self.client['user:2'] = 'u2' 1776 | self.client['user:3'] = 'u3' 1777 | self.make_list('a', '231') 1778 | self.assertEquals( 1779 | self.client.sort('a', get='user:*'), 1780 | [b('u1'), b('u2'), b('u3')]) 1781 | 1782 | def test_sort_get_multi(self): 1783 | # CLUSTER 1784 | try: 1785 | raise unittest.SkipTest() 1786 | except AttributeError: 1787 | return 1788 | self.client['user:1'] = 'u1' 1789 | self.client['user:2'] = 'u2' 1790 | self.client['user:3'] = 'u3' 1791 | self.make_list('a', '231') 1792 | self.assertEquals( 1793 | self.client.sort('a', get=('user:*', '#')), 1794 | [b('u1'), b('1'), b('u2'), b('2'), b('u3'), b('3')]) 1795 | 1796 | def test_sort_get_groups_two(self): 1797 | try: 1798 | raise unittest.SkipTest() 1799 | except AttributeError: 1800 | return 1801 | self.client['user:1'] = 'u1' 1802 | self.client['user:2'] = 'u2' 1803 | self.client['user:3'] = 'u3' 1804 | self.make_list('a', '231') 1805 | self.assertEquals( 1806 | self.client.sort('a', get=('user:*', '#'), groups=True), 1807 | [(b('u1'), b('1')), (b('u2'), b('2')), (b('u3'), b('3'))]) 1808 | 1809 | def test_sort_groups_string_get(self): 1810 | try: 1811 | raise unittest.SkipTest() 1812 | except AttributeError: 1813 | return 1814 | self.client['user:1'] = 'u1' 1815 | self.client['user:2'] = 'u2' 1816 | self.client['user:3'] = 'u3' 1817 | self.make_list('a', '231') 1818 | self.assertRaises(redis.DataError, self.client.sort, 'a', 1819 | get='user:*', groups=True) 1820 | 1821 | def test_sort_groups_just_one_get(self): 1822 | try: 1823 | raise unittest.SkipTest() 1824 | except AttributeError: 1825 | return 1826 | self.client['user:1'] = 'u1' 1827 | self.client['user:2'] = 'u2' 1828 | self.client['user:3'] = 'u3' 1829 | self.make_list('a', '231') 1830 | self.assertRaises(redis.DataError, self.client.sort, 'a', 1831 | get=['user:*'], groups=True) 1832 | 1833 | def test_sort_groups_no_get(self): 1834 | try: 1835 | raise unittest.SkipTest() 1836 | except AttributeError: 1837 | return 1838 | self.client['user:1'] = 'u1' 1839 | self.client['user:2'] = 'u2' 1840 | self.client['user:3'] = 'u3' 1841 | self.make_list('a', '231') 1842 | self.assertRaises(redis.DataError, self.client.sort, 'a', groups=True) 1843 | 1844 | def test_sort_groups_three_gets(self): 1845 | try: 1846 | raise unittest.SkipTest() 1847 | except AttributeError: 1848 | return 1849 | self.client['user:1'] = 'u1' 1850 | self.client['user:2'] = 'u2' 1851 | self.client['user:3'] = 'u3' 1852 | self.client['door:1'] = 'd1' 1853 | self.client['door:2'] = 'd2' 1854 | self.client['door:3'] = 'd3' 1855 | self.make_list('a', '231') 1856 | self.assertEquals( 1857 | self.client.sort('a', get=('user:*', 'door:*', '#'), groups=True), 1858 | [ 1859 | (b('u1'), b('d1'), b('1')), 1860 | (b('u2'), b('d2'), b('2')), 1861 | (b('u3'), b('d3'), b('3')) 1862 | ] 1863 | ) 1864 | 1865 | def test_sort_desc(self): 1866 | # CLUSTER 1867 | try: 1868 | raise unittest.SkipTest() 1869 | except AttributeError: 1870 | return 1871 | self.make_list('a', '231') 1872 | self.assertEquals( 1873 | self.client.sort('a', desc=True), 1874 | [b('3'), b('2'), b('1')]) 1875 | 1876 | def test_sort_alpha(self): 1877 | # CLUSTER 1878 | try: 1879 | raise unittest.SkipTest() 1880 | except AttributeError: 1881 | return 1882 | self.make_list('a', 'ecbda') 1883 | self.assertEquals( 1884 | self.client.sort('a', alpha=True), 1885 | [b('a'), b('b'), b('c'), b('d'), b('e')]) 1886 | 1887 | def test_sort_store(self): 1888 | # CLUSTER 1889 | try: 1890 | raise unittest.SkipTest() 1891 | except AttributeError: 1892 | return 1893 | self.make_list('a', '231') 1894 | self.assertEquals(self.client.sort('a', store='sorted_values'), 3) 1895 | self.assertEquals( 1896 | self.client.lrange('sorted_values', 0, 5), 1897 | [b('1'), b('2'), b('3')]) 1898 | 1899 | def test_sort_all_options(self): 1900 | # CLUSTER 1901 | try: 1902 | raise unittest.SkipTest() 1903 | except AttributeError: 1904 | return 1905 | self.client['user:1:username'] = 'zeus' 1906 | self.client['user:2:username'] = 'titan' 1907 | self.client['user:3:username'] = 'hermes' 1908 | self.client['user:4:username'] = 'hercules' 1909 | self.client['user:5:username'] = 'apollo' 1910 | self.client['user:6:username'] = 'athena' 1911 | self.client['user:7:username'] = 'hades' 1912 | self.client['user:8:username'] = 'dionysus' 1913 | 1914 | self.client['user:1:favorite_drink'] = 'yuengling' 1915 | self.client['user:2:favorite_drink'] = 'rum' 1916 | self.client['user:3:favorite_drink'] = 'vodka' 1917 | self.client['user:4:favorite_drink'] = 'milk' 1918 | self.client['user:5:favorite_drink'] = 'pinot noir' 1919 | self.client['user:6:favorite_drink'] = 'water' 1920 | self.client['user:7:favorite_drink'] = 'gin' 1921 | self.client['user:8:favorite_drink'] = 'apple juice' 1922 | 1923 | self.make_list('gods', '12345678') 1924 | num = self.client.sort( 1925 | 'gods', start=2, num=4, by='user:*:username', 1926 | get='user:*:favorite_drink', desc=True, alpha=True, store='sorted') 1927 | self.assertEquals(num, 4) 1928 | self.assertEquals( 1929 | self.client.lrange('sorted', 0, 10), 1930 | [b('vodka'), b('milk'), b('gin'), b('apple juice')]) 1931 | 1932 | def test_strict_zadd(self): 1933 | client = self.get_client(rediscluster.StrictRedisCluster) 1934 | client.zadd('a', 1.0, 'a1', 2.0, 'a2', a3=3.0) 1935 | self.assertEquals(client.zrange('a', 0, 3, withscores=True), 1936 | [(b('a1'), 1.0), (b('a2'), 2.0), (b('a3'), 3.0)]) 1937 | 1938 | def test_strict_lrem(self): 1939 | client = self.get_client(rediscluster.StrictRedisCluster) 1940 | client.rpush('a', 'a1') 1941 | client.rpush('a', 'a2') 1942 | client.rpush('a', 'a3') 1943 | client.rpush('a', 'a1') 1944 | client.lrem('a', 0, 'a1') 1945 | self.assertEquals(client.lrange('a', 0, -1), [b('a2'), b('a3')]) 1946 | 1947 | def test_strict_setex(self): 1948 | "SETEX swaps the order of the value and timeout" 1949 | client = self.get_client(rediscluster.StrictRedisCluster) 1950 | self.assertEquals(client.setex('a', 60, '1'), True) 1951 | self.assertEquals(client['a'], b('1')) 1952 | self.assertEquals(client.ttl('a'), 60) 1953 | 1954 | def test_strict_expire(self): 1955 | "TTL is -1 by default in StrictRedis" 1956 | client = self.get_client(rediscluster.StrictRedisCluster) 1957 | self.assertEquals(client.expire('a', 10), False) 1958 | self.client['a'] = 'foo' 1959 | self.assertEquals(client.expire('a', 10), True) 1960 | self.assertEquals(client.ttl('a'), 10) 1961 | self.assertEquals(client.persist('a'), True) 1962 | self.assertEquals(client.ttl('a'), -1) 1963 | 1964 | def test_strict_pexpire(self): 1965 | client = self.get_client(rediscluster.StrictRedisCluster) 1966 | for info in itervalues(self.client.info()): 1967 | version = info['redis_version'] 1968 | if StrictVersion(version) < StrictVersion('2.5.0'): 1969 | try: 1970 | raise unittest.SkipTest() 1971 | except AttributeError: 1972 | return 1973 | 1974 | self.assertEquals(client.pexpire('a', 10000), False) 1975 | self.client['a'] = 'foo' 1976 | self.assertEquals(client.pexpire('a', 10000), True) 1977 | self.assert_(client.pttl('a') <= 10000) 1978 | self.assertEquals(client.persist('a'), True) 1979 | self.assertEquals(client.pttl('a'), -1) 1980 | 1981 | # # BINARY SAFE 1982 | # TODO add more tests 1983 | def test_binary_get_set(self): 1984 | self.assertTrue(self.client.set(' foo bar ', '123')) 1985 | self.assertEqual(self.client.get(' foo bar '), b('123')) 1986 | 1987 | self.assertTrue(self.client.set(' foo\r\nbar\r\n ', '456')) 1988 | self.assertEqual(self.client.get(' foo\r\nbar\r\n '), b('456')) 1989 | 1990 | self.assertTrue(self.client.set(' \r\n\t\x07\x13 ', '789')) 1991 | self.assertEqual(self.client.get(' \r\n\t\x07\x13 '), b('789')) 1992 | 1993 | self.assertTrue(self.client.delete(' foo bar ')) 1994 | self.assertTrue(self.client.delete(' foo\r\nbar\r\n ')) 1995 | self.assertTrue(self.client.delete(' \r\n\t\x07\x13 ')) 1996 | 1997 | def test_binary_lists(self): 1998 | mapping = { 1999 | b('foo bar'): [b('1'), b('2'), b('3')], 2000 | b('foo\r\nbar\r\n'): [b('4'), b('5'), b('6')], 2001 | b('foo\tbar\x07'): [b('7'), b('8'), b('9')], 2002 | } 2003 | # fill in lists 2004 | for key, value in iteritems(mapping): 2005 | for c in value: 2006 | self.assertTrue(self.client.rpush(key, c)) 2007 | 2008 | # check that KEYS returns all the keys as they are 2009 | # self.assertEqual(sorted(self.client.keys('*')), sorted(iterkeys(mapping))) 2010 | 2011 | # check that it is possible to get list content by key name 2012 | for key in iterkeys(mapping): 2013 | self.assertEqual(self.client.lrange(key, 0, -1), 2014 | mapping[key]) 2015 | 2016 | def test_22_info(self): 2017 | """ 2018 | Older Redis versions contained 'allocation_stats' in INFO that 2019 | was the cause of a number of bugs when parsing. 2020 | """ 2021 | info = "allocation_stats:6=1,7=1,8=7141,9=180,10=92,11=116,12=5330," \ 2022 | "13=123,14=3091,15=11048,16=225842,17=1784,18=814,19=12020," \ 2023 | "20=2530,21=645,22=15113,23=8695,24=142860,25=318,26=3303," \ 2024 | "27=20561,28=54042,29=37390,30=1884,31=18071,32=31367,33=160," \ 2025 | "34=169,35=201,36=10155,37=1045,38=15078,39=22985,40=12523," \ 2026 | "41=15588,42=265,43=1287,44=142,45=382,46=945,47=426,48=171," \ 2027 | "49=56,50=516,51=43,52=41,53=46,54=54,55=75,56=647,57=332," \ 2028 | "58=32,59=39,60=48,61=35,62=62,63=32,64=221,65=26,66=30," \ 2029 | "67=36,68=41,69=44,70=26,71=144,72=169,73=24,74=37,75=25," \ 2030 | "76=42,77=21,78=126,79=374,80=27,81=40,82=43,83=47,84=46," \ 2031 | "85=114,86=34,87=37,88=7240,89=34,90=38,91=18,92=99,93=20," \ 2032 | "94=18,95=17,96=15,97=22,98=18,99=69,100=17,101=22,102=15," \ 2033 | "103=29,104=39,105=30,106=70,107=22,108=21,109=26,110=52," \ 2034 | "111=45,112=33,113=67,114=41,115=44,116=48,117=53,118=54," \ 2035 | "119=51,120=75,121=44,122=57,123=44,124=66,125=56,126=52," \ 2036 | "127=81,128=108,129=70,130=50,131=51,132=53,133=45,134=62," \ 2037 | "135=12,136=13,137=7,138=15,139=21,140=11,141=20,142=6,143=7," \ 2038 | "144=11,145=6,146=16,147=19,148=1112,149=1,151=83,154=1," \ 2039 | "155=1,156=1,157=1,160=1,161=1,162=2,166=1,169=1,170=1,171=2," \ 2040 | "172=1,174=1,176=2,177=9,178=34,179=73,180=30,181=1,185=3," \ 2041 | "187=1,188=1,189=1,192=1,196=1,198=1,200=1,201=1,204=1,205=1," \ 2042 | "207=1,208=1,209=1,214=2,215=31,216=78,217=28,218=5,219=2," \ 2043 | "220=1,222=1,225=1,227=1,234=1,242=1,250=1,252=1,253=1," \ 2044 | ">=256=203" 2045 | parsed = parse_info(info) 2046 | self.assert_('allocation_stats' in parsed) 2047 | self.assert_('6' in parsed['allocation_stats']) 2048 | self.assert_('>=256' in parsed['allocation_stats']) 2049 | 2050 | """def test_large_responses(self): 2051 | "The PythonParser has some special cases for return values > 1MB" 2052 | # load up 5MB of data into a key 2053 | data = [] 2054 | for i in range(5000000 // len(ascii_letters)): 2055 | data.append(ascii_letters) 2056 | data = ''.join(data) 2057 | self.client.set('a', data) 2058 | self.assertEquals(self.client.get('a'), b(data))""" 2059 | 2060 | def test_floating_point_encoding(self): 2061 | """ 2062 | High precision floating point values sent to the server should keep 2063 | precision. 2064 | """ 2065 | timestamp = 1349673917.939762 2066 | self.client.zadd('a', timestamp, 'aaa') 2067 | self.assertEquals(self.client.zscore('a', 'aaa'), timestamp) 2068 | -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | cluster = { 2 | # node names 3 | 'nodes': { 4 | # masters 5 | 'node_1': {'host': '127.0.0.1', 'port': 63791}, 6 | 'node_2': {'host': '127.0.0.1', 'port': 63792}, 7 | } 8 | } 9 | --------------------------------------------------------------------------------