From 6b69b11dd9b5133898455feb9842a3254e57d06e Mon Sep 17 00:00:00 2001 From: Oleksandr Tykhonruk Date: Sat, 6 Jul 2019 15:25:13 +0300 Subject: [PATCH 1/5] Support for `bzpopmax` and `bzpopmin` commands (server 5.0.0) --- aioredis/commands/sorted_set.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/aioredis/commands/sorted_set.py b/aioredis/commands/sorted_set.py index fbc546b6d..58e77d25a 100644 --- a/aioredis/commands/sorted_set.py +++ b/aioredis/commands/sorted_set.py @@ -18,6 +18,34 @@ class SortedSetCommandsMixin: ZSET_IF_NOT_EXIST = 'ZSET_IF_NOT_EXIST' # NX ZSET_IF_EXIST = 'ZSET_IF_EXIST' # XX + def bzpopmax(self, key, *keys, timeout=0, encoding=_NOTSET): + """Remove and get an element with the highest score in the sorted set, + or block until one is available. + + :raises TypeError: if timeout is not int + :raises ValueError: if timeout is less than 0 + """ + if not isinstance(timeout, int): + raise TypeError("timeout argument must be int") + if timeout < 0: + raise ValueError("timeout must be greater equal 0") + args = keys + (timeout,) + return self.execute(b'BZPOPMAX', key, *args, encoding=encoding) + + def bzpopmin(self, key, *keys, timeout=0, encoding=_NOTSET): + """Remove and get an element with the lowest score in the sorted set, + or block until one is available. + + :raises TypeError: if timeout is not int + :raises ValueError: if timeout is less than 0 + """ + if not isinstance(timeout, int): + raise TypeError("timeout argument must be int") + if timeout < 0: + raise ValueError("timeout must be greater equal 0") + args = keys + (timeout,) + return self.execute(b'BZPOPMIN', key, *args, encoding=encoding) + def zadd(self, key, score, member, *pairs, exist=None): """Add one or more members to a sorted set or update its score. From 964fb1ff46b29d71a10b19f27afd5c421e5894ce Mon Sep 17 00:00:00 2001 From: Oleksandr Tykhonruk Date: Sat, 6 Jul 2019 15:28:04 +0300 Subject: [PATCH 2/5] Support for `INCR` and `CH` arguments of the `ZADD` command. --- aioredis/commands/sorted_set.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/aioredis/commands/sorted_set.py b/aioredis/commands/sorted_set.py index 58e77d25a..1df2ab986 100644 --- a/aioredis/commands/sorted_set.py +++ b/aioredis/commands/sorted_set.py @@ -46,7 +46,8 @@ def bzpopmin(self, key, *keys, timeout=0, encoding=_NOTSET): args = keys + (timeout,) return self.execute(b'BZPOPMIN', key, *args, encoding=encoding) - def zadd(self, key, score, member, *pairs, exist=None): + def zadd(self, key, score, member, *pairs, exist=None, changed=False, + incr=False): """Add one or more members to a sorted set or update its score. :raises TypeError: score not int or float @@ -67,6 +68,15 @@ def zadd(self, key, score, member, *pairs, exist=None): elif exist is self.ZSET_IF_NOT_EXIST: args.append(b'NX') + if changed: + args.append(b'CH') + + if incr: + if pairs: + raise ValueError('only one score-element pair ' + 'can be specified in this mode') + args.append(b'INCR') + args.extend([score, member]) if pairs: args.extend(pairs) From e9f8d9326be7c7825f18f1a187adcbb5785f54d8 Mon Sep 17 00:00:00 2001 From: Oleksandr Tykhonruk Date: Mon, 8 Jul 2019 12:25:59 +0300 Subject: [PATCH 3/5] Tests for the `bzpopmax`/`bzpopmin` commands and `zadd` options. --- tests/sorted_set_commands_test.py | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/sorted_set_commands_test.py b/tests/sorted_set_commands_test.py index 84b47fc6d..319f766e6 100644 --- a/tests/sorted_set_commands_test.py +++ b/tests/sorted_set_commands_test.py @@ -5,6 +5,52 @@ from _testutils import redis_version +@redis_version(5, 0, 0, reason='BZPOPMAX is available since redis>=5.0.0') +@pytest.mark.run_loop +async def test_bzpopmax(redis): + key1 = b'key:zpopmax:1' + key2 = b'key:zpopmax:2' + + pairs = [ + (0, b'a'), (5, b'c'), (2, b'd'), (8, b'e'), (9, b'f'), (3, b'g') + ] + await redis.zadd(key1, *pairs[0]) + await redis.zadd(key2, *itertools.chain.from_iterable(pairs)) + + res = await redis.bzpopmax(key1, timeout=0) + assert res == [key1, b'a', b'0'] + res = await redis.bzpopmax(key1, key2, timeout=0) + assert res == [key2, b'f', b'9'] + + with pytest.raises(TypeError): + await redis.bzpopmax(key1, timeout=b'one') + with pytest.raises(ValueError): + await redis.bzpopmax(key2, timeout=-10) + + +@redis_version(5, 0, 0, reason='BZPOPMIN is available since redis>=5.0.0') +@pytest.mark.run_loop +async def test_bzpopmin(redis): + key1 = b'key:zpopmin:1' + key2 = b'key:zpopmin:2' + + pairs = [ + (0, b'a'), (5, b'c'), (2, b'd'), (8, b'e'), (9, b'f'), (3, b'g') + ] + await redis.zadd(key1, *pairs[0]) + await redis.zadd(key2, *itertools.chain.from_iterable(pairs)) + + res = await redis.bzpopmin(key1, timeout=0) + assert res == [key1, b'a', b'0'] + res = await redis.bzpopmin(key1, key2, timeout=0) + assert res == [key2, b'a', b'0'] + + with pytest.raises(TypeError): + await redis.bzpopmin(key1, timeout=b'one') + with pytest.raises(ValueError): + await redis.bzpopmin(key2, timeout=-10) + + @pytest.mark.run_loop async def test_zadd(redis): key = b'key:zadd' @@ -22,6 +68,11 @@ async def test_zadd(redis): res = await redis.zrange(key, 0, -1, withscores=False) assert res == [b'one', b'uno', b'two', b'three', b'four'] + res = await redis.zadd(key, 1, b'two', changed=True) + assert res == 1 + res = await redis.zadd(key, 1, b'two', incr=True) + assert int(res) == 2 + with pytest.raises(TypeError): await redis.zadd(None, 1, b'one') with pytest.raises(TypeError): @@ -30,6 +81,8 @@ async def test_zadd(redis): await redis.zadd(key, 3, b'three', 4) with pytest.raises(TypeError): await redis.zadd(key, 3, b'three', 'four', 4) + with pytest.raises(ValueError): + await redis.zadd(key, 1, b'one', 2, b'two', incr=True) @redis_version( From 5626e46caec1592da76fef10d80c03754dcd7893 Mon Sep 17 00:00:00 2001 From: Oleksandr Tykhonruk Date: Mon, 8 Jul 2019 13:27:16 +0300 Subject: [PATCH 4/5] Tests reorganization. --- tests/sorted_set_commands_test.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/sorted_set_commands_test.py b/tests/sorted_set_commands_test.py index 319f766e6..b02119461 100644 --- a/tests/sorted_set_commands_test.py +++ b/tests/sorted_set_commands_test.py @@ -68,11 +68,6 @@ async def test_zadd(redis): res = await redis.zrange(key, 0, -1, withscores=False) assert res == [b'one', b'uno', b'two', b'three', b'four'] - res = await redis.zadd(key, 1, b'two', changed=True) - assert res == 1 - res = await redis.zadd(key, 1, b'two', incr=True) - assert int(res) == 2 - with pytest.raises(TypeError): await redis.zadd(None, 1, b'one') with pytest.raises(TypeError): @@ -81,8 +76,6 @@ async def test_zadd(redis): await redis.zadd(key, 3, b'three', 4) with pytest.raises(TypeError): await redis.zadd(key, 3, b'three', 'four', 4) - with pytest.raises(ValueError): - await redis.zadd(key, 1, b'one', 2, b'two', incr=True) @redis_version( @@ -122,6 +115,15 @@ async def test_zadd_options(redis): res = await redis.zrange(key, 0, -1, withscores=False) assert res == [b'one', b'two'] + res = await redis.zadd(key, 1, b'two', changed=True) + assert res == 1 + + res = await redis.zadd(key, 1, b'two', incr=True) + assert int(res) == 2 + + with pytest.raises(ValueError): + await redis.zadd(key, 1, b'one', 2, b'two', incr=True) + @pytest.mark.run_loop async def test_zcard(redis): From 209bb0e7b749d6706572e6786e7dc6f500aa7719 Mon Sep 17 00:00:00 2001 From: Oleksandr Tykhonruk Date: Sat, 13 Jul 2019 15:27:19 +0300 Subject: [PATCH 5/5] Updated contributors list and added change description. --- CHANGES/618.feature | 4 ++++ CONTRIBUTORS.txt | 1 + 2 files changed, 5 insertions(+) create mode 100644 CHANGES/618.feature diff --git a/CHANGES/618.feature b/CHANGES/618.feature new file mode 100644 index 000000000..f10db1430 --- /dev/null +++ b/CHANGES/618.feature @@ -0,0 +1,4 @@ +A few additions to the sorted set commands: + +- the blocking pop commands: `BZPOPMAX` and `BZPOPMIN` +- the `CH` and `INCR` options of the `ZADD` command \ No newline at end of file diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 53257133d..c668d8490 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -31,6 +31,7 @@ Maxim Dodonchuk Michael Käufl Nickolai Novik Oleg Butuzov +Oleksandr Tykhonruk Pau Freixes Paul Colomiets Samuel Colvin