pax_global_header00006660000000000000000000000064131701534270014515gustar00rootroot0000000000000052 comment=0e0cf4c1bb84328dd389127ea7d74fe1785c6b62 fakeredis-0.9.0/000077500000000000000000000000001317015342700134605ustar00rootroot00000000000000fakeredis-0.9.0/.gitignore000066400000000000000000000000771317015342700154540ustar00rootroot00000000000000.commands.json fakeredis.egg-info dump.rdb extras/* .tox *.pyc fakeredis-0.9.0/.travis.yml000066400000000000000000000007261317015342700155760ustar00rootroot00000000000000language: python python: - 2.6 - 2.7 - 3.3 - 3.4 - 3.5 - 3.6 - nightly sudo: false cache: - pip services: - redis-server install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install -r requirements-26.txt; fi - pip install -r requirements.txt - pip install coverage python-coveralls script: - coverage erase - coverage run --source fakeredis.py test_fakeredis.py notifications: email: - js@jamesls.com after_success: coveralls fakeredis-0.9.0/CONTRIBUTING.rst000066400000000000000000000012401317015342700161160ustar00rootroot00000000000000============ Contributing ============ Contributions are welcome. To ensure that your contributions are accepted please follow these guidelines. * Follow pep8 * If you are adding docstrings, follow pep257 * If you are adding new functionality or fixing a bug, please add tests. * If you are making a large change, consider filing an issue on github first to see if there are any objections to the proposed changes. In general, new features or bug fixes **will not be merged unless they have tests.** This is not only to ensure the correctness of the code, but to also encourage others to expirement without wondering whether or not they are breaking existing code. fakeredis-0.9.0/COPYING000066400000000000000000000025651317015342700145230ustar00rootroot00000000000000Copyright (c) 2011 James Saryerwinnie All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fakeredis-0.9.0/MANIFEST.in000066400000000000000000000000431317015342700152130ustar00rootroot00000000000000include COPYING include README.rst fakeredis-0.9.0/README.rst000066400000000000000000000131131317015342700151460ustar00rootroot00000000000000fakeredis: A fake version of a redis-py ======================================= .. image:: https://secure.travis-ci.org/jamesls/fakeredis.svg?branch=master :target: http://travis-ci.org/jamesls/fakeredis .. image:: https://coveralls.io/repos/jamesls/fakeredis/badge.svg?branch=master :target: https://coveralls.io/r/jamesls/fakeredis fakeredis is a pure python implementation of the redis-py python client that simulates talking to a redis server. This was created for a single purpose: **to write unittests**. Setting up redis is not hard, but many times you want to write unittests that do not talk to an external server (such as redis). This module now allows tests to simply use this module as a reasonable substitute for redis. How to Use ========== The intent is for fakeredis to act as though you're talking to a real redis server. It does this by storing state in the fakeredis module. For example: .. code-block:: python >>> import fakeredis >>> r = fakeredis.FakeStrictRedis() >>> r.set('foo', 'bar') True >>> r.get('foo') 'bar' >>> r.lpush('bar', 1) 1 >>> r.lpush('bar', 2) 2 >>> r.lrange('bar', 0, -1) [2, 1] By storing state in the fakeredis module, instances can share data: .. code-block:: python >>> import fakeredis >>> r1 = fakeredis.FakeStrictRedis() >>> r1.set('foo', 'bar') True >>> r2 = fakeredis.FakeStrictRedis() >>> r2.get('foo') 'bar' >>> r2.set('bar', 'baz') True >>> r1.get('bar') 'baz' >>> r2.get('bar') 'baz' Because fakeredis stores state at the module level, if you want to ensure that you have a clean slate for every unit test you run, be sure to call `r.flushall()` in your ``tearDown`` method. For example:: def setUp(self): # Setup fake redis for testing. self.r = fakeredis.FakeStrictRedis() def tearDown(self): # Clear data in fakeredis. self.r.flushall() Fakeredis implements the same interface as `redis-py`_, the popular redis client for python, and models the responses of redis 2.6. Unimplemented Commands ====================== All of the redis commands are implemented in fakeredis with these exceptions: sorted_set ---------- * zscan hash ---- * hstrlen string ------ * bitop * bitpos geo --- * geoadd * geopos * georadius * geohash * georadiusbymember * geodist generic ------- * restore * dump * migrate * object * wait server ------ * client list * lastsave * slowlog * debug object * shutdown * debug segfault * command count * monitor * client kill * cluster slots * role * config resetstat * time * config get * config set * save * client setname * command getkeys * config rewrite * sync * client getname * bgrewriteaof * slaveof * info * client pause * bgsave * command * dbsize * command info cluster ------- * cluster getkeysinslot * cluster info * readwrite * cluster slots * cluster keyslot * cluster addslots * readonly * cluster saveconfig * cluster forget * cluster meet * cluster slaves * cluster nodes * cluster countkeysinslot * cluster setslot * cluster count-failure-reports * cluster reset * cluster failover * cluster set-config-epoch * cluster delslots * cluster replicate connection ---------- * echo * select * quit * auth scripting --------- * script flush * script kill * script load * evalsha * eval * script exists Contributing ============ Contributions are welcome. Please see the `contributing guide`_ for more details. If you'd like to help out, you can start with any of the issues labeled with `HelpWanted`_. Running the Tests ================= To ensure parity with the real redis, there are a set of integration tests that mirror the unittests. For every unittest that is written, the same test is run against a real redis instance using a real redis-py client instance. In order to run these tests you must have a redis server running on localhost, port 6379 (the default settings). The integration tests use db=10 in order to minimize collisions with an existing redis instance. To run all the tests, install the requirements file:: pip install -r requirements.txt If you just want to run the unittests:: nosetests test_fakeredis.py:TestFakeStrictRedis test_fakeredis.py:TestFakeRedis Because this module is attempting to provide the same interface as `redis-py`_, the python bindings to redis, a reasonable way to test this to to take each unittest and run it against a real redis server. fakeredis and the real redis server should give the same result. This ensures parity between the two. You can run these "integration" tests like this:: nosetests test_fakeredis.py:TestRealStrictRedis test_fakeredis.py:TestRealRedis In terms of implementation, ``TestRealRedis`` is a subclass of ``TestFakeRedis`` that overrides a factory method to create an instance of ``redis.Redis`` (an actual python client for redis) instead of ``fakeredis.FakeStrictRedis``. To run both the unittests and the "integration" tests, run:: nosetests If redis is not running and you try to run tests against a real redis server, these tests will have a result of 'S' for skipped. There are some tests that test redis blocking operations that are somewhat slow. If you want to skip these tests during day to day development, they have all been tagged as 'slow' so you can skip them by running:: nosetests -a '!slow' .. _redis-py: http://redis-py.readthedocs.org/en/latest/index.html .. _contributing guide: https://github.com/jamesls/fakeredis/blob/master/CONTRIBUTING.rst .. _HelpWanted: https://github.com/jamesls/fakeredis/issues?q=is%3Aissue+is%3Aopen+label%3AHelpWanted fakeredis-0.9.0/fakeredis.py000066400000000000000000002036721317015342700160010ustar00rootroot00000000000000# -*- coding: utf-8 -*- import random import warnings import copy from ctypes import CDLL, POINTER, c_double, c_char_p, pointer from ctypes.util import find_library import fnmatch from collections import MutableMapping from datetime import datetime, timedelta import operator import sys import time import types import re import redis from redis.exceptions import ResponseError import redis.client try: # Python 2.6, 2.7 from Queue import Queue, Empty except: # Python 3 from queue import Queue, Empty PY2 = sys.version_info[0] == 2 __version__ = '0.9.0' if PY2: DEFAULT_ENCODING = 'utf-8' text_type = unicode string_types = (str, unicode) redis_string_types = (str, unicode, bytes) byte_to_int = ord int_to_byte = chr def to_bytes(x, charset=DEFAULT_ENCODING, errors='strict'): if isinstance(x, unicode): return x.encode(charset, errors) if isinstance(x, (bytes, bytearray, buffer)) or hasattr(x, '__str__'): return bytes(x) if hasattr(x, '__unicode__'): return unicode(x).encode(charset, errors) raise TypeError('expected bytes or unicode, not ' + type(x).__name__) def to_native(x, charset=sys.getdefaultencoding(), errors='strict'): if x is None or isinstance(x, str): return x return x.encode(charset, errors) iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() from urlparse import urlparse else: DEFAULT_ENCODING = sys.getdefaultencoding() long = int basestring = str text_type = str string_types = (str,) redis_string_types = (bytes, str) def byte_to_int(b): if isinstance(b, int): return b raise TypeError('an integer is required') int_to_byte = operator.methodcaller('to_bytes', 1, 'big') def to_bytes(x, charset=sys.getdefaultencoding(), errors='strict'): if isinstance(x, (bytes, bytearray, memoryview)): return bytes(x) if isinstance(x, str): return x.encode(charset, errors) if hasattr(x, '__str__'): return str(x).encode(charset, errors) raise TypeError('expected bytes or str, not ' + type(x).__name__) def to_native(x, charset=sys.getdefaultencoding(), errors='strict'): if x is None or isinstance(x, str): return x return x.decode(charset, errors) iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) from urllib.parse import urlparse DATABASES = {} _libc_library = find_library('c') or find_library('msvcrt') if not _libc_library: raise ImportError('fakeredis: unable to find libc or equivalent') _libc = CDLL(_libc_library) _libc.strtod.restype = c_double _libc.strtod.argtypes = [c_char_p, POINTER(c_char_p)] _strtod = _libc.strtod def timedelta_total_seconds(delta): return delta.days * 86400 + delta.seconds + delta.microseconds / 1E6 class _StrKeyDict(MutableMapping): def __init__(self, *args, **kwargs): self._dict = dict(*args, **kwargs) self._ex_keys = {} def __getitem__(self, key): self._update_expired_keys() return self._dict[to_bytes(key)] def __setitem__(self, key, value): self._dict[to_bytes(key)] = value def __delitem__(self, key): del self._dict[to_bytes(key)] def __len__(self): return len(self._dict) def __iter__(self): return iter(self._dict) def expire(self, key, timestamp): self._ex_keys[key] = timestamp def persist(self, key): try: del self._ex_keys[key] except KeyError: pass def expiring(self, key): if key not in self._ex_keys: return None return self._ex_keys[key] def _update_expired_keys(self): now = datetime.now() deleted = [] for key in self._ex_keys: if now > self._ex_keys[key]: deleted.append(key) for key in deleted: del self._ex_keys[key] del self[key] def copy(self): new_copy = _StrKeyDict() for key, value in self._dict.items(): new_copy[key] = value return new_copy def clear(self): super(_StrKeyDict, self).clear() self._ex_keys.clear() def to_bare_dict(self): return copy.deepcopy(self._dict) class _ZSet(_StrKeyDict): redis_type = b'zset' class _Hash(_StrKeyDict): redis_type = b'hash' def DecodeGenerator(gen): for item in gen: yield _decode(item) def _decode(value): if isinstance(value, text_type): return value elif isinstance(value, bytes): value = value.decode(DEFAULT_ENCODING) elif isinstance(value, dict): value = dict((_decode(k), _decode(v)) for k, v in value.items()) elif isinstance(value, (list, set, tuple)): value = value.__class__(_decode(x) for x in value) elif isinstance(value, types.GeneratorType): value = DecodeGenerator(value) return value def _make_decode_func(func): def decode_response(*args, **kwargs): val = _decode(func(*args, **kwargs)) return val return decode_response def _patch_responses(obj): for attr_name in dir(obj): attr = getattr(obj, attr_name) if not callable(attr) or attr_name.startswith('_'): continue func = _make_decode_func(attr) setattr(obj, attr_name, func) class FakeStrictRedis(object): @classmethod def from_url(cls, url, db=None, **kwargs): url = urlparse(url) if db is None: try: db = int(url.path.replace('/', '')) except (AttributeError, ValueError): db = 0 return cls(db=db, **kwargs) def __init__(self, db=0, charset='utf-8', errors='strict', decode_responses=False, **kwargs): if db not in DATABASES: DATABASES[db] = _StrKeyDict() self._db = DATABASES[db] self._db_num = db self._encoding = charset self._encoding_errors = errors self._pubsubs = [] self._decode_responses = decode_responses if decode_responses: _patch_responses(self) def flushdb(self): DATABASES[self._db_num].clear() return True def flushall(self): for db in DATABASES: DATABASES[db].clear() del self._pubsubs[:] # Basic key commands def append(self, key, value): self._db.setdefault(key, b'') self._db[key] += to_bytes(value) return len(self._db[key]) def bitcount(self, name, start=0, end=-1): if end == -1: end = None else: end += 1 try: s = self._db[name][start:end] return sum([bin(byte_to_int(l)).count('1') for l in s]) except KeyError: return 0 def decr(self, name, amount=1): try: self._db[name] = int(self._db.get(name, '0')) - amount except (TypeError, ValueError): raise redis.ResponseError("value is not an integer or out of " "range.") return self._db[name] def exists(self, name): return name in self._db __contains__ = exists def expire(self, name, time): return self._expire(name, time) def pexpire(self, name, millis): return self._expire(name, millis, 1000) def _expire(self, name, time, multiplier=1): if isinstance(time, timedelta): time = int(timedelta_total_seconds(time) * multiplier) if not isinstance(time, int): raise redis.ResponseError("value is not an integer or out of " "range.") if self.exists(name): self._db.expire(name, datetime.now() + timedelta(seconds=time / float(multiplier))) return True else: return False def expireat(self, name, when): return self._expireat(name, when) def pexpireat(self, name, when): return self._expireat(name, when, 1000) def _expireat(self, name, when, multiplier=1): if not isinstance(when, datetime): when = datetime.fromtimestamp(when / float(multiplier)) if self.exists(name): self._db.expire(name, when) return True else: return False def echo(self, value): if isinstance(value, text_type): return value.encode('utf-8') return value def get(self, name): value = self._db.get(name) if isinstance(value, _StrKeyDict): raise redis.ResponseError("WRONGTYPE Operation against a key " "holding the wrong kind of value") if value is not None: return to_bytes(value) def __getitem__(self, name): return self.get(name) def getbit(self, name, offset): """Returns a boolean indicating the value of ``offset`` in ``name``""" val = self._db.get(name, '\x00') byte = offset // 8 remaining = offset % 8 actual_bitoffset = 7 - remaining try: actual_val = byte_to_int(val[byte]) except IndexError: return 0 return 1 if (1 << actual_bitoffset) & actual_val else 0 def getset(self, name, value): """ Set the value at key ``name`` to ``value`` if key doesn't exist Return the value at key ``name`` atomically """ val = self._db.get(name) self._db[name] = value return val def incr(self, name, amount=1): """ Increments the value of ``key`` by ``amount``. If no key exists, the value will be initialized as ``amount`` """ try: if not isinstance(amount, int): raise redis.ResponseError("value is not an integer or out " "of range.") self._db[name] = to_bytes(int(self._db.get(name, '0')) + amount) except (TypeError, ValueError): raise redis.ResponseError("value is not an integer or out of " "range.") return int(self._db[name]) def incrby(self, name, amount=1): """ Alias for command ``incr`` """ return self.incr(name, amount) def incrbyfloat(self, name, amount=1.0): try: self._db[name] = float(self._db.get(name, '0')) + amount except (TypeError, ValueError): raise redis.ResponseError("value is not a valid float.") return self._db[name] def keys(self, pattern=None): return [key for key in self._db if not key or not pattern or fnmatch.fnmatch(to_native(key), to_native(pattern))] def mget(self, keys, *args): all_keys = self._list_or_args(keys, args) found = [] if not all_keys: raise redis.ResponseError( "wrong number of arguments for 'mget' command") for key in all_keys: found.append(self._db.get(key)) return found def mset(self, *args, **kwargs): if args: if len(args) != 1 or not isinstance(args[0], dict): raise redis.RedisError( 'MSET requires **kwargs or a single dict arg') kwargs.update(args[0]) for key, val in iteritems(kwargs): self.set(key, val) return True def msetnx(self, mapping): """ Sets each key in the ``mapping`` dict to its corresponding value if none of the keys are already set """ if not any(k in self._db for k in mapping): for key, val in iteritems(mapping): self.set(key, val) return True return False def move(self, name, db): pass def persist(self, name): self._db.persist(name) def ping(self): return True def randomkey(self): pass def rename(self, src, dst): try: value = self._db[src] except KeyError: raise redis.ResponseError("No such key: %s" % src) self._db[dst] = value del self._db[src] return True def renamenx(self, src, dst): if dst in self._db: return False else: return self.rename(src, dst) def set(self, name, value, ex=None, px=None, nx=False, xx=False): if (not nx and not xx) or (nx and self._db.get(name, None) is None) \ or (xx and not self._db.get(name, None) is None): if ex is not None: if isinstance(ex, timedelta): ex = ex.seconds + ex.days * 24 * 3600 if ex < 0: raise ResponseError('invalid expire time in SETEX') if ex > 0: self._db.expire(name, datetime.now() + timedelta(seconds=ex)) elif px is not None: if isinstance(px, timedelta): ms = int(px.microseconds / 1000) px = (px.seconds + px.days * 24 * 3600) * 1000 + ms if px < 0: raise ResponseError('invalid expire time in SETEX') if px > 0: self._db.expire(name, datetime.now() + timedelta(milliseconds=px)) else: self._db.persist(name) self._db[name] = to_bytes(value) return True else: return None __setitem__ = set def setbit(self, name, offset, value): val = self._db.get(name, b'\x00') byte = offset // 8 remaining = offset % 8 actual_bitoffset = 7 - remaining if len(val) - 1 < byte: # We need to expand val so that we can set the appropriate # bit. needed = byte - (len(val) - 1) val += b'\x00' * needed if value == 1: new_byte = byte_to_int(val[byte]) | (1 << actual_bitoffset) else: new_byte = byte_to_int(val[byte]) ^ (1 << actual_bitoffset) reconstructed = bytearray(val) reconstructed[byte] = new_byte self._db[name] = bytes(reconstructed) def setex(self, name, time, value): if isinstance(time, timedelta): time = int(timedelta_total_seconds(time)) if not isinstance(time, int): raise ResponseError( 'value is not an integer or out of range') return self.set(name, value, ex=time) def psetex(self, name, time_ms, value): if isinstance(time_ms, timedelta): time_ms = int(timedelta_total_seconds(time_ms) * 1000) if time_ms == 0: raise ResponseError("invalid expire time in SETEX") return self.set(name, value, px=time_ms) def setnx(self, name, value): result = self.set(name, value, nx=True) # Real Redis returns False from setnx, but None from set(nx=...) if not result: return False return result def setrange(self, name, offset, value): val = self._db.get(name, b"") if len(val) < offset: val += b'\x00' * (offset - len(val)) val = val[0:offset] + to_bytes(value) + val[offset+len(value):] self.set(name, val) return len(val) def strlen(self, name): try: return len(self._db[name]) except KeyError: return 0 def substr(self, name, start, end=-1): if end == -1: end = None else: end += 1 try: return self._db[name][start:end] except KeyError: return b'' # Redis >= 2.0.0 this command is called getrange # according to the docs. getrange = substr def ttl(self, name): return self._ttl(name) def pttl(self, name): return self._ttl(name, 1000) def _ttl(self, name, multiplier=1): if name not in self._db: return -2 exp_time = self._db.expiring(name) if not exp_time: return -1 now = datetime.now() if now > exp_time: return None else: return long(round(((exp_time - now).days * 3600 * 24 + (exp_time - now).seconds + (exp_time - now).microseconds / 1E6) * multiplier)) def type(self, name): key = self._db.get(name) if hasattr(key.__class__, 'redis_type'): return key.redis_type if isinstance(key, redis_string_types): return b'string' elif isinstance(key, list): return b'list' elif isinstance(key, set): return b'set' else: assert key is None return b'none' def watch(self, *names): pass def unwatch(self): pass def delete(self, *names): deleted = 0 for name in names: try: del self._db[name] if name in self._db._ex_keys: del self._db._ex_keys[name] deleted += 1 except KeyError: continue return deleted def sort(self, name, start=None, num=None, by=None, get=None, desc=False, alpha=False, store=None): """Sort and return the list, set or sorted set at ``name``. ``start`` and ``num`` allow for paging through the sorted data ``by`` allows using an external key to weight and sort the items. Use an "*" to indicate where in the key the item value is located ``get`` allows for returning items from external keys rather than the sorted data itself. Use an "*" to indicate where int he key the item value is located ``desc`` allows for reversing the sort ``alpha`` allows for sorting lexicographically rather than numerically ``store`` allows for storing the result of the sort into the key ``store`` """ if (start is None and num is not None) or \ (start is not None and num is None): raise redis.RedisError( "RedisError: ``start`` and ``num`` must both be specified") try: data = list(self._db[name])[:] if by is not None: # _sort_using_by_arg mutates data so we don't # need need a return value. self._sort_using_by_arg(data, by=by) elif not alpha: data.sort(key=self._strtod_key_func) else: data.sort() if desc: data = list(reversed(data)) if not (start is None and num is None): data = data[start:start + num] if store is not None: self._db[store] = data return len(data) else: return self._retrive_data_from_sort(data, get) except KeyError: return [] def _retrive_data_from_sort(self, data, get): if get is not None: if isinstance(get, string_types): get = [get] new_data = [] for k in data: for g in get: single_item = self._get_single_item(k, g) new_data.append(single_item) data = new_data return data def _get_single_item(self, k, g): g = to_bytes(g) if b'*' in g: g = g.replace(b'*', k) if b'->' in g: key, hash_key = g.split(b'->') single_item = self._db.get(key, {}).get(hash_key) else: single_item = self._db.get(g) elif b'#' in g: single_item = k else: single_item = None return single_item def _strtod_key_func(self, arg): # str()'ing the arg is important! Don't ever remove this. arg = to_bytes(arg) end = c_char_p() val = _strtod(arg, pointer(end)) # real Redis also does an isnan check, not sure if # that's needed here or not. if end.value: raise redis.ResponseError( "One or more scores can't be converted into double") else: return val def _sort_using_by_arg(self, data, by): by = to_bytes(by) def _by_key(arg): key = by.replace(b'*', arg) if b'->' in by: key, hash_key = key.split(b'->') return self._db.get(key, {}).get(hash_key) else: return self._db.get(key) data.sort(key=_by_key) def lpush(self, name, *values): self._db.setdefault(name, [])[0:0] = list(reversed( [to_bytes(x) for x in values])) return len(self._db[name]) def lrange(self, name, start, end): if end == -1: end = None else: end += 1 return self._db.get(name, [])[start:end] def llen(self, name): return len(self._db.get(name, [])) def lrem(self, name, count, value): value = to_bytes(value) a_list = self._db.get(name, []) found = [] for i, el in enumerate(a_list): if el == value: found.append(i) if count > 0: indices_to_remove = found[:count] elif count < 0: indices_to_remove = found[count:] else: indices_to_remove = found # Iterating in reverse order to ensure the indices # remain valid during deletion. for index in reversed(indices_to_remove): del a_list[index] return len(indices_to_remove) def rpush(self, name, *values): self._db.setdefault(name, []).extend([to_bytes(x) for x in values]) return len(self._db[name]) def lpop(self, name): try: return self._db.get(name, []).pop(0) except IndexError: return None def lset(self, name, index, value): try: self._db.get(name, [])[index] = to_bytes(value) except IndexError: raise redis.ResponseError("index out of range") def rpushx(self, name, value): try: self._db[name].append(to_bytes(value)) except KeyError: return def ltrim(self, name, start, end): try: val = self._db[name] except KeyError: return True if end == -1: end = None else: end += 1 self._db[name] = val[start:end] return True def lindex(self, name, index): try: return self._db.get(name, [])[index] except IndexError: return None def lpushx(self, name, value): try: self._db[name].insert(0, to_bytes(value)) except KeyError: return def rpop(self, name): try: return self._db.get(name, []).pop() except IndexError: return None def linsert(self, name, where, refvalue, value): index = self._db.get(name, []).index(to_bytes(refvalue)) self._db.get(name, []).insert(index, to_bytes(value)) def rpoplpush(self, src, dst): el = self.rpop(src) if el is not None: el = to_bytes(el) try: self._db[dst].insert(0, el) except KeyError: self._db[dst] = [el] return el def blpop(self, keys, timeout=0): # This has to be a best effort approximation which follows # these rules: # 1) For each of those keys see if there's something we can # pop from. # 2) If this is not the case then simulate a timeout. # This means that there's not really any blocking behavior here. if isinstance(keys, string_types): keys = [to_bytes(keys)] else: keys = [to_bytes(k) for k in keys] for key in keys: if self._db.get(key, []): return (key, self._db[key].pop(0)) def brpop(self, keys, timeout=0): if isinstance(keys, string_types): keys = [to_bytes(keys)] else: keys = [to_bytes(k) for k in keys] for key in keys: if self._db.get(key, []): return (key, self._db[key].pop()) def brpoplpush(self, src, dst, timeout=0): el = self.rpop(src) if el is not None: el = to_bytes(el) try: self._db[dst].insert(0, el) except KeyError: self._db[dst] = [el] return el def hdel(self, name, *keys): h = self._db.get(name, {}) rem = 0 for k in keys: if k in h: del h[k] rem += 1 return rem def hexists(self, name, key): "Returns a boolean indicating if ``key`` exists within hash ``name``" if self._db.get(name, {}).get(key) is None: return 0 else: return 1 def hget(self, name, key): "Return the value of ``key`` within the hash ``name``" return self._db.get(name, {}).get(key) def hgetall(self, name): "Return a Python dict of the hash's name/value pairs" all_items = self._db.get(name, {}) if hasattr(all_items, 'to_bare_dict'): all_items = all_items.to_bare_dict() return all_items def hincrby(self, name, key, amount=1): "Increment the value of ``key`` in hash ``name`` by ``amount``" new = int(self._db.setdefault(name, _Hash()).get(key, '0')) + amount self._db[name][key] = new return new def hincrbyfloat(self, name, key, amount=1.0): """Increment the value of key in hash name by floating amount""" try: amount = float(amount) except ValueError: raise redis.ResponseError("value is not a valid float") try: current = float(self._db.setdefault(name, _Hash()).get(key, '0')) except ValueError: raise redis.ResponseError("hash value is not a valid float") new = current + amount self._db[name][key] = new return new def hkeys(self, name): "Return the list of keys within hash ``name``" return list(self._db.get(name, {})) def hlen(self, name): "Return the number of elements in hash ``name``" return len(self._db.get(name, {})) def hset(self, name, key, value): """ Set ``key`` to ``value`` within hash ``name`` Returns 1 if HSET created a new field, otherwise 0 """ key_is_new = key not in self._db.get(name, {}) self._db.setdefault(name, _Hash())[key] = to_bytes(value) return 1 if key_is_new else 0 def hsetnx(self, name, key, value): """ Set ``key`` to ``value`` within hash ``name`` if ``key`` does not exist. Returns 1 if HSETNX created a field, otherwise 0. """ if key in self._db.get(name, {}): return False self._db.setdefault(name, _Hash())[key] = to_bytes(value) return True def hmset(self, name, mapping): """ Sets each key in the ``mapping`` dict to its corresponding value in the hash ``name`` """ if not mapping: raise redis.DataError("'hmset' with 'mapping' of length 0") new_mapping = {} for k, v in mapping.items(): new_mapping[k] = to_bytes(v) self._db.setdefault(name, _Hash()).update(new_mapping) return True def hmget(self, name, keys, *args): "Returns a list of values ordered identically to ``keys``" h = self._db.get(name, {}) all_keys = self._list_or_args(keys, args) return [h.get(k) for k in all_keys] def hvals(self, name): "Return the list of values within hash ``name``" return list(self._db.get(name, {}).values()) def sadd(self, name, *values): "Add ``value`` to set ``name``" a_set = self._db.setdefault(name, set()) card = len(a_set) a_set |= set(to_bytes(x) for x in values) return len(a_set) - card def scard(self, name): "Return the number of elements in set ``name``" return len(self._db.get(name, set())) def sdiff(self, keys, *args): "Return the difference of sets specified by ``keys``" all_keys = (to_bytes(x) for x in self._list_or_args(keys, args)) diff = self._db.get(next(all_keys), set()).copy() for key in all_keys: diff -= self._db.get(key, set()) return diff def sdiffstore(self, dest, keys, *args): """ Store the difference of sets specified by ``keys`` into a new set named ``dest``. Returns the number of keys in the new set. """ diff = self.sdiff(keys, *args) self._db[dest] = set(to_bytes(x) for x in diff) return len(diff) def sinter(self, keys, *args): "Return the intersection of sets specified by ``keys``" all_keys = (to_bytes(x) for x in self._list_or_args(keys, args)) intersect = self._db.get(next(all_keys), set()).copy() for key in all_keys: intersect.intersection_update(self._db.get(key, set())) return intersect def sinterstore(self, dest, keys, *args): """ Store the intersection of sets specified by ``keys`` into a new set named ``dest``. Returns the number of keys in the new set. """ intersect = self.sinter(keys, *args) self._db[dest] = set(to_bytes(x) for x in intersect) return len(intersect) def sismember(self, name, value): "Return a boolean indicating if ``value`` is a member of set ``name``" return to_bytes(value) in self._db.get(name, set()) def smembers(self, name): "Return all members of the set ``name``" return self._db.get(name, set()) def smove(self, src, dst, value): value = to_bytes(value) try: self._db.get(src, set()).remove(value) self._db.setdefault(dst, set()).add(value) return True except KeyError: return False def spop(self, name): "Remove and return a random member of set ``name``" try: return self._db.get(name, set()).pop() except KeyError: return None def srandmember(self, name, number=None): """ If ``number`` is None, returns a random member of set ``name``. If ``number`` is supplied, returns a list of ``number`` random memebers of set ``name``. """ members = self._db.get(name, set()) if not members: if number is not None: return [] else: return None if number is None: index = random.randint(0, len(members) - 1) return list(members)[index] elif len(members) <= number: # We return them all, shuffled. res = list(members) random.shuffle(res) return res else: member_list = list(members) return [ member_list[i] for i in sorted(random.sample(range(len(members)), number)) ] def srem(self, name, *values): "Remove ``value`` from set ``name``" a_set = self._db.setdefault(name, set()) card = len(a_set) a_set -= set(to_bytes(x) for x in values) return card - len(a_set) def sunion(self, keys, *args): "Return the union of sets specifiued by ``keys``" all_keys = (to_bytes(x) for x in self._list_or_args(keys, args)) union = self._db.get(next(all_keys), set()).copy() for key in all_keys: union.update(self._db.get(key, set())) return union def sunionstore(self, dest, keys, *args): """ Store the union of sets specified by ``keys`` into a new set named ``dest``. Returns the number of keys in the new set. """ union = self.sunion(keys, *args) self._db[dest] = set(to_bytes(x) for x in union) return len(union) def _get_zelement_range_filter_func(self, min_val, max_val): # This will return a filter function based on the # min and max values. It takes a single argument # and return True if it matches the range filter # criteria, and False otherwise. # This will also handle the case when # min/max are '-inf', '+inf'. # It needs to handle exclusive intervals # where the min/max value is something like # '(0' # a < x < b # ^ ^ ^ ^ # actual_min left_comp right_comp actual_max left_comparator, actual_min = self._get_comparator_and_val(min_val) right_comparator, actual_max = self._get_comparator_and_val(max_val) def _matches(x): return (left_comparator(actual_min, x) and right_comparator(x, actual_max)) return _matches def _get_comparator_and_val(self, value): try: if isinstance(value, string_types) and value.startswith('('): comparator = operator.lt actual_value = float(value[1:]) else: comparator = operator.le actual_value = float(value) except ValueError: raise redis.ResponseError('min or max is not a float') return comparator, actual_value def _get_zelement_lexrange_filter_func(self, min_str, max_str): # This will return a filter function based on the # min_str and max_str values. It takes a single argument # and return True if it matches the range filter # criteria, and False otherwise. # This will handles inclusive '[' and exclusive '(' # boundaries, as well as '-' and '+' which are # considered 'negative infinitiy string' and # maximum infinity string, which are handled by comparing # against empty string. # a < or <= x < or <= b # ^ ^ ^ ^ # actual_min left_comp right_comp actual_max min_str = to_bytes(min_str) max_str = to_bytes(max_str) left_comparator, actual_min = self._get_lexcomp_and_str(min_str) right_comparator, actual_max = self._get_lexcomp_and_str(max_str) def _matches(x): return (left_comparator(actual_min, x) and right_comparator(x, actual_max)) return _matches def _get_lexcomp_and_str(self, value): if value.startswith(b'('): comparator = operator.lt actual_value = value[1:] elif value.startswith(b'['): comparator = operator.le actual_value = value[1:] elif value == b'-': # negative infinity string -- all strings greater than # compares: '' < X comparator = operator.le actual_value = b'' elif value == b'+': # positive infinity string -- all strings less than # compares: '' > X comparator = operator.ge actual_value = b'' else: msg = ('min and max must start with ( or [, ' + ' or min may be - and max may be +') raise redis.ResponseError(msg) return comparator, actual_value def zadd(self, name, *args, **kwargs): """ Set any number of score, element-name pairs to the key ``name``. Pairs can be specified in two ways: As *args, in the form of: score1, name1, score2, name2, ... or as **kwargs, in the form of: name1=score1, name2=score2, ... The following example would add four values to the 'my-key' key: redis.zadd('my-key', 1.1, 'name1', 2.2, 'name2', name3=3.3, name4=4.4) """ if len(args) % 2 != 0: raise redis.RedisError("ZADD requires an equal number of " "values and scores") zset = self._db.setdefault(name, _ZSet()) added = 0 for score, value in zip(*[args[i::2] for i in range(2)]): if value not in zset: added += 1 try: zset[value] = float(score) except ValueError: raise redis.ResponseError("value is not a valid float") for value, score in kwargs.items(): if value not in zset: added += 1 try: zset[value] = float(score) except ValueError: raise redis.ResponseError("value is not a valid float") return added def zcard(self, name): "Return the number of elements in the sorted set ``name``" return len(self._db.get(name, {})) def zcount(self, name, min, max): found = 0 filter_func = self._get_zelement_range_filter_func(min, max) for score in self._db.get(name, {}).values(): if filter_func(score): found += 1 return found def zincrby(self, name, value, amount=1): "Increment the score of ``value`` in sorted set ``name`` by ``amount``" d = self._db.setdefault(name, _ZSet()) score = d.get(value, 0) + amount d[value] = score return score def zinterstore(self, dest, keys, aggregate=None): """ Intersect multiple sorted sets specified by ``keys`` into a new sorted set, ``dest``. Scores in the destination will be aggregated based on the ``aggregate``, or SUM if none is provided. """ if not keys: raise redis.ResponseError("At least one key must be specified " "for ZINTERSTORE/ZUNIONSTORE") # keys can be a list or a dict so it needs to be converted to # a list first. list_keys = list(keys) valid_keys = set(self._db.get(list_keys[0], {})) for key in list_keys[1:]: valid_keys.intersection_update(self._db.get(key, {})) return self._zaggregate(dest, keys, aggregate, lambda x: x in valid_keys) def zrange(self, name, start, end, desc=False, withscores=False): """ Return a range of values from sorted set ``name`` between ``start`` and ``end`` sorted in ascending order. ``start`` and ``end`` can be negative, indicating the end of the range. ``desc`` indicates to sort in descending order. ``withscores`` indicates to return the scores along with the values. The return type is a list of (value, score) pairs """ if end == -1: end = None else: end += 1 all_items = self._db.get(name, {}) if desc: reverse = True else: reverse = False in_order = self._get_zelements_in_order(all_items, reverse) items = in_order[start:end] if not withscores: return items else: return [(k, all_items[k]) for k in items] def _get_zelements_in_order(self, all_items, reverse=False): by_keyname = sorted( all_items.items(), key=lambda x: x[0], reverse=reverse) in_order = sorted(by_keyname, key=lambda x: x[1], reverse=reverse) return [el[0] for el in in_order] def zrangebyscore(self, name, min, max, start=None, num=None, withscores=False): """ Return a range of values from the sorted set ``name`` with scores between ``min`` and ``max``. If ``start`` and ``num`` are specified, then return a slice of the range. ``withscores`` indicates to return the scores along with the values. The return type is a list of (value, score) pairs """ return self._zrangebyscore(name, min, max, start, num, withscores, reverse=False) def _zrangebyscore(self, name, min, max, start, num, withscores, reverse): if (start is not None and num is None) or \ (num is not None and start is None): raise redis.RedisError("``start`` and ``num`` must both " "be specified") all_items = self._db.get(name, {}) in_order = self._get_zelements_in_order(all_items, reverse=reverse) filter_func = self._get_zelement_range_filter_func(min, max) matches = [] for item in in_order: if filter_func(all_items[item]): matches.append(item) if start is not None: matches = matches[start:start + num] if withscores: return [(k, all_items[k]) for k in matches] return matches def zrangebylex(self, name, min, max, start=None, num=None): """ Returns lexicographically ordered values from sorted set ``name`` between values ``min`` and ``max``. The ``min`` and ``max`` params must: - start with ``(`` for exclusive boundary - start with ``[`` (inclusive boundary) - equal ``-`` for negative infinite string (start) - equal ``+`` for positive infinite string (stop) If ``start`` and ``num`` are specified, then a slice of the range is returned. """ return self._zrangebylex(name, min, max, start, num, reverse=False) def _zrangebylex(self, name, min, max, start, num, reverse): if (start is not None and num is None) or \ (num is not None and start is None): raise redis.RedisError("``start`` and ``num`` must both " "be specified") all_items = self._db.get(name, {}) in_order = self._get_zelements_in_order(all_items, reverse=reverse) filter_func = self._get_zelement_lexrange_filter_func(min, max) matches = [] for item in in_order: if filter_func(item): matches.append(item) if start is not None: if num < 0: num = len(matches) matches = matches[start:start + num] return matches def zrank(self, name, value): """ Returns a 0-based value indicating the rank of ``value`` in sorted set ``name`` """ all_items = self._db.get(name, {}) in_order = sorted(all_items, key=lambda x: all_items[x]) try: return in_order.index(to_bytes(value)) except ValueError: return None def zrem(self, name, *values): "Remove member ``value`` from sorted set ``name``" z = self._db.get(name, {}) rem = 0 for v in values: if v in z: del z[v] rem += 1 return rem def zremrangebyrank(self, name, min, max): """ Remove all elements in the sorted set ``name`` with ranks between ``min`` and ``max``. Values are 0-based, ordered from smallest score to largest. Values can be negative indicating the highest scores. Returns the number of elements removed """ all_items = self._db.get(name, {}) in_order = self._get_zelements_in_order(all_items) num_deleted = 0 if max == -1: max = None else: max += 1 for key in in_order[min:max]: del all_items[key] num_deleted += 1 return num_deleted def zremrangebyscore(self, name, min, max): """ Remove all elements in the sorted set ``name`` with scores between ``min`` and ``max``. Returns the number of elements removed. """ all_items = self._db.get(name, {}) filter_func = self._get_zelement_range_filter_func(min, max) removed = 0 for key in all_items.copy(): if filter_func(all_items[key]): del all_items[key] removed += 1 return removed def zremrangebylex(self, name, min, max): """ Remove all elements in the sorted set ``name`` that are in lexicograpically between ``min`` and ``max`` The ``min`` and ``max`` params must: - start with ``(`` for exclusive boundary - start with ``[`` (inclusive boundary) - equal ``-`` for negative infinite string (start) - equal ``+`` for positive infinite string (stop) """ all_items = self._db.get(name, {}) filter_func = self._get_zelement_lexrange_filter_func(min, max) removed = 0 for key in all_items.copy(): if filter_func(key): del all_items[key] removed += 1 return removed def zlexcount(self, name, min, max): """ Returns a count of elements in the sorted set ``name`` that are in lexicograpically between ``min`` and ``max`` The ``min`` and ``max`` params must: - start with ``(`` for exclusive boundary - start with ``[`` (inclusive boundary) - equal ``-`` for negative infinite string (start) - equal ``+`` for positive infinite string (stop) """ all_items = self._db.get(name, {}) filter_func = self._get_zelement_lexrange_filter_func(min, max) found = 0 for key in all_items.copy(): if filter_func(key): found += 1 return found def zrevrange(self, name, start, num, withscores=False): """ Return a range of values from sorted set ``name`` between ``start`` and ``num`` sorted in descending order. ``start`` and ``num`` can be negative, indicating the end of the range. ``withscores`` indicates to return the scores along with the values The return type is a list of (value, score) pairs """ return self.zrange(name, start, num, True, withscores) def zrevrangebyscore(self, name, max, min, start=None, num=None, withscores=False): """ Return a range of values from the sorted set ``name`` with scores between ``min`` and ``max`` in descending order. If ``start`` and ``num`` are specified, then return a slice of the range. ``withscores`` indicates to return the scores along with the values. The return type is a list of (value, score) pairs """ return self._zrangebyscore(name, min, max, start, num, withscores, reverse=True) def zrevrangebylex(self, name, max, min, start=None, num=None): """ Returns reverse lexicographically ordered values from sorted set ``name`` between values ``min`` and ``max``. The ``min`` and ``max`` params must: - start with ``(`` for exclusive boundary - start with ``[`` (inclusive boundary) - equal ``-`` for negative infinite string (start) - equal ``+`` for positive infinite string (stop) If ``start`` and ``num`` are specified, then a slice of the range is returned. """ return self._zrangebylex(name, min, max, start, num, reverse=True) def zrevrank(self, name, value): """ Returns a 0-based value indicating the descending rank of ``value`` in sorted set ``name`` """ num_items = len(self._db.get(name, {})) zrank = self.zrank(name, value) if zrank is not None: return num_items - self.zrank(name, value) - 1 def zscore(self, name, value): "Return the score of element ``value`` in sorted set ``name``" try: return self._db[name][value] except KeyError: return None def zunionstore(self, dest, keys, aggregate=None): """ Union multiple sorted sets specified by ``keys`` into a new sorted set, ``dest``. Scores in the destination will be aggregated based on the ``aggregate``, or SUM if none is provided. """ if not keys: raise redis.ResponseError("At least one key must be specified " "for ZINTERSTORE/ZUNIONSTORE") self._zaggregate(dest, keys, aggregate, lambda x: True) def _zaggregate(self, dest, keys, aggregate, should_include): new_zset = _ZSet() if aggregate is None: aggregate = 'SUM' # This is what the actual redis client uses, so we'll use # the same type check. if isinstance(keys, dict): keys_weights = [(k, keys[k]) for k in keys] else: keys_weights = [(k, 1) for k in keys] for key, weight in keys_weights: current_zset = self._db.get(key, {}) if isinstance(current_zset, set): # When casting set to zset redis uses a default score of 1.0 current_zset = dict((k, 1.0) for k in current_zset) for el in current_zset: if not should_include(el): continue if el not in new_zset: new_zset[el] = current_zset[el] * weight elif aggregate == 'SUM': new_zset[el] += current_zset[el] * weight elif aggregate == 'MAX': new_zset[el] = max([new_zset[el], current_zset[el] * weight]) elif aggregate == 'MIN': new_zset[el] = min([new_zset[el], current_zset[el] * weight]) self._db[dest] = new_zset def _list_or_args(self, keys, args): # Returns a single list combining keys and args. # Copy of list_or_args from redis-py. try: iter(keys) # a string or bytes instance can be iterated, but indicates # keys wasn't passed as a list if isinstance(keys, (basestring, bytes)): keys = [keys] except TypeError: keys = [keys] if args: keys.extend(args) return keys def pipeline(self, transaction=True): """Return an object that can be used to issue Redis commands in a batch. Arguments -- transaction (bool) -- whether the buffered commands are issued atomically. True by default. """ return FakePipeline(self, transaction) def transaction(self, func, *keys): # We use a for loop instead of while # because if the test this is being used in # goes wrong we don't want an infinite loop! with self.pipeline() as p: for _ in range(5): try: p.watch(*keys) func(p) return p.execute() except redis.WatchError: continue raise redis.WatchError('Could not run transaction after 5 tries') def pubsub(self, ignore_subscribe_messages=False): """ Returns a new FakePubSub instance """ ps = FakePubSub(decode_responses=self._decode_responses, ignore_subscribe_messages=ignore_subscribe_messages) self._pubsubs.append(ps) return ps def publish(self, channel, message): """ Loops throug all available pubsub objects and publishes the ``message`` to then for the given ``channel``. """ count = 0 for i, ps in enumerate(self._pubsubs): if not ps.subscribed: del self._pubsubs[i] continue count += ps.put(channel, message, 'message') return count # HYPERLOGLOG COMMANDS def pfadd(self, name, *values): "Adds the specified elements to the specified HyperLogLog." # Simulate the behavior of HyperLogLog by using SETs underneath to # approximate the behavior. result = self.sadd(name, *values) # Per the documentation: # - 1 if at least 1 HyperLogLog internal register was altered. 0 otherwise. return 1 if result > 0 else 0 def pfcount(self, *sources): """ Return the approximated cardinality of the set observed by the HyperLogLog at key(s). """ return len(self.sunion(*sources)) def pfmerge(self, dest, *sources): "Merge N different HyperLogLogs into a single one." self.sunionstore(dest, sources) return True # SCAN commands def _scan(self, keys, cursor, match, count): """ This is the basis of most of the ``scan`` methods. This implementation is KNOWN to be un-performant, as it requires grabbing the full set of keys over which we are investigating subsets. """ if cursor >= len(keys): return 0, [] data = sorted(keys) result_cursor = cursor + count result_data = [] # subset = for val in data[cursor:result_cursor]: if not match or fnmatch.fnmatch(to_native(val), to_native(match)): result_data.append(val) if result_cursor >= len(data): result_cursor = 0 return result_cursor, result_data def scan(self, cursor=0, match=None, count=None): return self._scan(self.keys(), int(cursor), match, count or 10) def sscan(self, name, cursor=0, match=None, count=None): return self._scan(self.smembers(name), int(cursor), match, count or 10) def hscan(self, name, cursor=0, match=None, count=None): cursor, keys = self._scan(self.hkeys(name), int(cursor), match, count or 10) results = {} for k in keys: results[k] = self.hget(name, k) return cursor, results def scan_iter(self, match=None, count=None): # This is from redis-py cursor = '0' while cursor != 0: cursor, data = self.scan(cursor=cursor, match=match, count=count) for item in data: yield item def sscan_iter(self, name, match=None, count=None): # This is from redis-py cursor = '0' while cursor != 0: cursor, data = self.sscan(name, cursor=cursor, match=match, count=count) for item in data: yield item def hscan_iter(self, name, match=None, count=None): # This is from redis-py cursor = '0' while cursor != 0: cursor, data = self.hscan(name, cursor=cursor, match=match, count=count) for item in data.items(): yield item class FakeRedis(FakeStrictRedis): def setex(self, name, value, time): return super(FakeRedis, self).setex(name, time, value) def lrem(self, name, value, num=0): return super(FakeRedis, self).lrem(name, num, value) def zadd(self, name, value=None, score=None, **pairs): """ For each kwarg in ``pairs``, add that item and it's score to the sorted set ``name``. The ``value`` and ``score`` arguments are deprecated. """ if value is not None or score is not None: if value is None or score is None: raise redis.RedisError( "Both 'value' and 'score' must be specified to ZADD") warnings.warn(DeprecationWarning( "Passing 'value' and 'score' has been deprecated. " "Please pass via kwargs instead.")) pairs = {str(value): score} elif not pairs: raise redis.RedisError("ZADD is missing kwargs param") return super(FakeRedis, self).zadd(name, **pairs) def ttl(self, name): r = super(FakeRedis, self).ttl(name) return r if r >= 0 else None def pttl(self, name): r = super(FakeRedis, self).pttl(name) return r if r >= 0 else None class FakePipeline(object): """Helper class for FakeStrictRedis to implement pipelines. A pipeline is a collection of commands that are buffered until you call ``execute``, at which point they are called sequentially and a list of their return values is returned. """ def __init__(self, owner, transaction=True): """Create a pipeline for the specified FakeStrictRedis instance. Arguments -- owner -- a FakeStrictRedis instance. """ self.owner = owner self.transaction = transaction self.commands = [] self.need_reset = False self.is_immediate = False self.watching = {} def __getattr__(self, name): """Magic method to allow FakeStrictRedis commands to be called. Returns a method that records the command for later. """ if not hasattr(self.owner, name): raise AttributeError('%r: does not have attribute %r' % (self.owner, name)) def meth(*args, **kwargs): if self.is_immediate: # Special mode during watch_multi sequence. return getattr(self.owner, name)(*args, **kwargs) self.commands.append((name, args, kwargs)) return self setattr(self, name, meth) return meth def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.reset() def execute(self, raise_on_error=True): """Run all the commands in the pipeline and return the results.""" try: if self.watching: mismatches = [ (k, v, u) for (k, v, u) in [(k, v, self.owner._db.get(k)) for (k, v) in self.watching.items()] if v != u] if mismatches: self.commands = [] self.watching = {} raise redis.WatchError( 'Watched key%s %s changed' % ( '' if len(mismatches) == 1 else 's', ', '.join(k for (k, _, _) in mismatches))) if raise_on_error: ret = [getattr(self.owner, name)(*args, **kwargs) for name, args, kwargs in self.commands] else: ret = [] for name, args, kwargs in self.commands: try: ret.append(getattr(self.owner, name)(*args, **kwargs)) except Exception as exc: ret.append(exc) return ret finally: # Redis-py will reset in all cases, so do that. self.commands = [] self.watching = {} def watch(self, *keys): self.watching.update((key, copy.deepcopy(self.owner._db.get(key))) for key in keys) self.need_reset = True self.is_immediate = True def multi(self): self.is_immediate = False def reset(self): self.need_reset = False class FakePubSub(object): PUBLISH_MESSAGE_TYPES = ['message', 'pmessage'] SUBSCRIBE_MESSAGE_TYPES = ['subscribe', 'psubscribe'] UNSUBSCRIBE_MESSAGE_TYPES = ['unsubscribe', 'punsubscribe'] PATTERN_MESSAGE_TYPES = ['psubscribe', 'punsubscribe'] LISTEN_DELAY = 0.1 # delay between listen loops (seconds) def __init__(self, decode_responses=False, *args, **kwargs): self.channels = {} self.patterns = {} self._q = Queue() self.subscribed = False if decode_responses: _patch_responses(self) self.ignore_subscribe_messages = kwargs.get( 'ignore_subscribe_messages', False) def put(self, channel, message, message_type, pattern=None): """ Utility function to be used as the publishing entrypoint for this pubsub object """ if message_type in self.SUBSCRIBE_MESSAGE_TYPES or\ message_type in self.UNSUBSCRIBE_MESSAGE_TYPES: return self._send(message_type, None, channel, message) count = 0 # Send the message on the given channel if channel in self.channels: count += self._send(message_type, None, channel, message) # See if any of the patterns match the given channel for pattern, pattern_obj in iteritems(self.patterns): match = re.match(pattern_obj['regex'], channel) if match: count += self._send('pmessage', pattern, channel, message) return count def _send(self, message_type, pattern, channel, data): msg = { 'type': message_type, 'pattern': pattern, 'channel': channel.encode(), 'data': data.encode() if type(data) == str else data } self._q.put(msg) return 1 def psubscribe(self, *args, **kwargs): """ Subcribe to channel patterns. """ def _subscriber(pattern, handler): regex = self._parse_pattern(pattern) return { 'regex': regex, 'handler': handler } total_subscriptions =\ len(self.channels.keys()) + len(self.patterns.keys()) self._subscribe(self.patterns, 'psubscribe', total_subscriptions, _subscriber, *args, **kwargs) def punsubscribe(self, *args): """ Unsubscribes from one or more patterns. """ total_subscriptions =\ len(self.channels.keys()) + len(self.patterns.keys()) self._usubscribe(self.patterns, 'punsubscribe', total_subscriptions, *args) def _parse_pattern(self, pattern): temp_pattern = pattern if '?' in temp_pattern: temp_pattern = temp_pattern.replace('?', '.') if '*' in temp_pattern: temp_pattern = temp_pattern.replace('*', '.*') if ']' in temp_pattern: temp_pattern = temp_pattern.replace(']', ']?') return temp_pattern def subscribe(self, *args, **kwargs): """ Subscribes to one or more given ``channels``. """ def _subscriber(channel, handler): return handler total_subscriptions =\ len(self.channels.keys()) + len(self.patterns.keys()) self._subscribe(self.channels, 'subscribe', total_subscriptions, _subscriber, *args, **kwargs) def _subscribe(self, subscribed_dict, message_type, total_subscriptions, subscriber, *args, **kwargs): new_channels = {} if args: for arg in args: new_channels[arg] = subscriber(arg, None) for channel, handler in iteritems(kwargs): new_channels[channel] = handler subscribed_dict.update(new_channels) self.subscribed = True for channel in new_channels: total_subscriptions += 1 self.put(channel, long(total_subscriptions), message_type) def unsubscribe(self, *args): """ Unsubscribes from one or more given ``channels``. """ total_subscriptions =\ len(self.channels.keys()) + len(self.patterns.keys()) self._usubscribe(self.channels, 'unsubscribe', total_subscriptions, *args) def _usubscribe(self, subscribed_dict, message_type, total_subscriptions, *args): if args: for channel in args: if channel in subscribed_dict: total_subscriptions -= 1 self.put(channel, long(total_subscriptions), message_type) else: for channel in subscribed_dict: total_subscriptions -= 1 self.put(channel, long(total_subscriptions), message_type) subscribed_dict.clear() if total_subscriptions == 0: self.subscribed = False def listen(self): """ Listens for queued messages and yields the to the calling process """ while self.subscribed: message = self.get_message() if message: yield message continue time.sleep(self.LISTEN_DELAY) def close(self): """ Stops the listen function by calling unsubscribe """ self.unsubscribe() self.punsubscribe() def get_message(self, ignore_subscribe_messages=False, timeout=0): """ Returns the next available message. """ try: message = self._q.get(True, timeout) return self.handle_message(message, ignore_subscribe_messages) except Empty: return None def handle_message(self, message, ignore_subscribe_messages=False): """ Parses a pubsub message. It invokes the handler of a message type, if the handler is avaialble. If the message is of type ``subscribe`` and ignore_subscribe_messages if True, then it returns None. Otherwise, it returns the message. """ message_type = message['type'] if message_type in self.UNSUBSCRIBE_MESSAGE_TYPES: subscribed_dict = None if message_type == 'punsubscribe': subscribed_dict = self.patterns else: subscribed_dict = self.channels try: channel = message['channel'].decode('utf-8') del subscribed_dict[channel] except: pass if message_type in self.PUBLISH_MESSAGE_TYPES: # if there's a message handler, invoke it handler = None if message_type == 'pmessage': pattern = self.patterns.get(message['pattern'], None) if pattern: handler = pattern['handler'] else: handler = self.channels.get(message['channel'], None) if handler: handler(message) return None else: # this is a subscribe/unsubscribe message. ignore if we don't # want them if ignore_subscribe_messages or self.ignore_subscribe_messages: return None return message fakeredis-0.9.0/requirements-26.txt000066400000000000000000000000451317015342700171700ustar00rootroot00000000000000-r requirements.txt unittest2==0.5.1 fakeredis-0.9.0/requirements-dev.txt000066400000000000000000000000641317015342700175200ustar00rootroot00000000000000invoke==0.9.0 wheel==0.24.0 tox==1.4.2 twine==1.5.0 fakeredis-0.9.0/requirements.txt000066400000000000000000000000321317015342700167370ustar00rootroot00000000000000nose==1.3.4 redis==2.10.5 fakeredis-0.9.0/scripts/000077500000000000000000000000001317015342700151475ustar00rootroot00000000000000fakeredis-0.9.0/scripts/supported000077500000000000000000000040301317015342700171170ustar00rootroot00000000000000#!/usr/bin/env python # Script will import fakeredis and list what # commands it does not have support for, based on the # command list from: # https://raw.github.com/antirez/redis-doc/master/commands.json # Because, who wants to do this by hand... import os import json import inspect from urllib2 import urlopen import fakeredis THIS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) COMMANDS_FILE = os.path.join(THIS_DIR, '.commands.json') COMMANDS_URL = 'https://raw.github.com/antirez/redis-doc/master/commands.json' if not os.path.exists(COMMANDS_FILE): contents = urlopen(COMMANDS_URL).read() open(COMMANDS_FILE, 'w').write(contents) commands = json.load(open(COMMANDS_FILE)) for k, v in commands.items(): commands[k.lower()] = v del commands[k] implemented_commands = set() for name, method in inspect.getmembers(fakeredis.FakeRedis): if hasattr(method, 'im_func'): if name == 'delete': # This is specific to the python bindings because # 'del' is a keyword so delete is used instead. name = 'del' if name == 'incr': # redis-py supports incrby by the incr method # so implementing incr means implementing incrby. implemented_commands.add('incrby') if name == 'decr': # Same thing for decr vs. decrby implemented_commands.add('decrby') implemented_commands.add(name) unimplemented_commands = set() for command in commands: if command not in implemented_commands: unimplemented_commands.add(command) # Group by 'group' for easier to read output groups = {} for command in unimplemented_commands: group = commands[command]['group'] groups.setdefault(group, []).append(command) print """ Unimplemented Commands ====================== All of the redis commands are implemented in fakeredis with these exceptions: """ for group in groups: print group print "-" * len(str(group)) print for command in groups[group]: print " *", command print "\n" fakeredis-0.9.0/setup.cfg000066400000000000000000000000341317015342700152760ustar00rootroot00000000000000[bdist_wheel] universal = 1 fakeredis-0.9.0/setup.py000066400000000000000000000020201317015342700151640ustar00rootroot00000000000000import os from setuptools import setup, find_packages setup( name='fakeredis', version='0.9.0', description="Fake implementation of redis API for testing purposes.", long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(), license='BSD', url="https://github.com/jamesls/fakeredis", author='James Saryerwinnie', author_email='js@jamesls.com', py_modules=['fakeredis'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], install_requires=[ 'redis', ] ) fakeredis-0.9.0/test_fakeredis.py000066400000000000000000003440501317015342700170340ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from time import sleep, time from redis.exceptions import ResponseError import inspect from functools import wraps import os import sys import threading from nose.plugins.skip import SkipTest from nose.plugins.attrib import attr import redis import redis.client import fakeredis from datetime import datetime, timedelta try: # Python 2.6, 2.7 from Queue import Queue except: # Python 3 from queue import Queue PY2 = sys.version_info[0] == 2 if not PY2: long = int if sys.version_info[:2] == (2, 6): import unittest2 as unittest else: import unittest # Try importlib, then imp, then the old builtin `reload` try: from importlib import reload except: try: from imp import reload except: pass DEFAULT_ENCODING = fakeredis.DEFAULT_ENCODING def redis_must_be_running(cls): # This can probably be improved. This will determines # at import time if the tests should be run, but we probably # want it to be when the tests are actually run. try: r = redis.StrictRedis('localhost', port=6379) r.ping() except redis.ConnectionError: redis_running = False else: redis_running = True if not redis_running: for name, attribute in inspect.getmembers(cls): if name.startswith('test_'): @wraps(attribute) def skip_test(*args, **kwargs): raise SkipTest("Redis is not running.") setattr(cls, name, skip_test) cls.setUp = lambda x: None cls.tearDown = lambda x: None return cls def key_val_dict(size=100): return dict([(b'key:' + bytes([i]), b'val:' + bytes([i])) for i in range(size)]) class TestFakeStrictRedis(unittest.TestCase): decode_responses = False def setUp(self): self.redis = self.create_redis() def tearDown(self): self.redis.flushall() del self.redis if sys.version_info >= (3,): def assertItemsEqual(self, a, b): return self.assertCountEqual(a, b) def create_redis(self, db=0): return fakeredis.FakeStrictRedis(db=db) def test_flushdb(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.keys(), [b'foo']) self.assertEqual(self.redis.flushdb(), True) self.assertEqual(self.redis.keys(), []) def test_set_then_get(self): self.assertEqual(self.redis.set('foo', 'bar'), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_set_None_value(self): self.assertEqual(self.redis.set('foo', None), True) self.assertEqual(self.redis.get('foo'), b'None') def test_saving_non_ascii_chars_as_value(self): self.assertEqual(self.redis.set('foo', 'Ñandu'), True) self.assertEqual(self.redis.get('foo'), u'Ñandu'.encode(DEFAULT_ENCODING)) def test_saving_unicode_type_as_value(self): self.assertEqual(self.redis.set('foo', u'Ñandu'), True) self.assertEqual(self.redis.get('foo'), u'Ñandu'.encode(DEFAULT_ENCODING)) def test_saving_non_ascii_chars_as_key(self): self.assertEqual(self.redis.set('Ñandu', 'foo'), True) self.assertEqual(self.redis.get('Ñandu'), b'foo') def test_saving_unicode_type_as_key(self): self.assertEqual(self.redis.set(u'Ñandu', 'foo'), True) self.assertEqual(self.redis.get(u'Ñandu'), b'foo') def test_get_does_not_exist(self): self.assertEqual(self.redis.get('foo'), None) def test_get_with_non_str_keys(self): self.assertEqual(self.redis.set('2', 'bar'), True) self.assertEqual(self.redis.get(2), b'bar') def test_get_invalid_type(self): self.assertEqual(self.redis.hset('foo', 'key', 'value'), 1) with self.assertRaises(redis.ResponseError): self.redis.get('foo') def test_set_non_str_keys(self): self.assertEqual(self.redis.set(2, 'bar'), True) self.assertEqual(self.redis.get(2), b'bar') self.assertEqual(self.redis.get('2'), b'bar') def test_getbit(self): self.redis.setbit('foo', 3, 1) self.assertEqual(self.redis.getbit('foo', 0), 0) self.assertEqual(self.redis.getbit('foo', 1), 0) self.assertEqual(self.redis.getbit('foo', 2), 0) self.assertEqual(self.redis.getbit('foo', 3), 1) self.assertEqual(self.redis.getbit('foo', 4), 0) self.assertEqual(self.redis.getbit('foo', 100), 0) def test_multiple_bits_set(self): self.redis.setbit('foo', 1, 1) self.redis.setbit('foo', 3, 1) self.redis.setbit('foo', 5, 1) self.assertEqual(self.redis.getbit('foo', 0), 0) self.assertEqual(self.redis.getbit('foo', 1), 1) self.assertEqual(self.redis.getbit('foo', 2), 0) self.assertEqual(self.redis.getbit('foo', 3), 1) self.assertEqual(self.redis.getbit('foo', 4), 0) self.assertEqual(self.redis.getbit('foo', 5), 1) self.assertEqual(self.redis.getbit('foo', 6), 0) def test_unset_bits(self): self.redis.setbit('foo', 1, 1) self.redis.setbit('foo', 2, 0) self.redis.setbit('foo', 3, 1) self.assertEqual(self.redis.getbit('foo', 1), 1) self.redis.setbit('foo', 1, 0) self.assertEqual(self.redis.getbit('foo', 1), 0) self.redis.setbit('foo', 3, 0) self.assertEqual(self.redis.getbit('foo', 3), 0) def test_setbits_and_getkeys(self): # The bit operations and the get commands # should play nicely with each other. self.redis.setbit('foo', 1, 1) self.assertEqual(self.redis.get('foo'), b'@') self.redis.setbit('foo', 2, 1) self.assertEqual(self.redis.get('foo'), b'`') self.redis.setbit('foo', 3, 1) self.assertEqual(self.redis.get('foo'), b'p') self.redis.setbit('foo', 9, 1) self.assertEqual(self.redis.get('foo'), b'p@') self.redis.setbit('foo', 54, 1) self.assertEqual(self.redis.get('foo'), b'p@\x00\x00\x00\x00\x02') def test_bitcount(self): self.redis.delete('foo') self.assertEqual(self.redis.bitcount('foo'), 0) self.redis.setbit('foo', 1, 1) self.assertEqual(self.redis.bitcount('foo'), 1) self.redis.setbit('foo', 8, 1) self.assertEqual(self.redis.bitcount('foo'), 2) self.assertEqual(self.redis.bitcount('foo', 1, 1), 1) self.redis.setbit('foo', 57, 1) self.assertEqual(self.redis.bitcount('foo'), 3) self.redis.set('foo', ' ') self.assertEqual(self.redis.bitcount('foo'), 1) def test_getset_not_exist(self): val = self.redis.getset('foo', 'bar') self.assertEqual(val, None) self.assertEqual(self.redis.get('foo'), b'bar') def test_getset_exists(self): self.redis.set('foo', 'bar') val = self.redis.getset('foo', b'baz') self.assertEqual(val, b'bar') val = self.redis.getset('foo', b'baz2') self.assertEqual(val, b'baz') def test_setitem_getitem(self): self.assertEqual(self.redis.keys(), []) self.redis['foo'] = 'bar' self.assertEqual(self.redis['foo'], b'bar') def test_strlen(self): self.redis['foo'] = 'bar' self.assertEqual(self.redis.strlen('foo'), 3) self.assertEqual(self.redis.strlen('noexists'), 0) def test_substr(self): self.redis['foo'] = 'one_two_three' self.assertEqual(self.redis.substr('foo', 0), b'one_two_three') self.assertEqual(self.redis.substr('foo', 0, 2), b'one') self.assertEqual(self.redis.substr('foo', 4, 6), b'two') self.assertEqual(self.redis.substr('foo', -5), b'three') def test_substr_noexist_key(self): self.assertEqual(self.redis.substr('foo', 0), b'') self.assertEqual(self.redis.substr('foo', 10), b'') self.assertEqual(self.redis.substr('foo', -5, -1), b'') def test_append(self): self.assertTrue(self.redis.set('foo', 'bar')) self.assertEqual(self.redis.append('foo', 'baz'), 6) self.assertEqual(self.redis.get('foo'), b'barbaz') def test_append_with_no_preexisting_key(self): self.assertEqual(self.redis.append('foo', 'bar'), 3) self.assertEqual(self.redis.get('foo'), b'bar') def test_incr_with_no_preexisting_key(self): self.assertEqual(self.redis.incr('foo'), 1) self.assertEqual(self.redis.incr('bar', 2), 2) def test_incr_by(self): self.assertEqual(self.redis.incrby('foo'), 1) self.assertEqual(self.redis.incrby('bar', 2), 2) def test_incr_preexisting_key(self): self.redis.set('foo', 15) self.assertEqual(self.redis.incr('foo', 5), 20) self.assertEqual(self.redis.get('foo'), b'20') def test_incr_bad_type(self): self.redis.set('foo', 'bar') with self.assertRaises(redis.ResponseError): self.redis.incr('foo', 15) def test_incr_with_float(self): with self.assertRaises(redis.ResponseError): self.redis.incr('foo', 2.0) def test_incr_followed_by_mget(self): self.redis.set('foo', 15) self.assertEqual(self.redis.incr('foo', 5), 20) self.assertEqual(self.redis.get('foo'), b'20') def test_incr_followed_by_mget_returns_strings(self): self.redis.incr('foo', 1) self.assertEqual(self.redis.mget(['foo']), [b'1']) def test_incrbyfloat(self): self.redis.set('foo', 0) self.assertEqual(self.redis.incrbyfloat('foo', 1.0), 1.0) self.assertEqual(self.redis.incrbyfloat('foo', 1.0), 2.0) def test_incrbyfloat_with_noexist(self): self.assertEqual(self.redis.incrbyfloat('foo', 1.0), 1.0) self.assertEqual(self.redis.incrbyfloat('foo', 1.0), 2.0) def test_incrbyfloat_bad_type(self): self.redis.set('foo', 'bar') with self.assertRaisesRegexp(redis.ResponseError, 'not a valid float'): self.redis.incrbyfloat('foo', 1.0) def test_decr(self): self.redis.set('foo', 10) self.assertEqual(self.redis.decr('foo'), 9) self.assertEqual(self.redis.get('foo'), b'9') def test_decr_newkey(self): self.redis.decr('foo') self.assertEqual(self.redis.get('foo'), b'-1') def test_decr_badtype(self): self.redis.set('foo', 'bar') with self.assertRaises(redis.ResponseError): self.redis.decr('foo', 15) def test_exists(self): self.assertFalse('foo' in self.redis) self.redis.set('foo', 'bar') self.assertTrue('foo' in self.redis) def test_contains(self): self.assertFalse(self.redis.exists('foo')) self.redis.set('foo', 'bar') self.assertTrue(self.redis.exists('foo')) def test_rename(self): self.redis.set('foo', 'unique value') self.assertTrue(self.redis.rename('foo', 'bar')) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.get('bar'), b'unique value') def test_rename_nonexistent_key(self): with self.assertRaises(redis.ResponseError): self.redis.rename('foo', 'bar') def test_renamenx_doesnt_exist(self): self.redis.set('foo', 'unique value') self.assertTrue(self.redis.renamenx('foo', 'bar')) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.get('bar'), b'unique value') def test_rename_does_exist(self): self.redis.set('foo', 'unique value') self.redis.set('bar', 'unique value2') self.assertFalse(self.redis.renamenx('foo', 'bar')) self.assertEqual(self.redis.get('foo'), b'unique value') self.assertEqual(self.redis.get('bar'), b'unique value2') def test_mget(self): self.redis.set('foo', 'one') self.redis.set('bar', 'two') self.assertEqual(self.redis.mget(['foo', 'bar']), [b'one', b'two']) self.assertEqual(self.redis.mget(['foo', 'bar', 'baz']), [b'one', b'two', None]) self.assertEqual(self.redis.mget('foo', 'bar'), [b'one', b'two']) self.assertEqual(self.redis.mget('foo', 'bar', None), [b'one', b'two', None]) def test_mgset_with_no_keys_raises_error(self): with self.assertRaisesRegexp( redis.ResponseError, 'wrong number of arguments'): self.redis.mget([]) def test_mset_with_no_keys_raises_error(self): with self.assertRaisesRegexp( redis.RedisError, 'MSET requires'): self.redis.mset([]) def test_mset(self): self.assertEqual(self.redis.mset({'foo': 'one', 'bar': 'two'}), True) self.assertEqual(self.redis.mset({'foo': 'one', 'bar': 'two'}), True) self.assertEqual(self.redis.mget('foo', 'bar'), [b'one', b'two']) def test_mset_accepts_kwargs(self): self.assertEqual( self.redis.mset(foo='one', bar='two'), True) self.assertEqual( self.redis.mset(foo='one', baz='three'), True) self.assertEqual(self.redis.mget('foo', 'bar', 'baz'), [b'one', b'two', b'three']) def test_msetnx(self): self.assertEqual(self.redis.msetnx({'foo': 'one', 'bar': 'two'}), True) self.assertEqual(self.redis.msetnx({'bar': 'two', 'baz': 'three'}), False) self.assertEqual(self.redis.mget('foo', 'bar', 'baz'), [b'one', b'two', None]) def test_setex(self): self.assertEqual(self.redis.setex('foo', 100, 'bar'), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_setex_using_timedelta(self): self.assertEqual( self.redis.setex('foo', timedelta(seconds=100), 'bar'), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_setex_using_float(self): self.assertRaisesRegexp( redis.ResponseError, 'integer', self.redis.setex, 'foo', 1.2, 'bar') def test_set_ex(self): self.assertEqual(self.redis.set('foo', 'bar', ex=100), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_set_ex_using_timedelta(self): self.assertEqual( self.redis.set('foo', 'bar', ex=timedelta(seconds=100)), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_set_px(self): self.assertEqual(self.redis.set('foo', 'bar', px=100), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_set_px_using_timedelta(self): self.assertEqual( self.redis.set('foo', 'bar', px=timedelta(milliseconds=100)), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_set_raises_wrong_ex(self): with self.assertRaises(ResponseError): self.redis.set('foo', 'bar', ex=-100) def test_set_using_timedelta_raises_wrong_ex(self): with self.assertRaises(ResponseError): self.redis.set('foo', 'bar', ex=timedelta(seconds=-100)) def test_set_raises_wrong_px(self): with self.assertRaises(ResponseError): self.redis.set('foo', 'bar', px=-100) def test_set_using_timedelta_raises_wrong_px(self): with self.assertRaises(ResponseError): self.redis.set('foo', 'bar', px=timedelta(milliseconds=-100)) def test_setex_raises_wrong_ex(self): with self.assertRaises(ResponseError): self.redis.setex('foo', -100, 'bar') def test_setex_using_timedelta_raises_wrong_ex(self): with self.assertRaises(ResponseError): self.redis.setex('foo', timedelta(seconds=-100), 'bar') def test_setnx(self): self.assertEqual(self.redis.setnx('foo', 'bar'), True) self.assertEqual(self.redis.get('foo'), b'bar') self.assertEqual(self.redis.setnx('foo', 'baz'), False) self.assertEqual(self.redis.get('foo'), b'bar') def test_delete(self): self.redis['foo'] = 'bar' self.assertEqual(self.redis.delete('foo'), True) self.assertEqual(self.redis.get('foo'), None) def test_echo(self): self.assertEqual(self.redis.echo(b'hello'), b'hello') self.assertEqual(self.redis.echo('hello'), b'hello') @attr('slow') def test_delete_expire(self): self.redis.set("foo", "bar", ex=1) self.redis.delete("foo") self.redis.set("foo", "bar") sleep(2) self.assertEqual(self.redis.get("foo"), b'bar') def test_delete_multiple(self): self.redis['one'] = 'one' self.redis['two'] = 'two' self.redis['three'] = 'three' # Since redis>=2.7.6 returns number of deleted items. self.assertEqual(self.redis.delete('one', 'two'), 2) self.assertEqual(self.redis.get('one'), None) self.assertEqual(self.redis.get('two'), None) self.assertEqual(self.redis.get('three'), b'three') self.assertEqual(self.redis.delete('one', 'two'), False) # If any keys are deleted, True is returned. self.assertEqual(self.redis.delete('two', 'three'), True) self.assertEqual(self.redis.get('three'), None) def test_delete_nonexistent_key(self): self.assertEqual(self.redis.delete('foo'), False) # Tests for the list type. def test_rpush_then_lrange_with_nested_list1(self): self.assertEqual(self.redis.rpush('foo', [long(12345), long(6789)]), 1) self.assertEqual(self.redis.rpush('foo', [long(54321), long(9876)]), 2) self.assertEqual(self.redis.lrange( 'foo', 0, -1), ['[12345L, 6789L]', '[54321L, 9876L]'] if PY2 else [b'[12345, 6789]', b'[54321, 9876]']) self.redis.flushall() def test_rpush_then_lrange_with_nested_list2(self): self.assertEqual(self.redis.rpush('foo', [long(12345), 'banana']), 1) self.assertEqual(self.redis.rpush('foo', [long(54321), 'elephant']), 2) self.assertEqual(self.redis.lrange( 'foo', 0, -1), ['[12345L, \'banana\']', '[54321L, \'elephant\']'] if PY2 else [b'[12345, \'banana\']', b'[54321, \'elephant\']']) self.redis.flushall() def test_rpush_then_lrange_with_nested_list3(self): self.assertEqual(self.redis.rpush('foo', [long(12345), []]), 1) self.assertEqual(self.redis.rpush('foo', [long(54321), []]), 2) self.assertEqual(self.redis.lrange( 'foo', 0, -1), ['[12345L, []]', '[54321L, []]'] if PY2 else [b'[12345, []]', b'[54321, []]']) self.redis.flushall() def test_lpush_then_lrange_all(self): self.assertEqual(self.redis.lpush('foo', 'bar'), 1) self.assertEqual(self.redis.lpush('foo', 'baz'), 2) self.assertEqual(self.redis.lpush('foo', 'bam', 'buzz'), 4) self.assertEqual(self.redis.lrange('foo', 0, -1), [b'buzz', b'bam', b'baz', b'bar']) def test_lpush_then_lrange_portion(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'two') self.redis.lpush('foo', 'three') self.redis.lpush('foo', 'four') self.assertEqual(self.redis.lrange('foo', 0, 2), [b'four', b'three', b'two']) self.assertEqual(self.redis.lrange('foo', 0, 3), [b'four', b'three', b'two', b'one']) def test_lpush_key_does_not_exist(self): self.assertEqual(self.redis.lrange('foo', 0, -1), []) def test_lpush_with_nonstr_key(self): self.redis.lpush(1, 'one') self.redis.lpush(1, 'two') self.redis.lpush(1, 'three') self.assertEqual(self.redis.lrange(1, 0, 2), [b'three', b'two', b'one']) self.assertEqual(self.redis.lrange('1', 0, 2), [b'three', b'two', b'one']) def test_llen(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'two') self.redis.lpush('foo', 'three') self.assertEqual(self.redis.llen('foo'), 3) def test_llen_no_exist(self): self.assertEqual(self.redis.llen('foo'), 0) def test_lrem_postitive_count(self): self.redis.lpush('foo', 'same') self.redis.lpush('foo', 'same') self.redis.lpush('foo', 'different') self.redis.lrem('foo', 2, 'same') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'different']) def test_lrem_negative_count(self): self.redis.lpush('foo', 'removeme') self.redis.lpush('foo', 'three') self.redis.lpush('foo', 'two') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'removeme') self.redis.lrem('foo', -1, 'removeme') # Should remove it from the end of the list, # leaving the 'removeme' from the front of the list alone. self.assertEqual(self.redis.lrange('foo', 0, -1), [b'removeme', b'one', b'two', b'three']) def test_lrem_zero_count(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lrem('foo', 0, 'one') self.assertEqual(self.redis.lrange('foo', 0, -1), []) def test_lrem_default_value(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lrem('foo', 0, 'one') self.assertEqual(self.redis.lrange('foo', 0, -1), []) def test_lrem_does_not_exist(self): self.redis.lpush('foo', 'one') self.redis.lrem('foo', 0, 'one') # These should be noops. self.redis.lrem('foo', -2, 'one') self.redis.lrem('foo', 2, 'one') def test_lrem_return_value(self): self.redis.lpush('foo', 'one') count = self.redis.lrem('foo', 0, 'one') self.assertEqual(count, 1) self.assertEqual(self.redis.lrem('foo', 0, 'one'), 0) def test_rpush(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.redis.rpush('foo', 'three') self.redis.rpush('foo', 'four', 'five') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'one', b'two', b'three', b'four', b'five']) def test_lpop(self): self.assertEqual(self.redis.rpush('foo', 'one'), 1) self.assertEqual(self.redis.rpush('foo', 'two'), 2) self.assertEqual(self.redis.rpush('foo', 'three'), 3) self.assertEqual(self.redis.lpop('foo'), b'one') self.assertEqual(self.redis.lpop('foo'), b'two') self.assertEqual(self.redis.lpop('foo'), b'three') def test_lpop_empty_list(self): self.redis.rpush('foo', 'one') self.redis.lpop('foo') self.assertEqual(self.redis.lpop('foo'), None) # Verify what happens if we try to pop from a key # we've never seen before. self.assertEqual(self.redis.lpop('noexists'), None) def test_lset(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.redis.rpush('foo', 'three') self.redis.lset('foo', 0, 'four') self.redis.lset('foo', -2, 'five') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'four', b'five', b'three']) def test_lset_index_out_of_range(self): self.redis.rpush('foo', 'one') with self.assertRaises(redis.ResponseError): self.redis.lset('foo', 3, 'three') def test_rpushx(self): self.redis.rpush('foo', 'one') self.redis.rpushx('foo', 'two') self.redis.rpushx('bar', 'three') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'one', b'two']) self.assertEqual(self.redis.lrange('bar', 0, -1), []) def test_ltrim(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.redis.rpush('foo', 'three') self.redis.rpush('foo', 'four') self.assertTrue(self.redis.ltrim('foo', 1, 3)) self.assertEqual(self.redis.lrange('foo', 0, -1), [b'two', b'three', b'four']) self.assertTrue(self.redis.ltrim('foo', 1, -1)) self.assertEqual(self.redis.lrange('foo', 0, -1), [b'three', b'four']) def test_ltrim_with_non_existent_key(self): self.assertTrue(self.redis.ltrim('foo', 0, -1)) def test_lindex(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.assertEqual(self.redis.lindex('foo', 0), b'one') self.assertEqual(self.redis.lindex('foo', 4), None) self.assertEqual(self.redis.lindex('bar', 4), None) def test_lpushx(self): self.redis.lpush('foo', 'two') self.redis.lpushx('foo', 'one') self.redis.lpushx('bar', 'one') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'one', b'two']) self.assertEqual(self.redis.lrange('bar', 0, -1), []) def test_rpop(self): self.assertEqual(self.redis.rpop('foo'), None) self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.assertEqual(self.redis.rpop('foo'), b'two') self.assertEqual(self.redis.rpop('foo'), b'one') self.assertEqual(self.redis.rpop('foo'), None) def test_linsert(self): self.redis.rpush('foo', 'hello') self.redis.rpush('foo', 'world') self.redis.linsert('foo', 'before', 'world', 'there') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'hello', b'there', b'world']) def test_rpoplpush(self): self.assertEqual(self.redis.rpoplpush('foo', 'bar'), None) self.assertEqual(self.redis.lpop('bar'), None) self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.redis.rpush('bar', 'one') self.assertEqual(self.redis.rpoplpush('foo', 'bar'), b'two') self.assertEqual(self.redis.lrange('foo', 0, -1), [b'one']) self.assertEqual(self.redis.lrange('bar', 0, -1), [b'two', b'one']) # Catch instances where we store bytes and strings inconsistently # and thus bar = ['two', b'one'] self.assertEqual(self.redis.lrem('bar', -1, 'two'), 1) def test_rpoplpush_to_nonexistent_destination(self): self.redis.rpush('foo', 'one') self.assertEqual(self.redis.rpoplpush('foo', 'bar'), b'one') self.assertEqual(self.redis.rpop('bar'), b'one') def test_blpop_single_list(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.redis.rpush('foo', 'three') self.assertEqual(self.redis.blpop(['foo'], timeout=1), (b'foo', b'one')) def test_blpop_test_multiple_lists(self): self.redis.rpush('baz', 'zero') self.assertEqual(self.redis.blpop(['foo', 'baz'], timeout=1), (b'baz', b'zero')) self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') # bar has nothing, so the returned value should come # from foo. self.assertEqual(self.redis.blpop(['bar', 'foo'], timeout=1), (b'foo', b'one')) self.redis.rpush('bar', 'three') # bar now has something, so the returned value should come # from bar. self.assertEqual(self.redis.blpop(['bar', 'foo'], timeout=1), (b'bar', b'three')) self.assertEqual(self.redis.blpop(['bar', 'foo'], timeout=1), (b'foo', b'two')) def test_blpop_allow_single_key(self): # blpop converts single key arguments to a one element list. self.redis.rpush('foo', 'one') self.assertEqual(self.redis.blpop('foo', timeout=1), (b'foo', b'one')) def test_brpop_test_multiple_lists(self): self.redis.rpush('baz', 'zero') self.assertEqual(self.redis.brpop(['foo', 'baz'], timeout=1), (b'baz', b'zero')) self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.assertEqual(self.redis.brpop(['bar', 'foo'], timeout=1), (b'foo', b'two')) def test_brpop_single_key(self): self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.assertEqual(self.redis.brpop('foo', timeout=1), (b'foo', b'two')) def test_brpoplpush_multi_keys(self): self.assertEqual(self.redis.lpop('bar'), None) self.redis.rpush('foo', 'one') self.redis.rpush('foo', 'two') self.assertEqual(self.redis.brpoplpush('foo', 'bar', timeout=1), b'two') self.assertEqual(self.redis.lrange('bar', 0, -1), [b'two']) # Catch instances where we store bytes and strings inconsistently # and thus bar = ['two'] self.assertEqual(self.redis.lrem('bar', -1, 'two'), 1) @attr('slow') def test_blocking_operations_when_empty(self): self.assertEqual(self.redis.blpop(['foo'], timeout=1), None) self.assertEqual(self.redis.blpop(['bar', 'foo'], timeout=1), None) self.assertEqual(self.redis.brpop('foo', timeout=1), None) self.assertEqual(self.redis.brpoplpush('foo', 'bar', timeout=1), None) # Tests for the hash type. def test_hset_then_hget(self): self.assertEqual(self.redis.hset('foo', 'key', 'value'), 1) self.assertEqual(self.redis.hget('foo', 'key'), b'value') def test_hset_update(self): self.assertEqual(self.redis.hset('foo', 'key', 'value'), 1) self.assertEqual(self.redis.hset('foo', 'key', 'value'), 0) def test_hgetall(self): self.assertEqual(self.redis.hset('foo', 'k1', 'v1'), 1) self.assertEqual(self.redis.hset('foo', 'k2', 'v2'), 1) self.assertEqual(self.redis.hset('foo', 'k3', 'v3'), 1) self.assertEqual(self.redis.hgetall('foo'), {b'k1': b'v1', b'k2': b'v2', b'k3': b'v3'}) def test_hgetall_with_tuples(self): self.assertEqual(self.redis.hset('foo', (1, 2), (1, 2, 3)), 1) self.assertEqual(self.redis.hgetall('foo'), {b'(1, 2)': b'(1, 2, 3)'}) def test_hgetall_empty_key(self): self.assertEqual(self.redis.hgetall('foo'), {}) def test_hexists(self): self.redis.hset('foo', 'bar', 'v1') self.assertEqual(self.redis.hexists('foo', 'bar'), 1) self.assertEqual(self.redis.hexists('foo', 'baz'), 0) self.assertEqual(self.redis.hexists('bar', 'bar'), 0) def test_hkeys(self): self.redis.hset('foo', 'k1', 'v1') self.redis.hset('foo', 'k2', 'v2') self.assertEqual(set(self.redis.hkeys('foo')), set([b'k1', b'k2'])) self.assertEqual(set(self.redis.hkeys('bar')), set([])) def test_hlen(self): self.redis.hset('foo', 'k1', 'v1') self.redis.hset('foo', 'k2', 'v2') self.assertEqual(self.redis.hlen('foo'), 2) def test_hvals(self): self.redis.hset('foo', 'k1', 'v1') self.redis.hset('foo', 'k2', 'v2') self.assertEqual(set(self.redis.hvals('foo')), set([b'v1', b'v2'])) self.assertEqual(set(self.redis.hvals('bar')), set([])) def test_hmget(self): self.redis.hset('foo', 'k1', 'v1') self.redis.hset('foo', 'k2', 'v2') self.redis.hset('foo', 'k3', 'v3') # Normal case. self.assertEqual(self.redis.hmget('foo', ['k1', 'k3']), [b'v1', b'v3']) self.assertEqual(self.redis.hmget('foo', 'k1', 'k3'), [b'v1', b'v3']) # Key does not exist. self.assertEqual(self.redis.hmget('bar', ['k1', 'k3']), [None, None]) self.assertEqual(self.redis.hmget('bar', 'k1', 'k3'), [None, None]) # Some keys in the hash do not exist. self.assertEqual(self.redis.hmget('foo', ['k1', 'k500']), [b'v1', None]) self.assertEqual(self.redis.hmget('foo', 'k1', 'k500'), [b'v1', None]) def test_hdel(self): self.redis.hset('foo', 'k1', 'v1') self.redis.hset('foo', 'k2', 'v2') self.redis.hset('foo', 'k3', 'v3') self.assertEqual(self.redis.hget('foo', 'k1'), b'v1') self.assertEqual(self.redis.hdel('foo', 'k1'), True) self.assertEqual(self.redis.hget('foo', 'k1'), None) self.assertEqual(self.redis.hdel('foo', 'k1'), False) # Since redis>=2.7.6 returns number of deleted items. self.assertEqual(self.redis.hdel('foo', 'k2', 'k3'), 2) self.assertEqual(self.redis.hget('foo', 'k2'), None) self.assertEqual(self.redis.hget('foo', 'k3'), None) self.assertEqual(self.redis.hdel('foo', 'k2', 'k3'), False) def test_hincrby(self): self.redis.hset('foo', 'counter', 0) self.assertEqual(self.redis.hincrby('foo', 'counter'), 1) self.assertEqual(self.redis.hincrby('foo', 'counter'), 2) self.assertEqual(self.redis.hincrby('foo', 'counter'), 3) def test_hincrby_with_no_starting_value(self): self.assertEqual(self.redis.hincrby('foo', 'counter'), 1) self.assertEqual(self.redis.hincrby('foo', 'counter'), 2) self.assertEqual(self.redis.hincrby('foo', 'counter'), 3) def test_hincrby_with_range_param(self): self.assertEqual(self.redis.hincrby('foo', 'counter', 2), 2) self.assertEqual(self.redis.hincrby('foo', 'counter', 2), 4) self.assertEqual(self.redis.hincrby('foo', 'counter', 2), 6) def test_hincrbyfloat(self): self.redis.hset('foo', 'counter', 0.0) self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 1.0) self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 2.0) self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 3.0) def test_hincrbyfloat_with_no_starting_value(self): self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 1.0) self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 2.0) self.assertEqual(self.redis.hincrbyfloat('foo', 'counter'), 3.0) def test_hincrbyfloat_with_range_param(self): self.assertAlmostEqual( self.redis.hincrbyfloat('foo', 'counter', 0.1), 0.1) self.assertAlmostEqual( self.redis.hincrbyfloat('foo', 'counter', 0.1), 0.2) self.assertAlmostEqual( self.redis.hincrbyfloat('foo', 'counter', 0.1), 0.3) def test_hincrbyfloat_on_non_float_value_raises_error(self): self.redis.hset('foo', 'counter', 'cat') with self.assertRaises(redis.ResponseError): self.redis.hincrbyfloat('foo', 'counter') def test_hincrbyfloat_with_non_float_amount_raises_error(self): with self.assertRaises(redis.ResponseError): self.redis.hincrbyfloat('foo', 'counter', 'cat') def test_hsetnx(self): self.assertEqual(self.redis.hsetnx('foo', 'newkey', 'v1'), True) self.assertEqual(self.redis.hsetnx('foo', 'newkey', 'v1'), False) self.assertEqual(self.redis.hget('foo', 'newkey'), b'v1') def test_hmsetset_empty_raises_error(self): with self.assertRaises(redis.DataError): self.redis.hmset('foo', {}) def test_hmsetset(self): self.redis.hset('foo', 'k1', 'v1') self.assertEqual(self.redis.hmset('foo', {'k2': 'v2', 'k3': 'v3'}), True) def test_hmset_convert_values(self): self.redis.hmset('foo', {'k1': True, 'k2': 1}) self.assertEqual( self.redis.hgetall('foo'), {b'k1': b'True', b'k2': b'1'}) def test_hmset_does_not_mutate_input_params(self): original = {'key': [123, 456]} self.redis.hmset('foo', original) self.assertEqual(original, {'key': [123, 456]}) def test_sadd(self): self.assertEqual(self.redis.sadd('foo', 'member1'), 1) self.assertEqual(self.redis.sadd('foo', 'member1'), 0) self.assertEqual(self.redis.smembers('foo'), set([b'member1'])) self.assertEqual(self.redis.sadd('foo', 'member2', 'member3'), 2) self.assertEqual(self.redis.smembers('foo'), set([b'member1', b'member2', b'member3'])) self.assertEqual(self.redis.sadd('foo', 'member3', 'member4'), 1) self.assertEqual(self.redis.smembers('foo'), set([b'member1', b'member2', b'member3', b'member4'])) def test_sadd_as_str_type(self): self.assertEqual(self.redis.sadd('foo', *range(3)), 3) self.assertEqual(self.redis.smembers('foo'), set([b'0', b'1', b'2'])) def test_scan_single(self): self.redis.set('foo1', 'bar1') self.assertEqual(self.redis.scan(match="foo*"), (0, [b'foo1'])) def test_scan_iter_single_page(self): self.redis.set('foo1', 'bar1') self.redis.set('foo2', 'bar2') self.assertEqual(set(self.redis.scan_iter(match="foo*")), set([b'foo1', b'foo2'])) def test_scan_iter_multiple_pages(self): all_keys = key_val_dict(size=100) self.assertTrue( all(self.redis.set(k, v) for k, v in all_keys.items())) self.assertEqual( set(self.redis.scan_iter()), set(all_keys)) def test_scan_iter_multiple_pages_with_match(self): all_keys = key_val_dict(size=100) self.assertTrue( all(self.redis.set(k, v) for k, v in all_keys.items())) # Now add a few keys that don't match the key: pattern. self.redis.set('otherkey', 'foo') self.redis.set('andanother', 'bar') actual = set(self.redis.scan_iter(match='key:*')) self.assertEqual(actual, set(all_keys)) def test_scan_multiple_pages_with_count_arg(self): all_keys = key_val_dict(size=100) self.assertTrue( all(self.redis.set(k, v) for k, v in all_keys.items())) self.assertEqual( set(self.redis.scan_iter(count=1000)), set(all_keys)) def test_scan_all_in_single_call(self): all_keys = key_val_dict(size=100) self.assertTrue( all(self.redis.set(k, v) for k, v in all_keys.items())) # Specify way more than the 100 keys we've added. actual = self.redis.scan(count=1000) self.assertEqual(set(actual[1]), set(all_keys)) self.assertEqual(actual[0], 0) def test_scard(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('foo', 'member2') self.assertEqual(self.redis.scard('foo'), 2) def test_sdiff(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sdiff('foo', 'bar'), set([b'member1'])) # Original sets shouldn't be modified. self.assertEqual(self.redis.smembers('foo'), set([b'member1', b'member2'])) self.assertEqual(self.redis.smembers('bar'), set([b'member2', b'member3'])) def test_sdiff_one_key(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.assertEqual(self.redis.sdiff('foo'), set([b'member1', b'member2'])) def test_sdiff_empty(self): self.assertEqual(self.redis.sdiff('foo'), set()) def test_sdiffstore(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sdiffstore('baz', 'foo', 'bar'), 1) # Catch instances where we store bytes and strings inconsistently # and thus baz = {'member1', b'member1'} self.redis.sadd('baz', 'member1') self.assertEqual(self.redis.scard('baz'), 1) def test_setrange(self): self.redis.set('foo', 'test') self.assertEqual(self.redis.setrange('foo', 1, 'aste'), 5) self.assertEqual(self.redis.get('foo'), b'taste') self.redis.set('foo', 'test') self.assertEqual(self.redis.setrange('foo', 1, 'a'), 4) self.assertEqual(self.redis.get('foo'), b'tast') self.assertEqual(self.redis.setrange('bar', 2, 'test'), 6) self.assertEqual(self.redis.get('bar'), b'\x00\x00test') def test_sinter(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sinter('foo', 'bar'), set([b'member2'])) self.assertEqual(self.redis.sinter('foo'), set([b'member1', b'member2'])) def test_sinter_bytes_keys(self): foo = os.urandom(10) bar = os.urandom(10) self.redis.sadd(foo, 'member1') self.redis.sadd(foo, 'member2') self.redis.sadd(bar, 'member2') self.redis.sadd(bar, 'member3') self.assertEqual(self.redis.sinter(foo, bar), set([b'member2'])) self.assertEqual(self.redis.sinter(foo), set([b'member1', b'member2'])) def test_sinterstore(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sinterstore('baz', 'foo', 'bar'), 1) # Catch instances where we store bytes and strings inconsistently # and thus baz = {'member2', b'member2'} self.redis.sadd('baz', 'member2') self.assertEqual(self.redis.scard('baz'), 1) def test_sismember(self): self.assertEqual(self.redis.sismember('foo', 'member1'), False) self.redis.sadd('foo', 'member1') self.assertEqual(self.redis.sismember('foo', 'member1'), True) def test_smembers(self): self.assertEqual(self.redis.smembers('foo'), set()) def test_smove(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.assertEqual(self.redis.smove('foo', 'bar', 'member1'), True) self.assertEqual(self.redis.smembers('bar'), set([b'member1'])) def test_smove_non_existent_key(self): self.assertEqual(self.redis.smove('foo', 'bar', 'member1'), False) def test_spop(self): # This is tricky because it pops a random element. self.redis.sadd('foo', 'member1') self.assertEqual(self.redis.spop('foo'), b'member1') self.assertEqual(self.redis.spop('foo'), None) def test_srandmember(self): self.redis.sadd('foo', 'member1') self.assertEqual(self.redis.srandmember('foo'), b'member1') # Shouldn't be removed from the set. self.assertEqual(self.redis.srandmember('foo'), b'member1') def test_srandmember_number(self): """srandmember works with the number argument.""" self.assertEqual(self.redis.srandmember('foo', 2), []) self.redis.sadd('foo', b'member1') self.assertEqual(self.redis.srandmember('foo', 2), [b'member1']) self.redis.sadd('foo', b'member2') self.assertEqual(set(self.redis.srandmember('foo', 2)), set([b'member1', b'member2'])) self.redis.sadd('foo', b'member3') res = self.redis.srandmember('foo', 2) self.assertEqual(len(res), 2) if self.decode_responses: superset = set(['member1', 'member2', 'member3']) else: superset = set([b'member1', b'member2', b'member3']) for e in res: self.assertIn(e, superset) def test_srem(self): self.redis.sadd('foo', 'member1', 'member2', 'member3', 'member4') self.assertEqual(self.redis.smembers('foo'), set([b'member1', b'member2', b'member3', b'member4'])) self.assertEqual(self.redis.srem('foo', 'member1'), True) self.assertEqual(self.redis.smembers('foo'), set([b'member2', b'member3', b'member4'])) self.assertEqual(self.redis.srem('foo', 'member1'), False) # Since redis>=2.7.6 returns number of deleted items. self.assertEqual(self.redis.srem('foo', 'member2', 'member3'), 2) self.assertEqual(self.redis.smembers('foo'), set([b'member4'])) self.assertEqual(self.redis.srem('foo', 'member3', 'member4'), True) self.assertEqual(self.redis.smembers('foo'), set([])) self.assertEqual(self.redis.srem('foo', 'member3', 'member4'), False) def test_sunion(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sunion('foo', 'bar'), set([b'member1', b'member2', b'member3'])) def test_sunionstore(self): self.redis.sadd('foo', 'member1') self.redis.sadd('foo', 'member2') self.redis.sadd('bar', 'member2') self.redis.sadd('bar', 'member3') self.assertEqual(self.redis.sunionstore('baz', 'foo', 'bar'), 3) self.assertEqual(self.redis.smembers('baz'), set([b'member1', b'member2', b'member3'])) # Catch instances where we store bytes and strings inconsistently # and thus baz = {b'member1', b'member2', b'member3', 'member3'} self.redis.sadd('baz', 'member3') self.assertEqual(self.redis.scard('baz'), 3) def test_zadd(self): self.redis.zadd('foo', four=4) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zadd('foo', 2, 'two', 1, 'one', zero=0), 3) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'one', b'two', b'three', b'four']) self.assertEqual(self.redis.zadd('foo', 7, 'zero', one=1, five=5), 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'one', b'two', b'three', b'four', b'five', b'zero']) def test_zadd_uses_str(self): self.redis.zadd('foo', 12345, (1, 2, 3)) self.assertEqual(self.redis.zrange('foo', 0, 0), [b'(1, 2, 3)']) def test_zadd_errors(self): # The args are backwards, it should be 2, "two", so we # expect an exception to be raised. with self.assertRaises(redis.ResponseError): self.redis.zadd('foo', 'two', 2) with self.assertRaises(redis.ResponseError): self.redis.zadd('foo', two='two') # It's expected an equal number of values and scores with self.assertRaises(redis.RedisError): self.redis.zadd('foo', 'two') def test_zadd_multiple(self): self.redis.zadd('foo', 1, 'one', 2, 'two') self.assertEqual(self.redis.zrange('foo', 0, 0), [b'one']) self.assertEqual(self.redis.zrange('foo', 1, 1), [b'two']) def test_zrange_same_score(self): self.redis.zadd('foo', two_a=2) self.redis.zadd('foo', two_b=2) self.redis.zadd('foo', two_c=2) self.redis.zadd('foo', two_d=2) self.redis.zadd('foo', two_e=2) self.assertEqual(self.redis.zrange('foo', 2, 3), [b'two_c', b'two_d']) def test_zcard(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.assertEqual(self.redis.zcard('foo'), 2) def test_zcard_non_existent_key(self): self.assertEqual(self.redis.zcard('foo'), 0) def test_zcount(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', three=2) self.redis.zadd('foo', five=5) self.assertEqual(self.redis.zcount('foo', 2, 4), 1) self.assertEqual(self.redis.zcount('foo', 1, 4), 2) self.assertEqual(self.redis.zcount('foo', 0, 5), 3) self.assertEqual(self.redis.zcount('foo', 4, '+inf'), 1) self.assertEqual(self.redis.zcount('foo', '-inf', 4), 2) self.assertEqual(self.redis.zcount('foo', '-inf', '+inf'), 3) def test_zcount_exclusive(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', three=2) self.redis.zadd('foo', five=5) self.assertEqual(self.redis.zcount('foo', '-inf', '(2'), 1) self.assertEqual(self.redis.zcount('foo', '-inf', 2), 2) self.assertEqual(self.redis.zcount('foo', '(5', '+inf'), 0) self.assertEqual(self.redis.zcount('foo', '(1', 5), 2) self.assertEqual(self.redis.zcount('foo', '(2', '(5'), 0) self.assertEqual(self.redis.zcount('foo', '(1', '(5'), 1) self.assertEqual(self.redis.zcount('foo', 2, '(5'), 1) def test_zincrby(self): self.redis.zadd('foo', one=1) self.assertEqual(self.redis.zincrby('foo', 'one', 10), 11) self.assertEqual(self.redis.zrange('foo', 0, -1, withscores=True), [(b'one', 11)]) def test_zrange_descending(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrange('foo', 0, -1, desc=True), [b'three', b'two', b'one']) def test_zrange_descending_with_scores(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrange('foo', 0, -1, desc=True, withscores=True), [(b'three', 3), (b'two', 2), (b'one', 1)]) def test_zrange_with_positive_indices(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrange('foo', 0, 1), [b'one', b'two']) def test_zrank(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrank('foo', 'one'), 0) self.assertEqual(self.redis.zrank('foo', 'two'), 1) self.assertEqual(self.redis.zrank('foo', 'three'), 2) def test_zrank_non_existent_member(self): self.assertEqual(self.redis.zrank('foo', 'one'), None) def test_zrem(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.redis.zadd('foo', four=4) self.assertEqual(self.redis.zrem('foo', 'one'), True) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'two', b'three', b'four']) # Since redis>=2.7.6 returns number of deleted items. self.assertEqual(self.redis.zrem('foo', 'two', 'three'), 2) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'four']) self.assertEqual(self.redis.zrem('foo', 'three', 'four'), True) self.assertEqual(self.redis.zrange('foo', 0, -1), []) self.assertEqual(self.redis.zrem('foo', 'three', 'four'), False) def test_zrem_non_existent_member(self): self.assertFalse(self.redis.zrem('foo', 'one')) def test_zrem_numeric_member(self): self.redis.zadd('foo', **{'128': 13.0, '129': 12.0}) self.assertEqual(self.redis.zrem('foo', 128), True) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'129']) def test_zscore(self): self.redis.zadd('foo', one=54) self.assertEqual(self.redis.zscore('foo', 'one'), 54) def test_zscore_non_existent_member(self): self.assertIsNone(self.redis.zscore('foo', 'one')) def test_zrevrank(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrevrank('foo', 'one'), 2) self.assertEqual(self.redis.zrevrank('foo', 'two'), 1) self.assertEqual(self.redis.zrevrank('foo', 'three'), 0) def test_zrevrank_non_existent_member(self): self.assertEqual(self.redis.zrevrank('foo', 'one'), None) def test_zrevrange(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrevrange('foo', 0, 1), [b'three', b'two']) self.assertEqual(self.redis.zrevrange('foo', 0, -1), [b'three', b'two', b'one']) def test_zrevrange_sorted_keys(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', 2, 'two_b') self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrevrange('foo', 0, 2), [b'three', b'two_b', b'two']) self.assertEqual(self.redis.zrevrange('foo', 0, -1), [b'three', b'two_b', b'two', b'one']) def test_zrangebyscore(self): self.redis.zadd('foo', zero=0) self.redis.zadd('foo', two=2) self.redis.zadd('foo', two_a_also=2) self.redis.zadd('foo', two_b_also=2) self.redis.zadd('foo', four=4) self.assertEqual(self.redis.zrangebyscore('foo', 1, 3), [b'two', b'two_a_also', b'two_b_also']) self.assertEqual(self.redis.zrangebyscore('foo', 2, 3), [b'two', b'two_a_also', b'two_b_also']) self.assertEqual(self.redis.zrangebyscore('foo', 0, 4), [b'zero', b'two', b'two_a_also', b'two_b_also', b'four']) self.assertEqual(self.redis.zrangebyscore('foo', '-inf', 1), [b'zero']) self.assertEqual(self.redis.zrangebyscore('foo', 2, '+inf'), [b'two', b'two_a_also', b'two_b_also', b'four']) self.assertEqual(self.redis.zrangebyscore('foo', '-inf', '+inf'), [b'zero', b'two', b'two_a_also', b'two_b_also', b'four']) def test_zrangebysore_exclusive(self): self.redis.zadd('foo', zero=0) self.redis.zadd('foo', two=2) self.redis.zadd('foo', four=4) self.redis.zadd('foo', five=5) self.assertEqual(self.redis.zrangebyscore('foo', '(0', 6), [b'two', b'four', b'five']) self.assertEqual(self.redis.zrangebyscore('foo', '(2', '(5'), [b'four']) self.assertEqual(self.redis.zrangebyscore('foo', 0, '(4'), [b'zero', b'two']) def test_zrangebyscore_raises_error(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) with self.assertRaises(redis.ResponseError): self.redis.zrangebyscore('foo', 'one', 2) with self.assertRaises(redis.ResponseError): self.redis.zrangebyscore('foo', 2, 'three') with self.assertRaises(redis.ResponseError): self.redis.zrangebyscore('foo', 2, '3)') with self.assertRaises(redis.RedisError): self.redis.zrangebyscore('foo', 2, '3)', 0, None) def test_zrangebyscore_slice(self): self.redis.zadd('foo', two_a=2) self.redis.zadd('foo', two_b=2) self.redis.zadd('foo', two_c=2) self.redis.zadd('foo', two_d=2) self.assertEqual(self.redis.zrangebyscore('foo', 0, 4, 0, 2), [b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebyscore('foo', 0, 4, 1, 3), [b'two_b', b'two_c', b'two_d']) def test_zrangebyscore_withscores(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrangebyscore('foo', 1, 3, 0, 2, True), [(b'one', 1), (b'two', 2)]) def test_zrevrangebyscore(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrevrangebyscore('foo', 3, 1), [b'three', b'two', b'one']) self.assertEqual(self.redis.zrevrangebyscore('foo', 3, 2), [b'three', b'two']) self.assertEqual(self.redis.zrevrangebyscore('foo', 3, 1, 0, 1), [b'three']) self.assertEqual(self.redis.zrevrangebyscore('foo', 3, 1, 1, 2), [b'two', b'one']) def test_zrevrangebyscore_exclusive(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zrevrangebyscore('foo', '(3', 1), [b'two', b'one']) self.assertEqual(self.redis.zrevrangebyscore('foo', 3, '(2'), [b'three']) self.assertEqual(self.redis.zrevrangebyscore('foo', '(3', '(1'), [b'two']) self.assertEqual(self.redis.zrevrangebyscore('foo', '(2', 1, 0, 1), [b'one']) self.assertEqual(self.redis.zrevrangebyscore('foo', '(2', '(1', 0, 1), []) self.assertEqual(self.redis.zrevrangebyscore('foo', '(3', '(0', 1, 2), [b'one']) def test_zrevrangebyscore_raises_error(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) with self.assertRaises(redis.ResponseError): self.redis.zrevrangebyscore('foo', 'three', 1) with self.assertRaises(redis.ResponseError): self.redis.zrevrangebyscore('foo', 3, 'one') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebyscore('foo', 3, '1)') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebyscore('foo', '((3', '1)') def test_zrangebylex(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zrangebylex('foo', b'(t', b'+'), [b'three_a', b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'(t', b'[two_b'), [b'three_a', b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'(t', b'(two_b'), [b'three_a', b'two_a']) self.assertEqual(self.redis.zrangebylex('foo', b'[three_a', b'[two_b'), [b'three_a', b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'(three_a', b'[two_b'), [b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'-', b'(two_b'), [b'one_a', b'three_a', b'two_a']) self.assertEqual(self.redis.zrangebylex('foo', b'[two_b', b'(two_b'), []) # reversed max + and min - boundaries # these will be always empty, but allowed by redis self.assertEqual(self.redis.zrangebylex('foo', b'+', b'-'), []) self.assertEqual(self.redis.zrangebylex('foo', b'+', b'[three_a'), []) self.assertEqual(self.redis.zrangebylex('foo', b'[o', b'-'), []) def test_zlexcount(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zlexcount('foo', b'(t', b'+'), 3) self.assertEqual(self.redis.zlexcount('foo', b'(t', b'[two_b'), 3) self.assertEqual(self.redis.zlexcount('foo', b'(t', b'(two_b'), 2) self.assertEqual(self.redis.zlexcount('foo', b'[three_a', b'[two_b'), 3) self.assertEqual(self.redis.zlexcount('foo', b'(three_a', b'[two_b'), 2) self.assertEqual(self.redis.zlexcount('foo', b'-', b'(two_b'), 3) self.assertEqual(self.redis.zlexcount('foo', b'[two_b', b'(two_b'), 0) # reversed max + and min - boundaries # these will be always empty, but allowed by redis self.assertEqual(self.redis.zlexcount('foo', b'+', b'-'), 0) self.assertEqual(self.redis.zlexcount('foo', b'+', b'[three_a'), 0) self.assertEqual(self.redis.zlexcount('foo', b'[o', b'-'), 0) def test_zrangebylex_with_limit(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zrangebylex('foo', b'-', b'+', 1, 2), [b'three_a', b'two_a']) # negative offset no results self.assertEqual(self.redis.zrangebylex('foo', b'-', b'+', -1, 3), []) # negative limit ignored self.assertEqual(self.redis.zrangebylex('foo', b'-', b'+', 0, -2), [b'one_a', b'three_a', b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'-', b'+', 1, -2), [b'three_a', b'two_a', b'two_b']) self.assertEqual(self.redis.zrangebylex('foo', b'+', b'-', 1, 1), []) def test_zrangebylex_raises_error(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) with self.assertRaises(redis.ResponseError): self.redis.zrangebylex('foo', b'', b'[two_b') with self.assertRaises(redis.ResponseError): self.redis.zrangebylex('foo', b'-', b'two_b') with self.assertRaises(redis.ResponseError): self.redis.zrangebylex('foo', b'(t', b'two_b') with self.assertRaises(redis.ResponseError): self.redis.zrangebylex('foo', b't', b'+') with self.assertRaises(redis.ResponseError): self.redis.zrangebylex('foo', b'[two_a', b'') with self.assertRaises(redis.RedisError): self.redis.zrangebylex('foo', b'(two_a', b'[two_b', 1) def test_zrevrangebylex(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zrevrangebylex('foo', b'+', b'(t'), [b'two_b', b'two_a', b'three_a']) self.assertEqual(self.redis.zrevrangebylex('foo', b'[two_b', b'(t'), [b'two_b', b'two_a', b'three_a']) self.assertEqual(self.redis.zrevrangebylex('foo', b'(two_b', b'(t'), [b'two_a', b'three_a']) self.assertEqual(self.redis.zrevrangebylex('foo', b'[two_b', b'[three_a'), [b'two_b', b'two_a', b'three_a']) self.assertEqual(self.redis.zrevrangebylex('foo', b'[two_b', b'(three_a'), [b'two_b', b'two_a']) self.assertEqual(self.redis.zrevrangebylex('foo', b'(two_b', b'-'), [b'two_a', b'three_a', b'one_a']) self.assertEqual(self.redis.zrangebylex('foo', b'(two_b', b'[two_b'), []) # reversed max + and min - boundaries # these will be always empty, but allowed by redis self.assertEqual(self.redis.zrevrangebylex('foo', b'-', b'+'), []) self.assertEqual(self.redis.zrevrangebylex('foo', b'[three_a', b'+'), []) self.assertEqual(self.redis.zrevrangebylex('foo', b'-', b'[o'), []) def test_zrevrangebylex_with_limit(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zrevrangebylex('foo', b'+', b'-', 1, 2), [b'two_a', b'three_a']) def test_zrevrangebylex_raises_error(self): self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', three_a=0) with self.assertRaises(redis.ResponseError): self.redis.zrevrangebylex('foo', b'[two_b', b'') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebylex('foo', b'two_b', b'-') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebylex('foo', b'two_b', b'(t') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebylex('foo', b'+', b't') with self.assertRaises(redis.ResponseError): self.redis.zrevrangebylex('foo', b'', b'[two_a') with self.assertRaises(redis.RedisError): self.redis.zrevrangebylex('foo', b'[two_a', b'(two_b', 1) def test_zremrangebyrank(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zremrangebyrank('foo', 0, 1), 2) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'three']) def test_zremrangebyrank_negative_indices(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('foo', three=3) self.assertEqual(self.redis.zremrangebyrank('foo', -2, -1), 2) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'one']) def test_zremrangebyrank_out_of_bounds(self): self.redis.zadd('foo', one=1) self.assertEqual(self.redis.zremrangebyrank('foo', 1, 3), 0) def test_zremrangebyscore(self): self.redis.zadd('foo', zero=0) self.redis.zadd('foo', two=2) self.redis.zadd('foo', four=4) # Outside of range. self.assertEqual(self.redis.zremrangebyscore('foo', 5, 10), 0) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'two', b'four']) # Middle of range. self.assertEqual(self.redis.zremrangebyscore('foo', 1, 3), 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'four']) self.assertEqual(self.redis.zremrangebyscore('foo', 1, 3), 0) # Entire range. self.assertEqual(self.redis.zremrangebyscore('foo', 0, 4), 2) self.assertEqual(self.redis.zrange('foo', 0, -1), []) def test_zremrangebyscore_exclusive(self): self.redis.zadd('foo', zero=0) self.redis.zadd('foo', two=2) self.redis.zadd('foo', four=4) self.assertEqual(self.redis.zremrangebyscore('foo', '(0', 1), 0) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'two', b'four']) self.assertEqual(self.redis.zremrangebyscore('foo', '-inf', '(0'), 0) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'two', b'four']) self.assertEqual(self.redis.zremrangebyscore('foo', '(2', 5), 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'zero', b'two']) self.assertEqual(self.redis.zremrangebyscore('foo', 0, '(2'), 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'two']) self.assertEqual(self.redis.zremrangebyscore('foo', '(1', '(3'), 1) self.assertEqual(self.redis.zrange('foo', 0, -1), []) def test_zremrangebyscore_raises_error(self): self.redis.zadd('foo', zero=0) self.redis.zadd('foo', two=2) self.redis.zadd('foo', four=4) with self.assertRaises(redis.ResponseError): self.redis.zremrangebyscore('foo', 'three', 1) with self.assertRaises(redis.ResponseError): self.redis.zremrangebyscore('foo', 3, 'one') with self.assertRaises(redis.ResponseError): self.redis.zremrangebyscore('foo', 3, '1)') with self.assertRaises(redis.ResponseError): self.redis.zremrangebyscore('foo', '((3', '1)') def test_zremrangebyscore_badkey(self): self.assertEqual(self.redis.zremrangebyscore('foo', 0, 2), 0) def test_zremrangebylex(self): self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', three_a=0) self.assertEqual(self.redis.zremrangebylex('foo', b'(three_a', b'[two_b'), 2) self.assertEqual(self.redis.zremrangebylex('foo', b'(three_a', b'[two_b'), 0) self.assertEqual(self.redis.zremrangebylex('foo', b'-', b'(o'), 0) self.assertEqual(self.redis.zremrangebylex('foo', b'-', b'[one_a'), 1) self.assertEqual(self.redis.zremrangebylex('foo', b'[tw', b'+'), 0) self.assertEqual(self.redis.zremrangebylex('foo', b'[t', b'+'), 1) self.assertEqual(self.redis.zremrangebylex('foo', b'[t', b'+'), 0) def test_zremrangebylex_error(self): self.redis.zadd('foo', two_a=0) self.redis.zadd('foo', two_b=0) self.redis.zadd('foo', one_a=0) self.redis.zadd('foo', three_a=0) with self.assertRaises(redis.ResponseError): self.redis.zremrangebylex('foo', b'(t', b'two_b') with self.assertRaises(redis.ResponseError): self.redis.zremrangebylex('foo', b't', b'+') with self.assertRaises(redis.ResponseError): self.redis.zremrangebylex('foo', b'[two_a', b'') def test_zremrangebylex_badkey(self): self.assertEqual(self.redis.zremrangebylex('foo', b'(three_a', b'[two_b'), 0) def test_zunionstore(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zunionstore('baz', ['foo', 'bar']) self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 2), (b'three', 3), (b'two', 4)]) def test_zunionstore_sum(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zunionstore('baz', ['foo', 'bar'], aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 2), (b'three', 3), (b'two', 4)]) def test_zunionstore_max(self): self.redis.zadd('foo', one=0) self.redis.zadd('foo', two=0) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zunionstore('baz', ['foo', 'bar'], aggregate='MAX') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 1), (b'two', 2), (b'three', 3)]) def test_zunionstore_min(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('bar', one=0) self.redis.zadd('bar', two=0) self.redis.zadd('bar', three=3) self.redis.zunionstore('baz', ['foo', 'bar'], aggregate='MIN') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 0), (b'two', 0), (b'three', 3)]) def test_zunionstore_weights(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', four=4) self.redis.zunionstore('baz', {'foo': 1, 'bar': 2}, aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 3), (b'two', 6), (b'four', 8)]) def test_zunionstore_mixed_set_types(self): # No score, redis will use 1.0. self.redis.sadd('foo', 'one') self.redis.sadd('foo', 'two') self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zunionstore('baz', ['foo', 'bar'], aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 2), (b'three', 3), (b'two', 3)]) def test_zunionstore_badkey(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zunionstore('baz', ['foo', 'bar'], aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 1), (b'two', 2)]) self.redis.zunionstore('baz', {'foo': 1, 'bar': 2}, aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 1), (b'two', 2)]) def test_zinterstore(self): self.redis.zadd('foo', one=1) self.redis.zadd('foo', two=2) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zinterstore('baz', ['foo', 'bar']) self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 2), (b'two', 4)]) def test_zinterstore_mixed_set_types(self): self.redis.sadd('foo', 'one') self.redis.sadd('foo', 'two') self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zinterstore('baz', ['foo', 'bar'], aggregate='SUM') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 2), (b'two', 3)]) def test_zinterstore_max(self): self.redis.zadd('foo', one=0) self.redis.zadd('foo', two=0) self.redis.zadd('bar', one=1) self.redis.zadd('bar', two=2) self.redis.zadd('bar', three=3) self.redis.zinterstore('baz', ['foo', 'bar'], aggregate='MAX') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 1), (b'two', 2)]) def test_zinterstore_onekey(self): self.redis.zadd('foo', one=1) self.redis.zinterstore('baz', ['foo'], aggregate='MAX') self.assertEqual(self.redis.zrange('baz', 0, -1, withscores=True), [(b'one', 1)]) def test_zinterstore_nokey(self): with self.assertRaises(redis.ResponseError): self.redis.zinterstore('baz', [], aggregate='MAX') def test_zunionstore_nokey(self): with self.assertRaises(redis.ResponseError): self.redis.zunionstore('baz', [], aggregate='MAX') def test_multidb(self): r1 = self.create_redis(db=0) r2 = self.create_redis(db=1) r1['r1'] = 'r1' r2['r2'] = 'r2' self.assertTrue('r2' not in r1) self.assertTrue('r1' not in r2) self.assertEqual(r1['r1'], b'r1') self.assertEqual(r2['r2'], b'r2') r1.flushall() self.assertTrue('r1' not in r1) self.assertTrue('r2' not in r2) def test_basic_sort(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '3') self.assertEqual(self.redis.sort('foo'), [b'1', b'2', b'3']) def test_empty_sort(self): self.assertEqual(self.redis.sort('foo'), []) def test_sort_range_offset_range(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '4') self.redis.rpush('foo', '3') self.assertEqual(self.redis.sort('foo', start=0, num=2), [b'1', b'2']) def test_sort_range_offset_range_and_desc(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '4') self.redis.rpush('foo', '3') self.assertEqual(self.redis.sort("foo", start=0, num=1, desc=True), [b"4"]) def test_sort_range_offset_norange(self): with self.assertRaises(redis.RedisError): self.redis.sort('foo', start=1) def test_sort_range_with_large_range(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '4') self.redis.rpush('foo', '3') # num=20 even though len(foo) is 4. self.assertEqual(self.redis.sort('foo', start=1, num=20), [b'2', b'3', b'4']) def test_sort_descending(self): self.redis.rpush('foo', '1') self.redis.rpush('foo', '2') self.redis.rpush('foo', '3') self.assertEqual(self.redis.sort('foo', desc=True), [b'3', b'2', b'1']) def test_sort_alpha(self): self.redis.rpush('foo', '2a') self.redis.rpush('foo', '1b') self.redis.rpush('foo', '2b') self.redis.rpush('foo', '1a') self.assertEqual(self.redis.sort('foo', alpha=True), [b'1a', b'1b', b'2a', b'2b']) def test_foo(self): self.redis.rpush('foo', '2a') self.redis.rpush('foo', '1b') self.redis.rpush('foo', '2b') self.redis.rpush('foo', '1a') with self.assertRaises(redis.ResponseError): self.redis.sort('foo', alpha=False) def test_sort_with_store_option(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '4') self.redis.rpush('foo', '3') self.assertEqual(self.redis.sort('foo', store='bar'), 4) self.assertEqual(self.redis.lrange('bar', 0, -1), [b'1', b'2', b'3', b'4']) def test_sort_with_by_and_get_option(self): self.redis.rpush('foo', '2') self.redis.rpush('foo', '1') self.redis.rpush('foo', '4') self.redis.rpush('foo', '3') self.redis['weight_1'] = '4' self.redis['weight_2'] = '3' self.redis['weight_3'] = '2' self.redis['weight_4'] = '1' self.redis['data_1'] = 'one' self.redis['data_2'] = 'two' self.redis['data_3'] = 'three' self.redis['data_4'] = 'four' self.assertEqual(self.redis.sort('foo', by='weight_*', get='data_*'), [b'four', b'three', b'two', b'one']) self.assertEqual(self.redis.sort('foo', by='weight_*', get='#'), [b'4', b'3', b'2', b'1']) self.assertEqual( self.redis.sort('foo', by='weight_*', get=('data_*', '#')), [b'four', b'4', b'three', b'3', b'two', b'2', b'one', b'1']) self.assertEqual(self.redis.sort('foo', by='weight_*', get='data_1'), [None, None, None, None]) def test_sort_with_hash(self): self.redis.rpush('foo', 'middle') self.redis.rpush('foo', 'eldest') self.redis.rpush('foo', 'youngest') self.redis.hset('record_youngest', 'age', 1) self.redis.hset('record_youngest', 'name', 'baby') self.redis.hset('record_middle', 'age', 10) self.redis.hset('record_middle', 'name', 'teen') self.redis.hset('record_eldest', 'age', 20) self.redis.hset('record_eldest', 'name', 'adult') self.assertEqual(self.redis.sort('foo', by='record_*->age'), [b'youngest', b'middle', b'eldest']) self.assertEqual( self.redis.sort('foo', by='record_*->age', get='record_*->name'), [b'baby', b'teen', b'adult']) def test_sort_with_set(self): self.redis.sadd('foo', '3') self.redis.sadd('foo', '1') self.redis.sadd('foo', '2') self.assertEqual(self.redis.sort('foo'), [b'1', b'2', b'3']) def test_pipeline(self): # The pipeline method returns an object for # issuing multiple commands in a batch. p = self.redis.pipeline() p.watch('bam') p.multi() p.set('foo', 'bar').get('foo') p.lpush('baz', 'quux') p.lpush('baz', 'quux2').lrange('baz', 0, -1) res = p.execute() # Check return values returned as list. self.assertEqual(res, [True, b'bar', 1, 2, [b'quux2', b'quux']]) # Check side effects happened as expected. self.assertEqual(self.redis.lrange('baz', 0, -1), [b'quux2', b'quux']) # Check that the command buffer has been emptied. self.assertEqual(p.execute(), []) def test_pipeline_ignore_errors(self): """Test the pipeline ignoring errors when asked.""" with self.redis.pipeline() as p: p.set('foo', 'bar') p.rename('baz', 'bats') with self.assertRaises(redis.exceptions.ResponseError): p.execute() self.assertEqual([], p.execute()) with self.redis.pipeline() as p: p.set('foo', 'bar') p.rename('baz', 'bats') res = p.execute(raise_on_error=False) self.assertEqual([], p.execute()) self.assertEqual(len(res), 2) self.assertIsInstance(res[1], redis.exceptions.ResponseError) def test_multiple_successful_watch_calls(self): p = self.redis.pipeline() p.watch('bam') p.multi() p.set('foo', 'bar') # Check that the watched keys buffer has been emptied. p.execute() # bam is no longer being watched, so it's ok to modify # it now. p.watch('foo') self.redis.set('bam', 'boo') p.multi() p.set('foo', 'bats') self.assertEqual(p.execute(), [True]) def test_pipeline_non_transactional(self): # For our simple-minded model I don't think # there is any observable difference. p = self.redis.pipeline(transaction=False) res = p.set('baz', 'quux').get('baz').execute() self.assertEqual(res, [True, b'quux']) def test_pipeline_raises_when_watched_key_changed(self): self.redis.set('foo', 'bar') self.redis.rpush('greet', 'hello') p = self.redis.pipeline() self.addCleanup(p.reset) p.watch('greet', 'foo') nextf = fakeredis.to_bytes(p.get('foo')) + b'baz' # Simulate change happening on another thread. self.redis.rpush('greet', 'world') # Begin pipelining. p.multi() p.set('foo', nextf) with self.assertRaises(redis.WatchError): p.execute() def test_pipeline_succeeds_despite_unwatched_key_changed(self): # Same setup as before except for the params to the WATCH command. self.redis.set('foo', 'bar') self.redis.rpush('greet', 'hello') p = self.redis.pipeline() try: # Only watch one of the 2 keys. p.watch('foo') nextf = fakeredis.to_bytes(p.get('foo')) + b'baz' # Simulate change happening on another thread. self.redis.rpush('greet', 'world') p.multi() p.set('foo', nextf) p.execute() # Check the commands were executed. self.assertEqual(self.redis.get('foo'), b'barbaz') finally: p.reset() def test_pipeline_succeeds_when_watching_nonexistent_key(self): self.redis.set('foo', 'bar') self.redis.rpush('greet', 'hello') p = self.redis.pipeline() try: # Also watch a nonexistent key. p.watch('foo', 'bam') nextf = fakeredis.to_bytes(p.get('foo')) + b'baz' # Simulate change happening on another thread. self.redis.rpush('greet', 'world') p.multi() p.set('foo', nextf) p.execute() # Check the commands were executed. self.assertEqual(self.redis.get('foo'), b'barbaz') finally: p.reset() def test_watch_state_is_cleared_across_multiple_watches(self): self.redis.set('foo', 'one') self.redis.set('bar', 'baz') p = self.redis.pipeline() self.addCleanup(p.reset) p.watch('foo') # Simulate change happening on another thread. self.redis.set('foo', 'three') p.multi() p.set('foo', 'three') with self.assertRaises(redis.WatchError): p.execute() # Now watch another key. It should be ok to change # foo as we're no longer watching it. p.watch('bar') self.redis.set('foo', 'four') p.multi() p.set('bar', 'five') self.assertEqual(p.execute(), [True]) def test_pipeline_proxies_to_redis_object(self): p = self.redis.pipeline() self.assertTrue(hasattr(p, 'zadd')) with self.assertRaises(AttributeError): p.non_existent_attribute def test_pipeline_as_context_manager(self): self.redis.set('foo', 'bar') with self.redis.pipeline() as p: p.watch('foo') self.assertTrue(isinstance(p, redis.client.BasePipeline) or p.need_reset) p.multi() p.set('foo', 'baz') p.execute() # Usually you would consider the pipeline to # have been destroyed # after the with statement, but we need to check # it was reset properly: self.assertTrue(isinstance(p, redis.client.BasePipeline) or not p.need_reset) def test_pipeline_transaction_shortcut(self): # This example taken pretty much from the redis-py documentation. self.redis.set('OUR-SEQUENCE-KEY', 13) calls = [] def client_side_incr(pipe): calls.append((pipe,)) current_value = pipe.get('OUR-SEQUENCE-KEY') next_value = int(current_value) + 1 if len(calls) < 3: # Simulate a change from another thread. self.redis.set('OUR-SEQUENCE-KEY', next_value) pipe.multi() pipe.set('OUR-SEQUENCE-KEY', next_value) res = self.redis.transaction(client_side_incr, 'OUR-SEQUENCE-KEY') self.assertEqual([True], res) self.assertEqual(16, int(self.redis.get('OUR-SEQUENCE-KEY'))) self.assertEqual(3, len(calls)) def test_key_patterns(self): self.redis.mset({'one': 1, 'two': 2, 'three': 3, 'four': 4}) self.assertItemsEqual(self.redis.keys('*o*'), [b'four', b'one', b'two']) self.assertItemsEqual(self.redis.keys('t??'), [b'two']) self.assertItemsEqual(self.redis.keys('*'), [b'four', b'one', b'two', b'three']) self.assertItemsEqual(self.redis.keys(), [b'four', b'one', b'two', b'three']) def test_ping(self): self.assertTrue(self.redis.ping()) def test_type(self): self.redis.set('string_key', "value") self.redis.lpush("list_key", "value") self.redis.sadd("set_key", "value") self.redis.zadd("zset_key", 1, "value") self.redis.hset('hset_key', 'key', 'value') self.assertEqual(self.redis.type('string_key'), b'string') self.assertEqual(self.redis.type('list_key'), b'list') self.assertEqual(self.redis.type('set_key'), b'set') self.assertEqual(self.redis.type('zset_key'), b'zset') self.assertEqual(self.redis.type('hset_key'), b'hash') self.assertEqual(self.redis.type('none_key'), b'none') @attr('slow') def test_pubsub_subscribe(self): pubsub = self.redis.pubsub() pubsub.subscribe("channel") sleep(1) expected_message = {'type': 'subscribe', 'pattern': None, 'channel': b'channel', 'data': 1} message = pubsub.get_message() keys = list(pubsub.channels.keys()) key = keys[0] if not self.decode_responses: key = (key if type(key) == bytes else bytes(key, encoding='utf-8')) self.assertEqual(len(keys), 1) self.assertEqual(key, b'channel') self.assertEqual(message, expected_message) @attr('slow') def test_pubsub_psubscribe(self): pubsub = self.redis.pubsub() pubsub.psubscribe("channel.*") sleep(1) expected_message = {'type': 'psubscribe', 'pattern': None, 'channel': b'channel.*', 'data': 1} message = pubsub.get_message() keys = list(pubsub.patterns.keys()) self.assertEqual(len(keys), 1) self.assertEqual(message, expected_message) @attr('slow') def test_pubsub_unsubscribe(self): pubsub = self.redis.pubsub() pubsub.subscribe('channel-1', 'channel-2', 'channel-3') sleep(1) expected_message = {'type': 'unsubscribe', 'pattern': None, 'channel': b'channel-1', 'data': 2} pubsub.get_message() pubsub.get_message() pubsub.get_message() # unsubscribe from one pubsub.unsubscribe('channel-1') sleep(1) message = pubsub.get_message() keys = list(pubsub.channels.keys()) self.assertEqual(message, expected_message) self.assertEqual(len(keys), 2) # unsubscribe from multiple pubsub.unsubscribe() sleep(1) pubsub.get_message() pubsub.get_message() keys = list(pubsub.channels.keys()) self.assertEqual(message, expected_message) self.assertEqual(len(keys), 0) @attr('slow') def test_pubsub_punsubscribe(self): pubsub = self.redis.pubsub() pubsub.psubscribe('channel-1.*', 'channel-2.*', 'channel-3.*') sleep(1) expected_message = {'type': 'punsubscribe', 'pattern': None, 'channel': b'channel-1.*', 'data': 2} pubsub.get_message() pubsub.get_message() pubsub.get_message() # unsubscribe from one pubsub.punsubscribe('channel-1.*') sleep(1) message = pubsub.get_message() keys = list(pubsub.patterns.keys()) self.assertEqual(message, expected_message) self.assertEqual(len(keys), 2) # unsubscribe from multiple pubsub.punsubscribe() sleep(1) pubsub.get_message() pubsub.get_message() keys = list(pubsub.patterns.keys()) self.assertEqual(len(keys), 0) @attr('slow') def test_pubsub_listen(self): def _listen(pubsub, q): count = 0 for message in pubsub.listen(): q.put(message) count += 1 if count == 4: pubsub.close() channel = 'ch1' patterns = ['ch1*', 'ch[1]', 'ch?'] pubsub = self.redis.pubsub() pubsub.subscribe(channel) pubsub.psubscribe(*patterns) sleep(1) msg1 = pubsub.get_message() msg2 = pubsub.get_message() msg3 = pubsub.get_message() msg4 = pubsub.get_message() self.assertEqual(msg1['type'], 'subscribe') self.assertEqual(msg2['type'], 'psubscribe') self.assertEqual(msg3['type'], 'psubscribe') self.assertEqual(msg4['type'], 'psubscribe') q = Queue() t = threading.Thread(target=_listen, args=(pubsub, q)) t.start() msg = 'hello world' self.redis.publish(channel, msg) t.join() msg1 = q.get() msg2 = q.get() msg3 = q.get() msg4 = q.get() if self.decode_responses: bpatterns = patterns + [channel] else: bpatterns = [pattern.encode() for pattern in patterns] bpatterns.append(channel.encode()) msg = msg.encode() self.assertEqual(msg1['data'], msg) self.assertIn(msg1['channel'], bpatterns) self.assertEqual(msg2['data'], msg) self.assertIn(msg2['channel'], bpatterns) self.assertEqual(msg3['data'], msg) self.assertIn(msg3['channel'], bpatterns) self.assertEqual(msg4['data'], msg) self.assertIn(msg4['channel'], bpatterns) @attr('slow') def test_pubsub_ignore_sub_messages_listen(self): def _listen(pubsub, q): count = 0 for message in pubsub.listen(): q.put(message) count += 1 if count == 4: pubsub.close() channel = 'ch1' patterns = ['ch1*', 'ch[1]', 'ch?'] pubsub = self.redis.pubsub(ignore_subscribe_messages=True) pubsub.subscribe(channel) pubsub.psubscribe(*patterns) sleep(1) q = Queue() t = threading.Thread(target=_listen, args=(pubsub, q)) t.start() msg = 'hello world' self.redis.publish(channel, msg) t.join() msg1 = q.get() msg2 = q.get() msg3 = q.get() msg4 = q.get() if self.decode_responses: bpatterns = patterns + [channel] else: bpatterns = [pattern.encode() for pattern in patterns] bpatterns.append(channel.encode()) msg = msg.encode() self.assertEqual(msg1['data'], msg) self.assertIn(msg1['channel'], bpatterns) self.assertEqual(msg2['data'], msg) self.assertIn(msg2['channel'], bpatterns) self.assertEqual(msg3['data'], msg) self.assertIn(msg3['channel'], bpatterns) self.assertEqual(msg4['data'], msg) self.assertIn(msg4['channel'], bpatterns) def test_pfadd(self): key = "hll-pfadd" self.assertEqual( 1, self.redis.pfadd(key, "a", "b", "c", "d", "e", "f", "g")) self.assertEqual(7, self.redis.pfcount(key)) def test_pfcount(self): key1 = "hll-pfcount01" key2 = "hll-pfcount02" key3 = "hll-pfcount03" self.assertEqual(1, self.redis.pfadd(key1, "foo", "bar", "zap")) self.assertEqual(0, self.redis.pfadd(key1, "zap", "zap", "zap")) self.assertEqual(0, self.redis.pfadd(key1, "foo", "bar")) self.assertEqual(3, self.redis.pfcount(key1)) self.assertEqual(1, self.redis.pfadd(key2, "1", "2", "3")) self.assertEqual(3, self.redis.pfcount(key2)) self.assertEqual(6, self.redis.pfcount(key1, key2)) self.assertEqual(1, self.redis.pfadd(key3, "foo", "bar", "zip")) self.assertEqual(3, self.redis.pfcount(key3)) self.assertEqual(4, self.redis.pfcount(key1, key3)) self.assertEqual(7, self.redis.pfcount(key1, key2, key3)) def test_pfmerge(self): key1 = "hll-pfmerge01" key2 = "hll-pfmerge02" key3 = "hll-pfmerge03" self.assertEqual(1, self.redis.pfadd(key1, "foo", "bar", "zap", "a")) self.assertEqual(1, self.redis.pfadd(key2, "a", "b", "c", "foo")) self.assertTrue(self.redis.pfmerge(key3, key1, key2)) self.assertEqual(6, self.redis.pfcount(key3)) def test_scan(self): # Setup the data for ix in range(20): k = 'scan-test:%s' % ix v = 'result:%s' % ix self.redis.set(k, v) expected = self.redis.keys() self.assertEqual(20, len(expected)) # Ensure we know what we're testing # Test that we page through the results and get everything out results = [] cursor = '0' while cursor != 0: cursor, data = self.redis.scan(cursor, count=6) results.extend(data) self.assertSetEqual(set(expected), set(results)) # Now test that the MATCH functionality works results = [] cursor = '0' while cursor != 0: cursor, data = self.redis.scan(cursor, match='*7', count=100) results.extend(data) self.assertIn(b'scan-test:7', results) self.assertIn(b'scan-test:17', results) self.assertEqual(2, len(results)) # Test the match on iterator results = [r for r in self.redis.scan_iter(match='*7')] self.assertIn(b'scan-test:7', results) self.assertIn(b'scan-test:17', results) self.assertEqual(2, len(results)) def test_sscan(self): # Setup the data name = 'sscan-test' for ix in range(20): k = 'sscan-test:%s' % ix self.redis.sadd(name, k) expected = self.redis.smembers(name) self.assertEqual(20, len(expected)) # Ensure we know what we're testing # Test that we page through the results and get everything out results = [] cursor = '0' while cursor != 0: cursor, data = self.redis.sscan(name, cursor, count=6) results.extend(data) self.assertSetEqual(set(expected), set(results)) # Test the iterator version results = [r for r in self.redis.sscan_iter(name, count=6)] self.assertSetEqual(set(expected), set(results)) # Now test that the MATCH functionality works results = [] cursor = '0' while cursor != 0: cursor, data = self.redis.sscan(name, cursor, match='*7', count=100) results.extend(data) self.assertIn(b'sscan-test:7', results) self.assertIn(b'sscan-test:17', results) self.assertEqual(2, len(results)) # Test the match on iterator results = [r for r in self.redis.sscan_iter(name, match='*7')] self.assertIn(b'sscan-test:7', results) self.assertIn(b'sscan-test:17', results) self.assertEqual(2, len(results)) def test_hscan(self): # Setup the data name = 'hscan-test' for ix in range(20): k = 'key:%s' % ix v = 'result:%s' % ix self.redis.hset(name, k, v) expected = self.redis.hgetall(name) self.assertEqual(20, len(expected)) # Ensure we know what we're testing # Test that we page through the results and get everything out results = {} cursor = '0' while cursor != 0: cursor, data = self.redis.hscan(name, cursor, count=6) results.update(data) self.assertDictEqual(expected, results) # Test the iterator version results = {} for key, val in self.redis.hscan_iter(name, count=6): results[key] = val self.assertDictEqual(expected, results) # Now test that the MATCH functionality works results = {} cursor = '0' while cursor != 0: cursor, data = self.redis.hscan(name, cursor, match='*7', count=100) results.update(data) self.assertIn(b'key:7', results) self.assertIn(b'key:17', results) self.assertEqual(2, len(results)) # Test the match on iterator results = {} for key, val in self.redis.hscan_iter(name, match='*7'): results[key] = val self.assertIn(b'key:7', results) self.assertIn(b'key:17', results) self.assertEqual(2, len(results)) def test_ttl_should_return_minus_one_for_non_expiring_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.assertEqual(self.redis.ttl('foo'), -1) def test_ttl_should_return_minus_two_for_non_existent_key(self): self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.ttl('foo'), -2) def test_pttl_should_return_minus_one_for_non_expiring_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.assertEqual(self.redis.pttl('foo'), -1) def test_pttl_should_return_minus_two_for_non_existent_key(self): self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.pttl('foo'), -2) def test_persist(self): self.redis.set('foo', 'bar', ex=20) self.redis.persist('foo') self.assertEqual(self.redis.ttl('foo'), -1) def test_set_existing_key_persists(self): self.redis.set('foo', 'bar', ex=20) self.redis.set('foo', 'foo') self.assertEqual(self.redis.ttl('foo'), -1) class TestFakeRedis(unittest.TestCase): decode_responses = False def setUp(self): self.redis = self.create_redis() def tearDown(self): self.redis.flushall() del self.redis def assertInRange(self, value, start, end, msg=None): self.assertGreaterEqual(value, start, msg) self.assertLessEqual(value, end, msg) def create_redis(self, db=0): return fakeredis.FakeRedis(db=db) def test_setex(self): self.assertEqual(self.redis.setex('foo', 'bar', 100), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_setex_using_timedelta(self): self.assertEqual( self.redis.setex('foo', 'bar', timedelta(seconds=100)), True) self.assertEqual(self.redis.get('foo'), b'bar') def test_lrem_postitive_count(self): self.redis.lpush('foo', 'same') self.redis.lpush('foo', 'same') self.redis.lpush('foo', 'different') self.redis.lrem('foo', 'same', 2) self.assertEqual(self.redis.lrange('foo', 0, -1), [b'different']) def test_lrem_negative_count(self): self.redis.lpush('foo', 'removeme') self.redis.lpush('foo', 'three') self.redis.lpush('foo', 'two') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'removeme') self.redis.lrem('foo', 'removeme', -1) # Should remove it from the end of the list, # leaving the 'removeme' from the front of the list alone. self.assertEqual(self.redis.lrange('foo', 0, -1), [b'removeme', b'one', b'two', b'three']) def test_lrem_zero_count(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lrem('foo', 'one') self.assertEqual(self.redis.lrange('foo', 0, -1), []) def test_lrem_default_value(self): self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lpush('foo', 'one') self.redis.lrem('foo', 'one') self.assertEqual(self.redis.lrange('foo', 0, -1), []) def test_lrem_does_not_exist(self): self.redis.lpush('foo', 'one') self.redis.lrem('foo', 'one') # These should be noops. self.redis.lrem('foo', 'one', -2) self.redis.lrem('foo', 'one', 2) def test_lrem_return_value(self): self.redis.lpush('foo', 'one') count = self.redis.lrem('foo', 'one', 0) self.assertEqual(count, 1) self.assertEqual(self.redis.lrem('foo', 'one'), 0) def test_zadd_deprecated(self): result = self.redis.zadd('foo', 'one', 1) self.assertEqual(result, 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'one']) def test_zadd_missing_required_params(self): with self.assertRaises(redis.RedisError): # Missing the 'score' param. self.redis.zadd('foo', 'one') with self.assertRaises(redis.RedisError): # Missing the 'value' param. self.redis.zadd('foo', None, score=1) with self.assertRaises(redis.RedisError): self.redis.zadd('foo') def test_zadd_with_single_keypair(self): result = self.redis.zadd('foo', bar=1) self.assertEqual(result, 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'bar']) def test_zadd_with_multiple_keypairs(self): result = self.redis.zadd('foo', bar=1, baz=9) self.assertEqual(result, 2) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'bar', b'baz']) def test_zadd_with_name_is_non_string(self): result = self.redis.zadd('foo', 1, 9) self.assertEqual(result, 1) self.assertEqual(self.redis.zrange('foo', 0, -1), [b'1']) def test_set_nx_doesnt_set_value_twice(self): self.assertEqual(self.redis.set('foo', 'bar', nx=True), True) self.assertEqual(self.redis.set('foo', 'bar', nx=True), None) def test_set_xx_set_value_when_exists(self): self.assertEqual(self.redis.set('foo', 'bar', xx=True), None) self.redis.set('foo', 'bar') self.assertEqual(self.redis.set('foo', 'bar', xx=True), True) @attr('slow') def test_set_ex_should_expire_value(self): self.redis.set('foo', 'bar', ex=0) self.assertEqual(self.redis.get('foo'), b'bar') self.redis.set('foo', 'bar', ex=1) sleep(2) self.assertEqual(self.redis.get('foo'), None) @attr('slow') def test_set_px_should_expire_value(self): self.redis.set('foo', 'bar', px=500) sleep(1.5) self.assertEqual(self.redis.get('foo'), None) @attr('slow') def test_psetex_expire_value(self): with self.assertRaises(ResponseError): self.redis.psetex('foo', 0, 'bar') self.redis.psetex('foo', 500, 'bar') sleep(1.5) self.assertEqual(self.redis.get('foo'), None) @attr('slow') def test_psetex_expire_value_using_timedelta(self): with self.assertRaises(ResponseError): self.redis.psetex('foo', timedelta(seconds=0), 'bar') self.redis.psetex('foo', timedelta(seconds=0.5), 'bar') sleep(1.5) self.assertEqual(self.redis.get('foo'), None) @attr('slow') def test_expire_should_expire_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.expire('foo', 1) sleep(1.5) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expire('bar', 1), False) def test_expire_should_return_true_for_existing_key(self): self.redis.set('foo', 'bar') rv = self.redis.expire('foo', 1) self.assertIs(rv, True) def test_expire_should_return_false_for_missing_key(self): rv = self.redis.expire('missing', 1) self.assertIs(rv, False) @attr('slow') def test_expire_should_expire_key_using_timedelta(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.expire('foo', timedelta(seconds=1)) sleep(1.5) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expire('bar', 1), False) @attr('slow') def test_expire_should_expire_immediately_with_millisecond_timedelta(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.expire('foo', timedelta(milliseconds=750)) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expire('bar', 1), False) @attr('slow') def test_pexpire_should_expire_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.pexpire('foo', 150) sleep(0.2) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.pexpire('bar', 1), False) def test_pexpire_should_return_truthy_for_existing_key(self): self.redis.set('foo', 'bar') rv = self.redis.pexpire('foo', 1) self.assertIs(bool(rv), True) def test_pexpire_should_return_falsey_for_missing_key(self): rv = self.redis.pexpire('missing', 1) self.assertIs(bool(rv), False) @attr('slow') def test_pexpire_should_expire_key_using_timedelta(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.pexpire('foo', timedelta(milliseconds=750)) sleep(0.5) self.assertEqual(self.redis.get('foo'), b'bar') sleep(0.5) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.pexpire('bar', 1), False) @attr('slow') def test_expireat_should_expire_key_by_datetime(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.expireat('foo', datetime.now() + timedelta(seconds=1)) sleep(1.5) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expireat('bar', datetime.now()), False) @attr('slow') def test_expireat_should_expire_key_by_timestamp(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.expireat('foo', int(time() + 1)) sleep(1.5) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expire('bar', 1), False) def test_expireat_should_return_true_for_existing_key(self): self.redis.set('foo', 'bar') rv = self.redis.expireat('foo', int(time() + 1)) self.assertIs(rv, True) def test_expireat_should_return_false_for_missing_key(self): rv = self.redis.expireat('missing', int(time() + 1)) self.assertIs(rv, False) @attr('slow') def test_pexpireat_should_expire_key_by_datetime(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.pexpireat('foo', datetime.now() + timedelta(milliseconds=150)) sleep(0.2) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.pexpireat('bar', datetime.now()), False) @attr('slow') def test_pexpireat_should_expire_key_by_timestamp(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.redis.pexpireat('foo', int(time() * 1000 + 150)) sleep(0.2) self.assertEqual(self.redis.get('foo'), None) self.assertEqual(self.redis.expire('bar', 1), False) def test_pexpireat_should_return_true_for_existing_key(self): self.redis.set('foo', 'bar') rv = self.redis.pexpireat('foo', int(time() * 1000 + 150)) self.assertIs(bool(rv), True) def test_pexpireat_should_return_false_for_missing_key(self): rv = self.redis.pexpireat('missing', int(time() * 1000 + 150)) self.assertIs(bool(rv), False) def test_ttl_should_return_none_for_non_expiring_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.assertEqual(self.redis.ttl('foo'), None) def test_ttl_should_return_value_for_expiring_key(self): self.redis.set('foo', 'bar') self.redis.expire('foo', 1) self.assertEqual(self.redis.ttl('foo'), 1) self.redis.expire('foo', 2) self.assertEqual(self.redis.ttl('foo'), 2) # See https://github.com/antirez/redis/blob/unstable/src/db.c#L632 ttl = 1000000000 self.redis.expire('foo', ttl) self.assertEqual(self.redis.ttl('foo'), ttl) def test_pttl_should_return_none_for_non_expiring_key(self): self.redis.set('foo', 'bar') self.assertEqual(self.redis.get('foo'), b'bar') self.assertEqual(self.redis.pttl('foo'), None) def test_pttl_should_return_value_for_expiring_key(self): d = 100 self.redis.set('foo', 'bar') self.redis.expire('foo', 1) self.assertInRange(self.redis.pttl('foo'), 1000 - d, 1000) self.redis.expire('foo', 2) self.assertInRange(self.redis.pttl('foo'), 2000 - d, 2000) ttl = 1000000000 # See https://github.com/antirez/redis/blob/unstable/src/db.c#L632 self.redis.expire('foo', ttl) self.assertInRange(self.redis.pttl('foo'), ttl * 1000 - d, ttl * 1000) def test_ttls_should_always_be_long(self): self.redis.set('foo', 'bar') self.redis.expire('foo', 1) self.assertTrue(type(self.redis.ttl('foo')) is long) self.assertTrue(type(self.redis.pttl('foo')) is long) def test_expire_should_not_handle_floating_point_values(self): self.redis.set('foo', 'bar') with self.assertRaisesRegexp( redis.ResponseError, 'value is not an integer or out of range'): self.redis.expire('something_new', 1.2) self.redis.pexpire('something_new', 1000.2) self.redis.expire('some_unused_key', 1.2) self.redis.pexpire('some_unused_key', 1000.2) class DecodeMixin(object): decode_responses = True def assertEqual(self, a, b, msg=None): super(DecodeMixin, self).assertEqual(a, fakeredis._decode(b), msg) def assertIn(self, member, container, msg=None): super(DecodeMixin, self).assertIn(fakeredis._decode(member), container) def assertItemsEqual(self, a, b): super(DecodeMixin, self).assertItemsEqual(a, fakeredis._decode(b)) class TestFakeStrictRedisDecodeResponses(DecodeMixin, TestFakeStrictRedis): def create_redis(self, db=0): return fakeredis.FakeStrictRedis(db=db, decode_responses=True) class TestFakeRedisDecodeResponses(DecodeMixin, TestFakeRedis): def create_redis(self, db=0): return fakeredis.FakeRedis(db=db, decode_responses=True) @redis_must_be_running class TestRealRedis(TestFakeRedis): def create_redis(self, db=0): return redis.Redis('localhost', port=6379, db=db) @redis_must_be_running class TestRealStrictRedis(TestFakeStrictRedis): def create_redis(self, db=0): return redis.StrictRedis('localhost', port=6379, db=db) @redis_must_be_running class TestRealRedisDecodeResponses(TestFakeRedisDecodeResponses): def create_redis(self, db=0): return redis.Redis('localhost', port=6379, db=db, decode_responses=True) @redis_must_be_running class TestRealStrictRedisDecodeResponses(TestFakeStrictRedisDecodeResponses): def create_redis(self, db=0): return redis.StrictRedis('localhost', port=6379, db=db, decode_responses=True) class TestInitArgs(unittest.TestCase): def test_can_accept_any_kwargs(self): fakeredis.FakeRedis(foo='bar', bar='baz') fakeredis.FakeStrictRedis(foo='bar', bar='baz') def test_from_url(self): db = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/0') db.set('foo', 'bar') self.assertEqual(db.get('foo'), b'bar') def test_from_url_with_db_arg(self): db = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/0') db1 = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/1') db2 = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/', db=2) db.set('foo', 'foo0') db1.set('foo', 'foo1') db2.set('foo', 'foo2') self.assertEqual(db.get('foo'), b'foo0') self.assertEqual(db1.get('foo'), b'foo1') self.assertEqual(db2.get('foo'), b'foo2') def test_from_url_db_value_error(self): # In ValueError, should default to 0 db = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/a') self.assertEqual(db._db_num, 0) def test_can_pass_through_extra_args(self): db = fakeredis.FakeStrictRedis.from_url( 'redis://username:password@localhost:6379/0', decode_responses=True) db.set('foo', 'bar') self.assertEqual(db.get('foo'), 'bar') class TestImportation(unittest.TestCase): def test_searches_for_c_stdlib_and_raises_if_missing(self): """ Verifies that fakeredis checks for both libc and msvcrt when looking for a strtod implementation and that it fails fast when neither is found. """ import ctypes.util # Patch manually since unittest.mock.patch is not available in old Python versions old_find_library = ctypes.util.find_library searched_libraries = set() try: ctypes.util.find_library = lambda library: searched_libraries.add(library) with self.assertRaises(ImportError): reload(fakeredis) self.assertEqual(set(['c', 'msvcrt']), searched_libraries) finally: ctypes.util.find_library = old_find_library reload(fakeredis) if __name__ == '__main__': unittest.main() fakeredis-0.9.0/tox.ini000066400000000000000000000002611317015342700147720ustar00rootroot00000000000000[tox] envlist = py26,py27,py33,py34 [testenv] commands = nosetests -v {posargs} deps = nose [testenv:py26] commands = nosetests -v {posargs} deps = nose unittest2