Skip to content

Commit dabc409

Browse files
authored
Merge pull request jamesls#316 from jamesls/redis-py4
Updates to support newer redis and redis-py
2 parents 117eb77 + dd32ea3 commit dabc409

File tree

6 files changed

+84
-29
lines changed

6 files changed

+84
-29
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
coverage: yes
4141
services:
4242
redis:
43-
image: redis:6.0.10
43+
image: redis:6.2.6
4444
ports:
4545
- 6379:6379
4646
steps:

fakeredis/_server.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,14 +1757,38 @@ def linsert(self, key, where, pivot, value):
17571757
def llen(self, key):
17581758
return len(key.value)
17591759

1760-
@command((Key(list),))
1761-
def lpop(self, key):
1762-
try:
1763-
ret = key.value.pop(0)
1764-
key.updated()
1765-
return ret
1766-
except IndexError:
1760+
def _list_pop(self, get_slice, key, *args):
1761+
"""Implements lpop and rpop.
1762+
1763+
`get_slice` must take a count and return a slice expression for the
1764+
range to pop.
1765+
"""
1766+
# This implementation is somewhat contorted to match the odd
1767+
# behaviours described in https://github.com/redis/redis/issues/9680.
1768+
count = 1
1769+
if len(args) > 1:
1770+
raise SimpleError(SYNTAX_ERROR_MSG)
1771+
elif len(args) == 1:
1772+
count = args[0]
1773+
if count < 0:
1774+
raise SimpleError(INDEX_ERROR_MSG)
1775+
elif count == 0:
1776+
return None
1777+
if not key:
17671778
return None
1779+
elif type(key.value) != list:
1780+
raise SimpleError(WRONGTYPE_MSG)
1781+
slc = get_slice(count)
1782+
ret = key.value[slc]
1783+
del key.value[slc]
1784+
key.updated()
1785+
if not args:
1786+
ret = ret[0]
1787+
return ret
1788+
1789+
@command((Key(),), (Int(),))
1790+
def lpop(self, key, *args):
1791+
return self._list_pop(lambda count: slice(None, count), key, *args)
17681792

17691793
@command((Key(list), bytes), (bytes,))
17701794
def lpush(self, key, *values):
@@ -1829,14 +1853,9 @@ def ltrim(self, key, start, stop):
18291853
key.update(new_value)
18301854
return OK
18311855

1832-
@command((Key(list),))
1833-
def rpop(self, key):
1834-
try:
1835-
ret = key.value.pop()
1836-
key.updated()
1837-
return ret
1838-
except IndexError:
1839-
return None
1856+
@command((Key(list),), (Int(),))
1857+
def rpop(self, key, *args):
1858+
return self._list_pop(lambda count: slice(None, -count - 1, -1), key, *args)
18401859

18411860
@command((Key(list, None), Key(list)))
18421861
def rpoplpush(self, src, dst):
@@ -1905,13 +1924,11 @@ def sdiff(self, *keys):
19051924
def sdiffstore(self, dst, *keys):
19061925
return self._setop(lambda a, b: a - b, False, dst, *keys)
19071926

1908-
# The following keys can't be marked as sets because of the
1909-
# stop_if_empty early-out.
1910-
@command((Key(set),), (Key(),))
1927+
@command((Key(set),), (Key(set),))
19111928
def sinter(self, *keys):
19121929
return self._setop(lambda a, b: a & b, True, None, *keys)
19131930

1914-
@command((Key(), Key(set)), (Key(),))
1931+
@command((Key(), Key(set)), (Key(set),))
19151932
def sinterstore(self, dst, *keys):
19161933
return self._setop(lambda a, b: a & b, True, dst, *keys)
19171934

@@ -2358,8 +2375,10 @@ def zinterstore(self, dest, numkeys, *args):
23582375
# Server commands
23592376
# TODO: lots
23602377

2361-
@command((), flags='s')
2362-
def bgsave(self):
2378+
@command((), (bytes,), flags='s')
2379+
def bgsave(self, *args):
2380+
if len(args) > 1 or (len(args) == 1 and not casematch(args[0], b'schedule')):
2381+
raise SimpleError(SYNTAX_ERROR_MSG)
23632382
self._server.lastsave = int(time.time())
23642383
return BGSAVE_STARTED
23652384

@@ -2563,7 +2582,7 @@ def script(self, subcmd, *args):
25632582
elif casematch(subcmd, b'exists'):
25642583
return [int(sha1 in self._server.script_cache) for sha1 in args]
25652584
elif casematch(subcmd, b'flush'):
2566-
if len(args) != 0:
2585+
if len(args) > 1 or (len(args) == 1 and casenorm(args[0]) not in {b'sync', b'async'}):
25672586
raise SimpleError(BAD_SUBCOMMAND_MSG.format('SCRIPT'))
25682587
self._server.script_cache = {}
25692588
return OK

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ hypothesis==5.41.4
2424
# via -r requirements.in
2525
iniconfig==1.1.1
2626
# via pytest
27-
lupa==1.9
27+
lupa==1.10
2828
# via -r requirements.in
2929
mccabe==0.6.1
3030
# via flake8
@@ -40,7 +40,7 @@ pyflakes==2.2.0
4040
# via flake8
4141
pyparsing==2.4.7
4242
# via packaging
43-
pytest==6.1.2
43+
pytest==6.2.5
4444
# via
4545
# -r requirements.in
4646
# pytest-asyncio

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ install_requires =
2626
# Minor version updates to redis tend to break fakeredis. If you
2727
# need to use fakeredis with a newer redis, please submit a PR that
2828
# relaxes this restriction and adds it to the Github Actions tests.
29-
redis<3.6.0
29+
redis<4.1.0
3030
six>=1.12
3131
sortedcontainers
3232
python_requires = >=3.5

test/test_fakeredis.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,22 @@ def test_lpop_wrong_type(r):
10411041
r.lpop('foo')
10421042

10431043

1044+
@pytest.mark.min_server('6.2')
1045+
def test_lpop_count(r):
1046+
assert r.rpush('foo', 'one') == 1
1047+
assert r.rpush('foo', 'two') == 2
1048+
assert r.rpush('foo', 'three') == 3
1049+
assert raw_command(r, 'lpop', 'foo', 2) == [b'one', b'two']
1050+
# See https://github.com/redis/redis/issues/9680
1051+
assert raw_command(r, 'lpop', 'foo', 0) is None
1052+
1053+
1054+
@pytest.mark.min_server('6.2')
1055+
def test_lpop_count_negative(r):
1056+
with pytest.raises(redis.ResponseError):
1057+
raw_command(r, 'lpop', 'foo', -1)
1058+
1059+
10441060
def test_lset(r):
10451061
r.rpush('foo', 'one')
10461062
r.rpush('foo', 'two')
@@ -1148,6 +1164,22 @@ def test_rpop_wrong_type(r):
11481164
r.rpop('foo')
11491165

11501166

1167+
@pytest.mark.min_server('6.2')
1168+
def test_rpop_count(r):
1169+
assert r.rpush('foo', 'one') == 1
1170+
assert r.rpush('foo', 'two') == 2
1171+
assert r.rpush('foo', 'three') == 3
1172+
assert raw_command(r, 'rpop', 'foo', 2) == [b'three', b'two']
1173+
# See https://github.com/redis/redis/issues/9680
1174+
assert raw_command(r, 'rpop', 'foo', 0) is None
1175+
1176+
1177+
@pytest.mark.min_server('6.2')
1178+
def test_rpop_count_negative(r):
1179+
with pytest.raises(redis.ResponseError):
1180+
raw_command(r, 'rpop', 'foo', -1)
1181+
1182+
11511183
def test_linsert_before(r):
11521184
r.rpush('foo', 'hello')
11531185
r.rpush('foo', 'world')
@@ -3454,6 +3486,10 @@ def test_save(r):
34543486

34553487
def test_bgsave(r):
34563488
assert r.bgsave()
3489+
with pytest.raises(ResponseError):
3490+
r.execute_command('BGSAVE', 'SCHEDULE', 'FOO')
3491+
with pytest.raises(ResponseError):
3492+
r.execute_command('BGSAVE', 'FOO')
34573493

34583494

34593495
def test_lastsave(r):
@@ -5004,10 +5040,10 @@ def test_from_url_with_db_arg(self):
50045040
assert db2.get('foo') == b'foo2'
50055041

50065042
def test_from_url_db_value_error(self):
5007-
# In ValueError, should default to 0
5043+
# In case of ValueError, should default to 0, or be absent in redis-py 4.0
50085044
db = fakeredis.FakeStrictRedis.from_url(
50095045
'redis://localhost:6379/a')
5010-
assert db.connection_pool.connection_kwargs['db'] == 0
5046+
assert db.connection_pool.connection_kwargs.get('db', 0) == 0
50115047

50125048
def test_can_pass_through_extra_args(self):
50135049
db = fakeredis.FakeStrictRedis.from_url(

test/test_hypothesis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def commands(*args, **kwargs):
239239
st.sampled_from(['before', 'after', 'BEFORE', 'AFTER']) | st.binary(),
240240
values, values)
241241
| commands(st.just('llen'), keys)
242-
| commands(st.sampled_from(['lpop', 'rpop']), keys)
242+
| commands(st.sampled_from(['lpop', 'rpop']), keys, st.just(None) | st.integers())
243243
| commands(st.sampled_from(['lpush', 'lpushx', 'rpush', 'rpushx']), keys, st.lists(values))
244244
| commands(st.just('lrange'), keys, counts, counts)
245245
| commands(st.just('lrem'), keys, counts, values)

0 commit comments

Comments
 (0)