From 82439e5e4ec6094ef7387db12b47592880f65a75 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 17:52:47 -0500 Subject: [PATCH 1/8] Fix gh-issue body --- scripts/create_issues.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/create_issues.py b/scripts/create_issues.py index 45d21c69..b2c43e43 100644 --- a/scripts/create_issues.py +++ b/scripts/create_issues.py @@ -64,11 +64,12 @@ def create_issue(self, group: str, cmd: str, summary: str): link = f"https://redis.io/commands/{cmd.replace(' ', '-')}/" title = f"Implement support for `{cmd.upper()}` ({group} command)" filename = f'{group}_mixin.py' - body = f"""Implement support for command `{cmd.upper()}` in {filename}. + body = f""" +Implement support for command `{cmd.upper()}` in {filename}. - {summary}. +{summary}. - Here is the [Official documentation]({link})""" +Here is the [Official documentation]({link})""" labels = [f'{group}-commands', 'enhancement', 'help wanted'] for label in labels: if label not in self.labels: From cd817513ab0cc80d891cc6bfe0def00d6665f9dc Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 18:46:05 -0500 Subject: [PATCH 2/8] implement getex --- fakeredis/commands_mixins/string_mixin.py | 33 ++++++++++++++++++ test/test_mixins/test_string_commands.py | 41 +++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/fakeredis/commands_mixins/string_mixin.py b/fakeredis/commands_mixins/string_mixin.py index ac35280c..d19f30fe 100644 --- a/fakeredis/commands_mixins/string_mixin.py +++ b/fakeredis/commands_mixins/string_mixin.py @@ -193,3 +193,36 @@ def strlen(self, key): @command((Key(bytes), Int, Int)) def substr(self, key, start, end): return self.getrange(key, start, end) + + @command((Key(bytes),), (bytes,)) + def getex(self, key, *args): + i, count_options, expire_time, diff = 0, 0, None, None + while i < len(args): + count_options += 1 + if casematch(args[i], b'ex') and i + 1 < len(args): + diff = Int.decode(args[i + 1]) + expire_time = self._db.time + diff + i += 2 + elif casematch(args[i], b'px') and i + 1 < len(args): + diff = Int.decode(args[i + 1]) + expire_time = (self._db.time * 1000 + diff) / 1000.0 + i += 2 + elif casematch(args[i], b'exat') and i + 1 < len(args): + expire_time = Int.decode(args[i + 1]) + i += 2 + elif casematch(args[i], b'pxat') and i + 1 < len(args): + expire_time = Int.decode(args[i + 1]) / 1000.0 + i += 2 + elif casematch(args[i], b'persist'): + expire_time = None + i += 1 + else: + raise SimpleError(msgs.SYNTAX_ERROR_MSG) + if ((expire_time is not None and (expire_time <= 0 or expire_time * 1000 >= 2 ** 63)) \ + or (diff is not None and (diff <= 0 or diff * 1000 >= 2 ** 63))): + raise SimpleError(msgs.INVALID_EXPIRE_MSG.format('getex')) + if count_options > 1: + raise SimpleError(msgs.SYNTAX_ERROR_MSG) + + key.expireat = expire_time + return key.get(None) diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py index 8bc351a2..6ce4e3ea 100644 --- a/test/test_mixins/test_string_commands.py +++ b/test/test_mixins/test_string_commands.py @@ -1,5 +1,6 @@ from __future__ import annotations +import time from datetime import timedelta import pytest @@ -476,3 +477,43 @@ def test_setitem_getitem(r): def test_getitem_non_existent_key(r): assert r.keys() == [] assert 'noexists' not in r.keys() + + +@pytest.mark.slow +def test_getex(r: redis.Redis): + # Exceptions + with pytest.raises(redis.ResponseError): + raw_command(r, 'getex', 'foo', 'px', 1000, 'ex', 1) + with pytest.raises(redis.ResponseError): + raw_command(r, 'getex', 'foo', 'dsac', 1000, 'ex', 1) + # with pytest.raises(redis.ResponseError): + # raw_command(r, 'getex', 'foo', 'ex', -10) + r.set('foo', 'val') + assert r.getex('foo', ex=1) == b'val' + assert r.ttl('foo') > 0 + time.sleep(1) + assert r.get('foo') is None + + r.set('foo', 'val') + assert r.getex('foo', px=1000) == b'val' + assert r.ttl('foo') > 0 + time.sleep(1) + assert r.get('foo') is None + + r.set('foo', 'val') + r.getex('foo', exat=int(time.time() + 1.5)) + assert r.ttl('foo') > 0 + time.sleep(1.5) + assert r.get('foo') is None + + r.set('foo', 'val') + r.getex('foo', pxat=int(time.time() + 1.4) * 1000) + assert r.ttl('foo') > 0 + time.sleep(1.4) + assert r.get('foo') is None + + r.setex('foo', 1, 'val') + r.getex('foo', persist=True) + assert r.ttl('foo') == -1 + time.sleep(1) + assert r.get('foo') == b'val' From 2ce91507139ecfc810c7c962b1fe5022f2f244dd Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 18:46:26 -0500 Subject: [PATCH 3/8] Add getex --- REDIS_COMMANDS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/REDIS_COMMANDS.md b/REDIS_COMMANDS.md index db7a1545..df16b3ed 100644 --- a/REDIS_COMMANDS.md +++ b/REDIS_COMMANDS.md @@ -14,6 +14,8 @@ list of [unimplemented commands](#unimplemented-commands). Get the value of a key * [GETDEL](https://redis.io/commands/getdel/) Get the value of a key and delete the key + * [GETEX](https://redis.io/commands/getex/) + Get the value of a key and optionally set its expiration * [GETRANGE](https://redis.io/commands/getrange/) Get a substring of the string stored at a key * [GETSET](https://redis.io/commands/getset/) @@ -730,16 +732,14 @@ All the redis commands are implemented in fakeredis with these exceptions: * [GEOSEARCHSTORE](https://redis.io/commands/geosearchstore/) Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key. -### string - * [GETEX](https://redis.io/commands/getex/) - Get the value of a key and optionally set its expiration - * [LCS](https://redis.io/commands/lcs/) - Find longest common substring - ### hash * [HRANDFIELD](https://redis.io/commands/hrandfield/) Get one or multiple random fields from a hash +### string + * [LCS](https://redis.io/commands/lcs/) + Find longest common substring + ### hyperloglog * [PFDEBUG](https://redis.io/commands/pfdebug/) Internal commands for debugging HyperLogLog values From f4a90b0343c4d7998ddd934ac0eef947e9abb793 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 18:53:50 -0500 Subject: [PATCH 4/8] Fix flake --- fakeredis/commands_mixins/string_mixin.py | 2 +- test/test_mixins/test_server_commands.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/fakeredis/commands_mixins/string_mixin.py b/fakeredis/commands_mixins/string_mixin.py index d19f30fe..7b39605f 100644 --- a/fakeredis/commands_mixins/string_mixin.py +++ b/fakeredis/commands_mixins/string_mixin.py @@ -218,7 +218,7 @@ def getex(self, key, *args): i += 1 else: raise SimpleError(msgs.SYNTAX_ERROR_MSG) - if ((expire_time is not None and (expire_time <= 0 or expire_time * 1000 >= 2 ** 63)) \ + if ((expire_time is not None and (expire_time <= 0 or expire_time * 1000 >= 2 ** 63)) or (diff is not None and (diff <= 0 or diff * 1000 >= 2 ** 63))): raise SimpleError(msgs.INVALID_EXPIRE_MSG.format('getex')) if count_options > 1: diff --git a/test/test_mixins/test_server_commands.py b/test/test_mixins/test_server_commands.py index c92bc8da..9859a224 100644 --- a/test/test_mixins/test_server_commands.py +++ b/test/test_mixins/test_server_commands.py @@ -4,8 +4,6 @@ import pytest from redis.exceptions import ResponseError -from .. import testtools - def test_swapdb(r, create_redis): r1 = create_redis(1) From 8ca62b9b98a37182006bbc6437db5237ee635cee Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 19:47:36 -0500 Subject: [PATCH 5/8] Fix tests --- test/test_mixins/test_string_commands.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py index 6ce4e3ea..1425933f 100644 --- a/test/test_mixins/test_string_commands.py +++ b/test/test_mixins/test_string_commands.py @@ -486,8 +486,8 @@ def test_getex(r: redis.Redis): raw_command(r, 'getex', 'foo', 'px', 1000, 'ex', 1) with pytest.raises(redis.ResponseError): raw_command(r, 'getex', 'foo', 'dsac', 1000, 'ex', 1) - # with pytest.raises(redis.ResponseError): - # raw_command(r, 'getex', 'foo', 'ex', -10) + with pytest.raises(redis.ResponseError): + r.getex('foo', ex=-1) r.set('foo', 'val') assert r.getex('foo', ex=1) == b'val' assert r.ttl('foo') > 0 @@ -501,15 +501,13 @@ def test_getex(r: redis.Redis): assert r.get('foo') is None r.set('foo', 'val') - r.getex('foo', exat=int(time.time() + 1.5)) - assert r.ttl('foo') > 0 - time.sleep(1.5) + r.getex('foo', exat=int(time.time() + 1)) + time.sleep(1) assert r.get('foo') is None r.set('foo', 'val') - r.getex('foo', pxat=int(time.time() + 1.4) * 1000) - assert r.ttl('foo') > 0 - time.sleep(1.4) + r.getex('foo', pxat=int(time.time() + 1) * 1000) + time.sleep(1) assert r.get('foo') is None r.setex('foo', 1, 'val') From 764817dc745655c9a53366c80eda6432bc4ca3a6 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 20:12:20 -0500 Subject: [PATCH 6/8] Fix tests --- test/test_mixins/test_string_commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py index 1425933f..437e322c 100644 --- a/test/test_mixins/test_string_commands.py +++ b/test/test_mixins/test_string_commands.py @@ -486,8 +486,7 @@ def test_getex(r: redis.Redis): raw_command(r, 'getex', 'foo', 'px', 1000, 'ex', 1) with pytest.raises(redis.ResponseError): raw_command(r, 'getex', 'foo', 'dsac', 1000, 'ex', 1) - with pytest.raises(redis.ResponseError): - r.getex('foo', ex=-1) + r.set('foo', 'val') assert r.getex('foo', ex=1) == b'val' assert r.ttl('foo') > 0 From 709e5d9eef65b33eede14c25a10750fad7344af5 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 20:36:10 -0500 Subject: [PATCH 7/8] Fix tests --- test/test_mixins/test_string_commands.py | 28 +++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py index 437e322c..78faf583 100644 --- a/test/test_mixins/test_string_commands.py +++ b/test/test_mixins/test_string_commands.py @@ -489,28 +489,26 @@ def test_getex(r: redis.Redis): r.set('foo', 'val') assert r.getex('foo', ex=1) == b'val' - assert r.ttl('foo') > 0 time.sleep(1) assert r.get('foo') is None - r.set('foo', 'val') - assert r.getex('foo', px=1000) == b'val' - assert r.ttl('foo') > 0 + r.set('foo2', 'val') + assert r.getex('foo2', px=1000) == b'val' time.sleep(1) - assert r.get('foo') is None + assert r.get('foo2') is None - r.set('foo', 'val') - r.getex('foo', exat=int(time.time() + 1)) + r.set('foo4', 'val') + r.getex('foo4', exat=int(time.time() + 1)) time.sleep(1) - assert r.get('foo') is None + assert r.get('foo4') is None - r.set('foo', 'val') - r.getex('foo', pxat=int(time.time() + 1) * 1000) + r.set('foo2', 'val') + r.getex('foo2', pxat=int(time.time() + 1) * 1000) time.sleep(1) - assert r.get('foo') is None + assert r.get('foo2') is None - r.setex('foo', 1, 'val') - r.getex('foo', persist=True) - assert r.ttl('foo') == -1 + r.setex('foo5', 1, 'val') + r.getex('foo5', persist=True) + assert r.ttl('foo5') == -1 time.sleep(1) - assert r.get('foo') == b'val' + assert r.get('foo5') == b'val' From ee48d8013a509c77f63f6c4f88216ab58804513e Mon Sep 17 00:00:00 2001 From: Daniel M Date: Mon, 12 Dec 2022 20:49:22 -0500 Subject: [PATCH 8/8] Fix tests --- test/test_mixins/test_string_commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_mixins/test_string_commands.py b/test/test_mixins/test_string_commands.py index 78faf583..03754e31 100644 --- a/test/test_mixins/test_string_commands.py +++ b/test/test_mixins/test_string_commands.py @@ -489,26 +489,26 @@ def test_getex(r: redis.Redis): r.set('foo', 'val') assert r.getex('foo', ex=1) == b'val' - time.sleep(1) + time.sleep(1.5) assert r.get('foo') is None r.set('foo2', 'val') assert r.getex('foo2', px=1000) == b'val' - time.sleep(1) + time.sleep(1.5) assert r.get('foo2') is None r.set('foo4', 'val') r.getex('foo4', exat=int(time.time() + 1)) - time.sleep(1) + time.sleep(1.5) assert r.get('foo4') is None r.set('foo2', 'val') r.getex('foo2', pxat=int(time.time() + 1) * 1000) - time.sleep(1) + time.sleep(1.5) assert r.get('foo2') is None r.setex('foo5', 1, 'val') r.getex('foo5', persist=True) assert r.ttl('foo5') == -1 - time.sleep(1) + time.sleep(1.5) assert r.get('foo5') == b'val'