From 86eb834271299ea9359f91c2a559f8425fc1b7e3 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:09:56 -0400 Subject: [PATCH 1/8] Implement XREAD #141 --- docs/redis-commands/Redis.md | 8 ++-- fakeredis/_command_args_parsing.py | 2 +- fakeredis/_stream.py | 13 ++++-- fakeredis/commands_mixins/list_mixin.py | 2 - fakeredis/commands_mixins/streams_mixin.py | 46 ++++++++++++++++++++-- test/test_mixins/test_streams_commands.py | 10 ++++- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/docs/redis-commands/Redis.md b/docs/redis-commands/Redis.md index d5625b93..f5f7b252 100644 --- a/docs/redis-commands/Redis.md +++ b/docs/redis-commands/Redis.md @@ -1485,6 +1485,10 @@ Return the number of messages in a stream. Returns the messages from a stream within a range of IDs. +### [XREAD](https://redis.io/commands/xread/) + +Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise. + ### [XREVRANGE](https://redis.io/commands/xrevrange/) Returns the messages from a stream within a range of IDs in reverse order. @@ -1565,10 +1569,6 @@ Returns information about a stream. Returns the information and entries from a stream consumer group's pending entries list. -#### [XREAD](https://redis.io/commands/xread/) (not implemented) - -Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise. - #### [XREADGROUP](https://redis.io/commands/xreadgroup/) (not implemented) Returns new or historical messages from a stream for a consumer in agroup. Blocks until a message is available otherwise. diff --git a/fakeredis/_command_args_parsing.py b/fakeredis/_command_args_parsing.py index f9544901..af620081 100644 --- a/fakeredis/_command_args_parsing.py +++ b/fakeredis/_command_args_parsing.py @@ -126,7 +126,7 @@ def _parse_params( while i < len(actual_args): found = False for key in args_info: - if null_terminate(actual_args[i]).lower() == key: + if null_terminate(actual_args[i]) == key: arg_position, _ = args_info[key] results[arg_position], parsed = _parse_params(key, i, actual_args) i += parsed diff --git a/fakeredis/_stream.py b/fakeredis/_stream.py index 3db85ade..9ee4fe04 100644 --- a/fakeredis/_stream.py +++ b/fakeredis/_stream.py @@ -23,14 +23,14 @@ def parse_id(id_str: str): return timestamp, sequence @classmethod - def decode(cls, value): + def decode(cls, value, exclusive=False): if value == b'-': return cls(BeforeAny(), True) elif value == b'+': return cls(AfterAny(), True) elif value[:1] == b'(': return cls(cls.parse_id(value[1:]), True) - return cls(cls.parse_id(value), False) + return cls(cls.parse_id(value), exclusive) class XStream: @@ -88,10 +88,14 @@ def find_index(self, id_str: str) -> Tuple[int, bool]: ind = bisect.bisect_left(list(map(lambda x: x[0], self._values)), ts_seq) return ind, self._values[ind][0] == ts_seq + @staticmethod + def _encode_id(record): + return f'{record[0][0]}-{record[0][1]}'.encode() + @staticmethod def _format_record(record): results = list(record[1:][0]) - return [f'{record[0][0]}-{record[0][1]}'.encode(), results] + return [XStream._encode_id(record), results] def trim(self, maxlen: Optional[int] = None, @@ -125,3 +129,6 @@ def match(record): if reverse: return list(reversed(tuple(matches))) return list(matches) + + def last_item_key(self): + XStream._encode_id(self._values[-1]) diff --git a/fakeredis/commands_mixins/list_mixin.py b/fakeredis/commands_mixins/list_mixin.py index 9cb3f214..ebece148 100644 --- a/fakeredis/commands_mixins/list_mixin.py +++ b/fakeredis/commands_mixins/list_mixin.py @@ -36,8 +36,6 @@ def _list_pop(get_slice, key, *args): class ListCommandsMixin: - # List commands - def _bpop_pass(self, keys, op, first_pass): for key in keys: item = CommandItem(key, self._db, item=self._db.get(key), default=[]) diff --git a/fakeredis/commands_mixins/streams_mixin.py b/fakeredis/commands_mixins/streams_mixin.py index 4cd9c884..dcc1c494 100644 --- a/fakeredis/commands_mixins/streams_mixin.py +++ b/fakeredis/commands_mixins/streams_mixin.py @@ -1,14 +1,16 @@ +import functools +from typing import List + import fakeredis._msgs as msgs from fakeredis._command_args_parsing import extract_args -from fakeredis._commands import Key, command -from fakeredis._helpers import SimpleError +from fakeredis._commands import Key, command, CommandItem +from fakeredis._helpers import SimpleError, casematch from fakeredis._stream import XStream, StreamRangeTest class StreamsCommandsMixin: @command(name="XADD", fixed=(Key(),), repeat=(bytes,), ) def xadd(self, key, *args): - (nomkstream, limit, maxlen, minid), left_args = extract_args( args, ('nomkstream', '+limit', '~+maxlen', '~minid'), error_on_unexpected=False) if nomkstream and key.value is None: @@ -71,3 +73,41 @@ def xrange(self, key, _min, _max, *args): def xrevrange(self, key, _min, _max, *args): (count,), _ = extract_args(args, ('+count',)) return self._xrange(key, _max, _min, True, count) + + def _xread(self, stream_start_id_list: List, count: int, first_pass: bool): + max_inf = StreamRangeTest.decode(b'+') + res = list() + for (item, start_id) in stream_start_id_list: + stream_results = self._xrange(item, start_id, max_inf, False, count) + if first_pass and (count is None or len(stream_results) < count): + raise SimpleError(msgs.WRONGTYPE_MSG) + if len(stream_results) > 0: + res.append([item.key, stream_results]) + return res + + @staticmethod + def _parse_start_id(key: CommandItem, s: bytes) -> StreamRangeTest: + if s == b'$': + return StreamRangeTest.decode(key.value.last_item_key(), exclusive=True) + return StreamRangeTest.decode(s, exclusive=True) + + @command(name="XREAD", fixed=(bytes,), repeat=(bytes,)) + def xread(self, *args): + (count, timeout,), left_args = extract_args(args, ('+count', '+block',), error_on_unexpected=False) + if (len(left_args) < 3 + or not casematch(left_args[0], b'STREAMS') + or len(left_args) % 2 != 1): + raise SimpleError(msgs.SYNTAX_ERROR_MSG) + left_args = left_args[1:] + num_streams = int(len(left_args) / 2) + + stream_start_id_list = list() + for i in range(num_streams): + item = CommandItem(left_args[i], self._db, item=self._db.get(left_args[i]), default=None) + start_id = self._parse_start_id(item, left_args[i + num_streams]) + stream_start_id_list.append((item, start_id,)) + if timeout is None: + return self._xread(stream_start_id_list, count, False) + else: + return self._blocking(timeout, functools.partial(self._xread(stream_start_id_list, count))) + diff --git a/test/test_mixins/test_streams_commands.py b/test/test_mixins/test_streams_commands.py index 57b5f298..daf76685 100644 --- a/test/test_mixins/test_streams_commands.py +++ b/test/test_mixins/test_streams_commands.py @@ -220,7 +220,6 @@ def get_stream_message(client, stream, message_id): return response[0] -@pytest.mark.xfail # TODO def test_xread(r): stream = "stream" m1 = r.xadd(stream, {"foo": "bar"}) @@ -245,3 +244,12 @@ def test_xread(r): # xread starting at the last message returns an empty list assert r.xread(streams={stream: m2}) == [] + + +def test_xread_bad_commands(r): + with pytest.raises(redis.ResponseError) as exc_info: + testtools.raw_command(r, 'xread', 'foo', '11-1') + print(exc_info) + with pytest.raises(redis.ResponseError) as ex2: + testtools.raw_command(r, 'xread', 'streams', 'foo', ) + print(ex2) From e69dd801e3a8103d81fa199f1cabc1b41b121695 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:18:42 -0400 Subject: [PATCH 2/8] Formatting --- fakeredis/commands_mixins/bitmap_mixin.py | 1 - fakeredis/commands_mixins/connection_mixin.py | 4 +- fakeredis/commands_mixins/generic_mixin.py | 2 +- fakeredis/commands_mixins/hash_mixin.py | 4 +- fakeredis/commands_mixins/list_mixin.py | 9 ++-- fakeredis/commands_mixins/pubsub_mixin.py | 1 - fakeredis/commands_mixins/scripting_mixin.py | 4 +- fakeredis/commands_mixins/server_mixin.py | 6 +-- fakeredis/commands_mixins/streams_mixin.py | 1 - fakeredis/commands_mixins/string_mixin.py | 50 +++++++++---------- 10 files changed, 38 insertions(+), 44 deletions(-) diff --git a/fakeredis/commands_mixins/bitmap_mixin.py b/fakeredis/commands_mixins/bitmap_mixin.py index daf3fa66..f16ddbe5 100644 --- a/fakeredis/commands_mixins/bitmap_mixin.py +++ b/fakeredis/commands_mixins/bitmap_mixin.py @@ -4,7 +4,6 @@ class BitmapCommandsMixin: - # BITMAP commands # TODO: bitfield, bitfield_ro, bitpos @staticmethod def _bytes_as_bin_string(value): diff --git a/fakeredis/commands_mixins/connection_mixin.py b/fakeredis/commands_mixins/connection_mixin.py index c756e158..772df87d 100644 --- a/fakeredis/commands_mixins/connection_mixin.py +++ b/fakeredis/commands_mixins/connection_mixin.py @@ -1,6 +1,6 @@ from fakeredis import _msgs as msgs -from fakeredis._commands import command, DbIndex -from fakeredis._helpers import SimpleError, OK, SimpleString +from fakeredis._commands import (command, DbIndex) +from fakeredis._helpers import (SimpleError, OK, SimpleString) PONG = SimpleString(b'PONG') diff --git a/fakeredis/commands_mixins/generic_mixin.py b/fakeredis/commands_mixins/generic_mixin.py index 33e9791c..a0382f55 100644 --- a/fakeredis/commands_mixins/generic_mixin.py +++ b/fakeredis/commands_mixins/generic_mixin.py @@ -7,7 +7,7 @@ from fakeredis._commands import ( command, Key, Int, DbIndex, BeforeAny, CommandItem, SortFloat, delete_keys, key_value_type, ) -from fakeredis._helpers import compile_pattern, SimpleError, OK, casematch +from fakeredis._helpers import (compile_pattern, SimpleError, OK, casematch) from fakeredis._zset import ZSet diff --git a/fakeredis/commands_mixins/hash_mixin.py b/fakeredis/commands_mixins/hash_mixin.py index dfe0958e..5226f408 100644 --- a/fakeredis/commands_mixins/hash_mixin.py +++ b/fakeredis/commands_mixins/hash_mixin.py @@ -2,8 +2,8 @@ import math from fakeredis import _msgs as msgs -from fakeredis._commands import command, Key, Hash, Int, Float -from fakeredis._helpers import SimpleError, OK +from fakeredis._commands import (command, Key, Hash, Int, Float) +from fakeredis._helpers import (SimpleError, OK) class HashCommandsMixin: diff --git a/fakeredis/commands_mixins/list_mixin.py b/fakeredis/commands_mixins/list_mixin.py index ebece148..5686b42f 100644 --- a/fakeredis/commands_mixins/list_mixin.py +++ b/fakeredis/commands_mixins/list_mixin.py @@ -1,17 +1,14 @@ import functools from fakeredis import _msgs as msgs -from fakeredis._commands import ( - Key, command, Int, CommandItem, Timeout, fix_range) -from fakeredis._helpers import ( - OK, SimpleError, SimpleString, casematch) +from fakeredis._commands import (Key, command, Int, CommandItem, Timeout, fix_range) +from fakeredis._helpers import (OK, SimpleError, SimpleString, casematch) def _list_pop(get_slice, key, *args): """Implements lpop and rpop. - `get_slice` must take a count and return a slice expression for the - range to pop. + `get_slice` must take a count and return a slice expression for the range to pop. """ # This implementation is somewhat contorted to match the odd # behaviours described in https://github.com/redis/redis/issues/9680. diff --git a/fakeredis/commands_mixins/pubsub_mixin.py b/fakeredis/commands_mixins/pubsub_mixin.py index b04b2b70..4f4e5de9 100644 --- a/fakeredis/commands_mixins/pubsub_mixin.py +++ b/fakeredis/commands_mixins/pubsub_mixin.py @@ -4,7 +4,6 @@ class PubSubCommandsMixin: - # Pubsub commands def __init__(self, *args, **kwargs): super(PubSubCommandsMixin, self).__init__(*args, **kwargs) self._pubsub = 0 # Count of subscriptions diff --git a/fakeredis/commands_mixins/scripting_mixin.py b/fakeredis/commands_mixins/scripting_mixin.py index 443bfd40..afd7aaef 100644 --- a/fakeredis/commands_mixins/scripting_mixin.py +++ b/fakeredis/commands_mixins/scripting_mixin.py @@ -4,8 +4,8 @@ import logging from fakeredis import _msgs as msgs -from fakeredis._commands import command, Int -from fakeredis._helpers import SimpleError, SimpleString, null_terminate, OK, encode_command +from fakeredis._commands import (command, Int) +from fakeredis._helpers import (SimpleError, SimpleString, null_terminate, OK, encode_command) LOGGER = logging.getLogger('fakeredis') REDIS_LOG_LEVELS = { diff --git a/fakeredis/commands_mixins/server_mixin.py b/fakeredis/commands_mixins/server_mixin.py index e96dfc79..3532d844 100644 --- a/fakeredis/commands_mixins/server_mixin.py +++ b/fakeredis/commands_mixins/server_mixin.py @@ -46,9 +46,9 @@ def save(self): @command(()) def time(self): - now_us = round(time.time() * 1000000) - now_s = now_us // 1000000 - now_us %= 1000000 + now_us = round(time.time() * 1_000_000) + now_s = now_us // 1_000_000 + now_us %= 1_000_000 return [str(now_s).encode(), str(now_us).encode()] @command((DbIndex, DbIndex)) diff --git a/fakeredis/commands_mixins/streams_mixin.py b/fakeredis/commands_mixins/streams_mixin.py index dcc1c494..cc10fd58 100644 --- a/fakeredis/commands_mixins/streams_mixin.py +++ b/fakeredis/commands_mixins/streams_mixin.py @@ -110,4 +110,3 @@ def xread(self, *args): return self._xread(stream_start_id_list, count, False) else: return self._blocking(timeout, functools.partial(self._xread(stream_start_id_list, count))) - diff --git a/fakeredis/commands_mixins/string_mixin.py b/fakeredis/commands_mixins/string_mixin.py index 931f8e25..0312fc11 100644 --- a/fakeredis/commands_mixins/string_mixin.py +++ b/fakeredis/commands_mixins/string_mixin.py @@ -17,45 +17,45 @@ def _lcs(s1, s2): pi = [[0] * (l2 + 1) for _ in range(0, l1 + 1)] # Algorithm to calculate the length of the longest common subsequence - for i in range(1, l1 + 1): - for j in range(1, l2 + 1): - if s1[i - 1] == s2[j - 1]: - opt[i][j] = opt[i - 1][j - 1] + 1 - pi[i][j] = 0 - elif opt[i][j - 1] >= opt[i - 1][j]: - opt[i][j] = opt[i][j - 1] - pi[i][j] = 1 + for r in range(1, l1 + 1): + for c in range(1, l2 + 1): + if s1[r - 1] == s2[c - 1]: + opt[r][c] = opt[r - 1][c - 1] + 1 + pi[r][c] = 0 + elif opt[r][c - 1] >= opt[r - 1][c]: + opt[r][c] = opt[r][c - 1] + pi[r][c] = 1 else: - opt[i][j] = opt[i - 1][j] - pi[i][j] = 2 + opt[r][c] = opt[r - 1][c] + pi[r][c] = 2 # Length of the longest common subsequence is saved at opt[n][m] # Algorithm to calculate the longest common subsequence using the Pi array # Also calculate the list of matches - i, j = l1, l2 + r, c = l1, l2 result = '' matches = list() s1ind, s2ind, curr_length = None, None, 0 - while i > 0 and j > 0: - if pi[i][j] == 0: - result = chr(s1[i - 1]) + result - i -= 1 - j -= 1 + while r > 0 and c > 0: + if pi[r][c] == 0: + result = chr(s1[r - 1]) + result + r -= 1 + c -= 1 curr_length += 1 - elif pi[i][j] == 2: - i -= 1 + elif pi[r][c] == 2: + r -= 1 else: - j -= 1 + c -= 1 - if pi[i][j] == 0 and curr_length == 1: - s1ind = i - s2ind = j - elif pi[i][j] > 0 and curr_length > 0: - matches.append([[i, s1ind], [j, s2ind], curr_length]) + if pi[r][c] == 0 and curr_length == 1: + s1ind = r + s2ind = c + elif pi[r][c] > 0 and curr_length > 0: + matches.append([[r, s1ind], [c, s2ind], curr_length]) s1ind, s2ind, curr_length = None, None, 0 if curr_length: - matches.append([[s1ind, i], [s2ind, j], curr_length]) + matches.append([[s1ind, r], [s2ind, c], curr_length]) return opt[l1][l2], result.encode(), matches From 47a53d78bd63385a6ca972e8127254f75fa8d989 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:19:58 -0400 Subject: [PATCH 3/8] Fix blocking --- fakeredis/commands_mixins/streams_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fakeredis/commands_mixins/streams_mixin.py b/fakeredis/commands_mixins/streams_mixin.py index cc10fd58..a4e1b734 100644 --- a/fakeredis/commands_mixins/streams_mixin.py +++ b/fakeredis/commands_mixins/streams_mixin.py @@ -109,4 +109,4 @@ def xread(self, *args): if timeout is None: return self._xread(stream_start_id_list, count, False) else: - return self._blocking(timeout, functools.partial(self._xread(stream_start_id_list, count))) + return self._blocking(timeout, functools.partial(self._xread, stream_start_id_list, count)) From 73cacb82e5e27541a078f1485bf3efae3dd39b6c Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:40:02 -0400 Subject: [PATCH 4/8] fix error type --- fakeredis/commands_mixins/scripting_mixin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fakeredis/commands_mixins/scripting_mixin.py b/fakeredis/commands_mixins/scripting_mixin.py index afd7aaef..55db9497 100644 --- a/fakeredis/commands_mixins/scripting_mixin.py +++ b/fakeredis/commands_mixins/scripting_mixin.py @@ -175,8 +175,10 @@ def eval(self, script, numkeys, *keys_and_args): try: result = lua_runtime.execute(script) except SimpleError as ex: - if self.version == 6: + if self.version <= 6: raise SimpleError(msgs.SCRIPT_ERROR_MSG.format(sha1.decode(), ex)) + if ex.value.startswith('ERR wrong number of arguments'): + raise SimpleError('Wrong number of args calling Redis command from script') raise SimpleError(ex.value) except LuaError as ex: raise SimpleError(msgs.SCRIPT_ERROR_MSG.format(sha1.decode(), ex)) From 26c80a6825f1ed1d95d36fca5f5a798c9efdf050 Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:43:36 -0400 Subject: [PATCH 5/8] fix error type --- fakeredis/commands_mixins/scripting_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fakeredis/commands_mixins/scripting_mixin.py b/fakeredis/commands_mixins/scripting_mixin.py index 55db9497..febb1cb4 100644 --- a/fakeredis/commands_mixins/scripting_mixin.py +++ b/fakeredis/commands_mixins/scripting_mixin.py @@ -84,6 +84,8 @@ def _convert_redis_result(self, lua_runtime, result): ] return lua_runtime.table_from(converted) elif isinstance(result, SimpleError): + if result.value.startswith('ERR wrong number of arguments'): + raise SimpleError(msgs.WRONG_ARGS_MSG7) raise result else: raise RuntimeError("Unexpected return type from redis: {}".format(type(result))) @@ -177,8 +179,6 @@ def eval(self, script, numkeys, *keys_and_args): except SimpleError as ex: if self.version <= 6: raise SimpleError(msgs.SCRIPT_ERROR_MSG.format(sha1.decode(), ex)) - if ex.value.startswith('ERR wrong number of arguments'): - raise SimpleError('Wrong number of args calling Redis command from script') raise SimpleError(ex.value) except LuaError as ex: raise SimpleError(msgs.SCRIPT_ERROR_MSG.format(sha1.decode(), ex)) From 594766e8b05228200bbaaa2de494ad919e93749b Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 19:49:53 -0400 Subject: [PATCH 6/8] update dependencies and documentation --- docs/about/changelog.md | 18 ++++++++++++++++ poetry.lock | 47 +++++++++++++++++++++-------------------- pyproject.toml | 2 +- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 01353516..0975f245 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,24 @@ description: Change log of all fakeredis releases ## Next release +## v2.12.0 + +### 🚀 Features +- Implement `XREAD` #147 + +## v2.11.2 + +### 🧰 Bug Fixes + +- Unique FakeServer when no connection params are provided (#142) + +## v2.11.1 + +### 🧰 Maintenance + +- Minor fixes supporting multiple connections +- Update documentation + ## v2.11.0 ### 🚀 Features diff --git a/poetry.lock b/poetry.lock index eb0a8d1d..2512ff2b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,14 +70,14 @@ files = [ [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] @@ -401,14 +401,14 @@ files = [ [[package]] name = "docker" -version = "6.0.1" +version = "6.1.0" description = "A Python library for the Docker Engine API." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "docker-6.0.1-py3-none-any.whl", hash = "sha256:dbcb3bd2fa80dca0788ed908218bf43972772009b881ed1e20dfc29a65e49782"}, - {file = "docker-6.0.1.tar.gz", hash = "sha256:896c4282e5c7af5c45e8b683b0b0c33932974fe6e50fc6906a0a83616ab3da97"}, + {file = "docker-6.1.0-py3-none-any.whl", hash = "sha256:b65c999f87cb5c31700b6944dc17a631071170d1aab3ad6e23506068579f885d"}, + {file = "docker-6.1.0.tar.gz", hash = "sha256:cb697eccfeff55d232f7a7f4f88cd3770d27327c38d6c266b8f55c9f14a8491e"}, ] [package.dependencies] @@ -483,14 +483,14 @@ pyflakes = ">=3.0.0,<3.1.0" [[package]] name = "hypothesis" -version = "6.75.1" +version = "6.75.2" description = "A library for property-based testing" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "hypothesis-6.75.1-py3-none-any.whl", hash = "sha256:f67e4925e26cbee4561b492e3845f68559b8a51b08cfaef9b6cf6f6c40c5e091"}, - {file = "hypothesis-6.75.1.tar.gz", hash = "sha256:7940ae975ab48f86d36cedfd0eeccb8e37e99746785f7d6bb17b4a8ec4ec07a8"}, + {file = "hypothesis-6.75.2-py3-none-any.whl", hash = "sha256:29fee1fabf764eb021665d20e633ef7ba931c5841de20f9ff3e01e8a512d6a4d"}, + {file = "hypothesis-6.75.2.tar.gz", hash = "sha256:50c270b38d724ce0fefa71b8e3511d123f7a31a9af9ea003447d4e1489292fbc"}, ] [package.dependencies] @@ -1171,21 +1171,21 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "requests" -version = "2.29.0" +version = "2.30.0" description = "Python HTTP for Humans." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"}, - {file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"}, + {file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"}, + {file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -1414,14 +1414,14 @@ cryptography = ">=35.0.0" [[package]] name = "types-redis" -version = "4.5.4.1" +version = "4.5.4.2" description = "Typing stubs for redis" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-redis-4.5.4.1.tar.gz", hash = "sha256:bf04192f415b2b42ecefd70bb4b91eb0352e48f2716a213e038e35c096a639c2"}, - {file = "types_redis-4.5.4.1-py3-none-any.whl", hash = "sha256:2db530f54facec3149147bfe61d5ac24f5fe4e871823d95a601cd2c1d775d8a0"}, + {file = "types-redis-4.5.4.2.tar.gz", hash = "sha256:7979ce406cd7b4a0093b10a377e5060c5c890e8463cd1ef50423c1669efbc075"}, + {file = "types_redis-4.5.4.2-py3-none-any.whl", hash = "sha256:b6f7e44aae1a79732f694cb6df6093e38361382d2be03780460684ef59745d62"}, ] [package.dependencies] @@ -1442,20 +1442,21 @@ files = [ [[package]] name = "urllib3" -version = "1.26.15" +version = "2.0.2" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, + {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, + {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" diff --git a/pyproject.toml b/pyproject.toml index 54d89148..a14ca270 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ name = "fakeredis" packages = [ { include = "fakeredis" }, ] -version = "2.11.2" +version = "2.12.0" description = "Fake implementation of redis API for testing purposes." readme = "README.md" keywords = ["redis", "RedisJson", ] From 8aaa1620f8ea190bbd9cde66edfc10ac4f5a129c Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 20:03:49 -0400 Subject: [PATCH 7/8] Update versions --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 8 ++++---- test/test_redis_asyncio.py | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dcd1a6bc..69747711 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79bf9821..72fbaddd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-python@v4 with: cache-dependency-path: poetry.lock - python-version: "3.10" + python-version: "3.11" - name: Install dependencies env: PYTHON_KEYRING_BACKEND: keyring.backends.null.Keyring @@ -51,15 +51,15 @@ jobs: max-parallel: 8 fail-fast: false matrix: - redis-image: [ "redis:6.2.10", "redis:7.0.7" ] + redis-image: [ "redis:6.2.12", "redis:7.0.11" ] python-version: [ "3.7", "3.8", "3.10", "3.11" ] redis-py: [ "4.3.6", "4.4.4", "4.5.4" ] include: - - python-version: "3.10" + - python-version: "3.11" redis-image: "redis:6.2.10" redis-py: "4.5.4" lupa: true - - python-version: "3.10" + - python-version: "3.11" redis-image: "redis/redis-stack:7.0.6-RC3" redis-py: "4.5.4" lupa: true diff --git a/test/test_redis_asyncio.py b/test/test_redis_asyncio.py index bcec07b2..ac50c4b7 100644 --- a/test/test_redis_asyncio.py +++ b/test/test_redis_asyncio.py @@ -225,8 +225,7 @@ async def test_failed_script_error6(self, req_aioredis2): @pytest.mark.min_server('7') async def test_failed_script_error7(self, req_aioredis2): await req_aioredis2.set('foo', 'bar') - with pytest.raises(redis.asyncio.ResponseError, - match='^Wrong number of args calling Redis command from script'): + with pytest.raises(redis.asyncio.ResponseError): await req_aioredis2.eval('return redis.call("ZCOUNT", KEYS[1])', 1, 'foo') From 7f6f54990bb61c5704720a812c0bb8beb850653b Mon Sep 17 00:00:00 2001 From: Daniel M Date: Sun, 7 May 2023 20:05:32 -0400 Subject: [PATCH 8/8] Update versions --- docs/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index fee3f099..dacaaa5a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -mkdocs==1.4.2 -mkdocs-material==9.1.8 +mkdocs==1.4.3 +mkdocs-material==9.1.9