Skip to content

Commit

Permalink
Merge branch 'release/2.9.0.11'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesse Myers committed May 7, 2015
2 parents 0da311b + 8d1ba33 commit 45c0099
Show file tree
Hide file tree
Showing 22 changed files with 516 additions and 178 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Version 2.9.0.11
- Support: `scan_iter`, `sscan_iter`, `zscan_iter`, `hscan_iter`

Version 2.9.0.10
- Return & store byte strings everywhere (unicode turns into utf-8 by default)
- Fix *SCAN returning non-long values.
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Mock for the redis-py client library

Supports writing tests for code using the [redis-py][redis-py] library
Supports writing tests for code using the [redis-py][redis-py] library
without requiring a [redis-server][redis] install.

[![Build Status](https://travis-ci.org/locationlabs/mockredis.png)](https://travis-ci.org/locationlabs/mockredis)
Expand All @@ -17,11 +17,11 @@ Both `mockredis.mock_redis_client` and `mockredis.mock_strict_redis_client` can
used to patch instances of the *redis client*.

For example, using the [mock][mock] library:

@patch('redis.Redis', mock_redis_client)

Or:

@patch('redis.StrictRedis', mock_strict_redis_client)

## Testing
Expand All @@ -32,11 +32,12 @@ against ground truth. See `mockredis.tests.fixtures` for more details and discla

## Supported python versions

- Python 2.7
- Python 3.2
- Python 3.3
- Python 3.4
- PyPy
- Python 2.7
- Python 3.2
- Python 3.3
- Python 3.4
- PyPy
- PyPy3

## Attribution

Expand Down
143 changes: 109 additions & 34 deletions mockredis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ def __init__(self,
# Dictionary from script to sha ''Script''
self.shas = dict()

#### Connection Functions ####
# Connection Functions #

def echo(self, msg):
return self._encode(msg)

def ping(self):
return b'PONG'

#### Transactions Functions ####
# Transactions Functions #

def lock(self, key, timeout=0, sleep=0):
"""Emulate lock."""
Expand Down Expand Up @@ -101,7 +101,7 @@ def execute(self):
in this mock, so this is a no-op."""
pass

#### Keys Functions ####
# Keys Functions #

def type(self, key):
key = self._encode(key)
Expand Down Expand Up @@ -247,7 +247,7 @@ def _rename(self, old_key, new_key, nx=False):
return True
return False

#### String Functions ####
# String Functions #

def get(self, key):
key = self._encode(key)
Expand Down Expand Up @@ -413,7 +413,46 @@ def incr(self, key, amount=1):

incrby = incr

#### Hash Functions ####
def setbit(self, key, offset, value):
"""
Set the bit at ``offset`` in ``key`` to ``value``.
"""
key = self._encode(key)
index, bits, mask = self._get_bits_and_offset(key, offset)

if index >= len(bits):
bits.extend(b"\x00" * (index + 1 - len(bits)))

prev_val = 1 if (bits[index] & mask) else 0

if value:
bits[index] |= mask
else:
bits[index] &= ~mask

self.redis[key] = bytes(bits)

return prev_val

def getbit(self, key, offset):
"""
Returns the bit value at ``offset`` in ``key``.
"""
key = self._encode(key)
index, bits, mask = self._get_bits_and_offset(key, offset)

if index >= len(bits):
return 0

return 1 if (bits[index] & mask) else 0

def _get_bits_and_offset(self, key, offset):
bits = bytearray(self.redis.get(key, b""))
index, position = divmod(offset, 8)
mask = 128 >> position
return index, bits, mask

# Hash Functions #

def hexists(self, hashkey, attribute):
"""Emulate hexists."""
Expand Down Expand Up @@ -518,7 +557,7 @@ def hvals(self, hashkey):
redis_hash = self._get_hash(hashkey, 'HVALS')
return redis_hash.values()

#### List Functions ####
# List Functions #

def lrange(self, key, start, stop):
"""Emulate lrange."""
Expand Down Expand Up @@ -726,7 +765,7 @@ def sort(self, name,
return []

by = self._encode(by) if by is not None else by
# always organize the items as tuples of the value from the list itself and the value to sort by
# always organize the items as tuples of the value from the list and the sort key
if by and b'*' in by:
items = [(i, self.get(by.replace(b'*', self._encode(i)))) for i in items]
elif by in [None, b'nosort']:
Expand Down Expand Up @@ -782,7 +821,7 @@ def sort(self, name,
else:
return results

#### SCAN COMMANDS ####
# SCAN COMMANDS #

def _common_scan(self, values_function, cursor='0', match=None, count=10, key=None):
"""
Expand Down Expand Up @@ -820,6 +859,14 @@ def value_function():
return sorted(self.redis.keys()) # sorted list for consistent order
return self._common_scan(value_function, cursor=cursor, match=match, count=count)

def scan_iter(self, match=None, count=10):
"""Emulate scan_iter."""
cursor = '0'
while cursor != 0:
cursor, data = self.scan(cursor=cursor, match=match, count=count)
for item in data:
yield item

def sscan(self, name, cursor='0', match=None, count=10):
"""Emulate sscan."""
def value_function():
Expand All @@ -828,13 +875,31 @@ def value_function():
return members
return self._common_scan(value_function, cursor=cursor, match=match, count=count)

def sscan_iter(self, name, match=None, count=10):
"""Emulate sscan_iter."""
cursor = '0'
while cursor != 0:
cursor, data = self.sscan(name, cursor=cursor,
match=match, count=count)
for item in data:
yield item

def zscan(self, name, cursor='0', match=None, count=10):
"""Emulate zscan."""
def value_function():
values = self.zrange(name, 0, -1, withscores=True)
values.sort(key=lambda x: x[1]) # sort for consistent order
return values
return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0])
return self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) # noqa

def zscan_iter(self, name, match=None, count=10):
"""Emulate zscan_iter."""
cursor = '0'
while cursor != 0:
cursor, data = self.zscan(name, cursor=cursor, match=match,
count=count)
for item in data:
yield item

def hscan(self, name, cursor='0', match=None, count=10):
"""Emulate hscan."""
Expand All @@ -843,11 +908,20 @@ def value_function():
values = list(values.items()) # list of tuples for sorting and matching
values.sort(key=lambda x: x[0]) # sort for consistent order
return values
scanned = self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0])
scanned = self._common_scan(value_function, cursor=cursor, match=match, count=count, key=lambda v: v[0]) # noqa
scanned[1] = dict(scanned[1]) # from list of tuples back to dict
return scanned

#### SET COMMANDS ####
def hscan_iter(self, name, match=None, count=10):
"""Emulate hscan_iter."""
cursor = '0'
while cursor != 0:
cursor, data = self.hscan(name, cursor=cursor,
match=match, count=count)
for item in data.items():
yield item

# SET COMMANDS #

def sadd(self, key, *values):
"""Emulate sadd."""
Expand Down Expand Up @@ -960,7 +1034,7 @@ def sunionstore(self, dest, keys, *args):
self.redis[self._encode(dest)] = result
return len(result)

#### SORTED SET COMMANDS ####
# SORTED SET COMMANDS #

def zadd(self, name, *args, **kwargs):
zset = self._get_zset(name, "ZADD", create=True)
Expand All @@ -981,21 +1055,21 @@ def zadd(self, name, *args, **kwargs):
# kwargs
pieces.extend(kwargs.items())

insert_count = lambda member, score: 1 if zset.insert(self._encode(member), float(score)) else 0
insert_count = lambda member, score: 1 if zset.insert(self._encode(member), float(score)) else 0 # noqa
return sum((insert_count(member, score) for member, score in pieces))

def zcard(self, name):
zset = self._get_zset(name, "ZCARD")

return len(zset) if zset is not None else 0

def zcount(self, name, min_, max_):
def zcount(self, name, min, max):
zset = self._get_zset(name, "ZCOUNT")

if not zset:
return 0

return len(zset.scorerange(float(min_), float(max_)))
return len(zset.scorerange(float(min), float(max)))

def zincrby(self, name, value, amount=1):
zset = self._get_zset(name, "ZINCRBY", create=True)
Expand Down Expand Up @@ -1041,7 +1115,7 @@ def zrange(self, name, start, end, desc=False, withscores=False,
func = self._range_func(withscores, score_cast_func)
return [func(item) for item in zset.range(start, end, desc)]

def zrangebyscore(self, name, min_, max_, start=None, num=None,
def zrangebyscore(self, name, min, max, start=None, num=None,
withscores=False, score_cast_func=float):
if (start is None) ^ (num is None):
raise RedisError('`start` and `num` must both be specified')
Expand All @@ -1052,9 +1126,9 @@ def zrangebyscore(self, name, min_, max_, start=None, num=None,
return []

func = self._range_func(withscores, score_cast_func)
include_start, min_ = self._score_inclusive(min_)
include_end, max_ = self._score_inclusive(max_)
scorerange = zset.scorerange(min_, max_, start_inclusive=include_start, end_inclusive=include_end)
include_start, min = self._score_inclusive(min)
include_end, max = self._score_inclusive(max)
scorerange = zset.scorerange(min, max, start_inclusive=include_start, end_inclusive=include_end) # noqa
if start is not None and num is not None:
start, num = self._translate_limit(len(scorerange), int(start), int(num))
scorerange = scorerange[start:start + num]
Expand Down Expand Up @@ -1085,23 +1159,23 @@ def zremrangebyrank(self, name, start, end):

start, end = self._translate_range(len(zset), start, end)
count_removals = lambda score, member: 1 if zset.remove(member) else 0
removal_count = sum((count_removals(score, member) for score, member in zset.range(start, end)))
removal_count = sum((count_removals(score, member) for score, member in zset.range(start, end))) # noqa
if removal_count > 0 and len(zset) == 0:
self.delete(name)
return removal_count

def zremrangebyscore(self, name, min_, max_):
def zremrangebyscore(self, name, min, max):
zset = self._get_zset(name, "ZREMRANGEBYSCORE")

if not zset:
return 0

count_removals = lambda score, member: 1 if zset.remove(member) else 0
include_start, min_ = self._score_inclusive(min_)
include_end, max_ = self._score_inclusive(max_)
include_start, min = self._score_inclusive(min)
include_end, max = self._score_inclusive(max)

removal_count = sum((count_removals(score, member)
for score, member in zset.scorerange(min_, max_,
for score, member in zset.scorerange(min, max,
start_inclusive=include_start,
end_inclusive=include_end)))
if removal_count > 0 and len(zset) == 0:
Expand All @@ -1113,7 +1187,7 @@ def zrevrange(self, name, start, end, withscores=False,
return self.zrange(name, start, end,
desc=True, withscores=withscores, score_cast_func=score_cast_func)

def zrevrangebyscore(self, name, max_, min_, start=None, num=None,
def zrevrangebyscore(self, name, max, min, start=None, num=None,
withscores=False, score_cast_func=float):

if (start is None) ^ (num is None):
Expand All @@ -1124,11 +1198,12 @@ def zrevrangebyscore(self, name, max_, min_, start=None, num=None,
return []

func = self._range_func(withscores, score_cast_func)
include_start, min_ = self._score_inclusive(min_)
include_end, max_ = self._score_inclusive(max_)
include_start, min = self._score_inclusive(min)
include_end, max = self._score_inclusive(max)

scorerange = [x for x in reversed(zset.scorerange(float(min_), float(max_),
start_inclusive=include_start, end_inclusive=include_end))]
scorerange = [x for x in reversed(zset.scorerange(float(min), float(max),
start_inclusive=include_start,
end_inclusive=include_end))]
if start is not None and num is not None:
start, num = self._translate_limit(len(scorerange), int(start), int(num))
scorerange = scorerange[start:start + num]
Expand Down Expand Up @@ -1170,7 +1245,7 @@ def zunionstore(self, dest, keys, aggregate=None):
self.redis[self._encode(dest)] = union
return len(union)

#### Script Commands ####
# Script Commands #

def eval(self, script, numkeys, *keys_and_args):
"""Emulate eval"""
Expand Down Expand Up @@ -1279,12 +1354,12 @@ def _normalize_command_response(self, command, response):

return response

#### PubSub commands ####
# PubSub commands #

def publish(self, channel, message):
self.pubsub[channel].append(message)

#### Internal ####
# Internal #

def _get_list(self, key, operation, create=False):
"""
Expand All @@ -1308,7 +1383,7 @@ def _get_zset(self, name, operation, create=False):
"""
Get (and maybe create) a sorted set by name.
"""
return self._get_by_type(name, operation, create, b'zset', SortedSet(), return_default=False)
return self._get_by_type(name, operation, create, b'zset', SortedSet(), return_default=False) # noqa

def _get_by_type(self, key, operation, create, type_, default, return_default=True):
"""
Expand Down Expand Up @@ -1348,7 +1423,7 @@ def _range_func(self, withscores, score_cast_func):
Return a suitable function from (score, member)
"""
if withscores:
return lambda score_member: (score_member[1], score_cast_func(self._encode(score_member[0])))
return lambda score_member: (score_member[1], score_cast_func(self._encode(score_member[0]))) # noqa
else:
return lambda score_member: score_member[1]

Expand Down
2 changes: 0 additions & 2 deletions mockredis/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@
class RedisError(Exception):
pass


class ResponseError(RedisError):
pass


class WatchError(RedisError):
pass
Loading

0 comments on commit 45c0099

Please sign in to comment.