summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChayim I. Kirshen <c@kirshen.com>2022-11-29 12:39:52 +0200
committerChayim I. Kirshen <c@kirshen.com>2022-11-29 12:39:52 +0200
commit199634ea195a45cd511fd709f887eebc68cfb9d2 (patch)
treea31206071e553f80094cb9dee602fbde5b841bc0
parent596244fbc4828ad4848df470ab0e16f4663b903f (diff)
downloadredis-py-199634ea195a45cd511fd709f887eebc68cfb9d2.tar.gz
small improvements
-rw-r--r--redis/asyncio/connection.py2
-rw-r--r--redis/cluster.py2
-rw-r--r--redis/parsers/commands.py185
-rw-r--r--redis/parsers/encoders.py4
-rw-r--r--redis/parsers/hiredis.py11
-rw-r--r--redis/parsers/protocol.py12
-rw-r--r--redis/parsers/resp2.py2
-rw-r--r--redis/utils.py2
-rw-r--r--tests/test_encoding.py2
9 files changed, 109 insertions, 113 deletions
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
index 6275632..4faee34 100644
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -40,7 +40,7 @@ from redis.exceptions import (
ResponseError,
TimeoutError,
)
-from redis.typing import EncodableT, EncodedT
+from redis.typing import EncodableT
from redis.utils import HIREDIS_AVAILABLE, str_if_bytes
from ..parsers import AsyncHiredisParser, AsyncRESP2Parser, BaseParser, Encoder
diff --git a/redis/cluster.py b/redis/cluster.py
index 245dfdb..3fcae73 100644
--- a/redis/cluster.py
+++ b/redis/cluster.py
@@ -9,7 +9,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from redis.backoff import default_backoff
from redis.client import CaseInsensitiveDict, PubSub, Redis, parse_scan
from redis.commands import READ_COMMANDS, RedisClusterCommands
-from redis.connection import ConnectionPool, DefaultParser, Encoder, parse_url
+from redis.connection import ConnectionPool, DefaultParser, parse_url
from redis.crc import REDIS_CLUSTER_HASH_SLOTS, key_slot
from redis.exceptions import (
AskError,
diff --git a/redis/parsers/commands.py b/redis/parsers/commands.py
index 7981a52..73c45ba 100644
--- a/redis/parsers/commands.py
+++ b/redis/parsers/commands.py
@@ -7,94 +7,6 @@ if TYPE_CHECKING:
from redis.asyncio.cluster import ClusterNode
-class AsyncCommandsParser:
- """
- Parses Redis commands to get command keys.
-
- COMMAND output is used to determine key locations.
- Commands that do not have a predefined key location are flagged with 'movablekeys',
- and these commands' keys are determined by the command 'COMMAND GETKEYS'.
-
- NOTE: Due to a bug in redis<7.0, this does not work properly
- for EVAL or EVALSHA when the `numkeys` arg is 0.
- - issue: https://github.com/redis/redis/issues/9493
- - fix: https://github.com/redis/redis/pull/9733
-
- So, don't use this with EVAL or EVALSHA.
- """
-
- __slots__ = ("commands", "node")
-
- def __init__(self) -> None:
- self.commands: Dict[str, Union[int, Dict[str, Any]]] = {}
-
- async def initialize(self, node: Optional["ClusterNode"] = None) -> None:
- if node:
- self.node = node
-
- commands = await self.node.execute_command("COMMAND")
- for cmd, command in commands.items():
- if "movablekeys" in command["flags"]:
- commands[cmd] = -1
- elif command["first_key_pos"] == 0 and command["last_key_pos"] == 0:
- commands[cmd] = 0
- elif command["first_key_pos"] == 1 and command["last_key_pos"] == 1:
- commands[cmd] = 1
- self.commands = {cmd.upper(): command for cmd, command in commands.items()}
-
- # As soon as this PR is merged into Redis, we should reimplement
- # our logic to use COMMAND INFO changes to determine the key positions
- # https://github.com/redis/redis/pull/8324
- async def get_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
- if len(args) < 2:
- # The command has no keys in it
- return None
-
- try:
- command = self.commands[args[0]]
- except KeyError:
- # try to split the command name and to take only the main command
- # e.g. 'memory' for 'memory usage'
- args = args[0].split() + list(args[1:])
- cmd_name = args[0].upper()
- if cmd_name not in self.commands:
- # We'll try to reinitialize the commands cache, if the engine
- # version has changed, the commands may not be current
- await self.initialize()
- if cmd_name not in self.commands:
- raise RedisError(
- f"{cmd_name} command doesn't exist in Redis commands"
- )
-
- command = self.commands[cmd_name]
-
- if command == 1:
- return (args[1],)
- if command == 0:
- return None
- if command == -1:
- return await self._get_moveable_keys(*args)
-
- last_key_pos = command["last_key_pos"]
- if last_key_pos < 0:
- last_key_pos = len(args) + last_key_pos
- return args[command["first_key_pos"] : last_key_pos + 1 : command["step_count"]]
-
- async def _get_moveable_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
- try:
- keys = await self.node.execute_command("COMMAND GETKEYS", *args)
- except ResponseError as e:
- message = e.__str__()
- if (
- "Invalid arguments" in message
- or "The command has no key arguments" in message
- ):
- return None
- else:
- raise e
- return keys
-
-
class CommandsParser:
"""
Parses Redis commands to get command keys.
@@ -109,7 +21,7 @@ class CommandsParser:
self.initialize(redis_connection)
def initialize(self, r):
- commands = r.execute_command("COMMAND")
+ commands = r.command()
uppercase_commands = []
for cmd in commands:
if any(x.isupper() for x in cmd):
@@ -210,14 +122,11 @@ class CommandsParser:
So, don't use this function with EVAL or EVALSHA.
"""
- pieces = []
- cmd_name = args[0]
# The command name should be splitted into separate arguments,
# e.g. 'MEMORY USAGE' will be splitted into ['MEMORY', 'USAGE']
- pieces = pieces + cmd_name.split()
- pieces = pieces + list(args[1:])
+ pieces = args[0].split() + list(args[1:])
try:
- keys = redis_conn.execute_command("COMMAND GETKEYS", *pieces)
+ keys = redis_conn.command_getkeys(*pieces)
except ResponseError as e:
message = e.__str__()
if (
@@ -257,3 +166,91 @@ class CommandsParser:
# PUBLISH channel message
keys = [args[1]]
return keys
+
+
+class AsyncCommandsParser:
+ """
+ Parses Redis commands to get command keys.
+
+ COMMAND output is used to determine key locations.
+ Commands that do not have a predefined key location are flagged with 'movablekeys',
+ and these commands' keys are determined by the command 'COMMAND GETKEYS'.
+
+ NOTE: Due to a bug in redis<7.0, this does not work properly
+ for EVAL or EVALSHA when the `numkeys` arg is 0.
+ - issue: https://github.com/redis/redis/issues/9493
+ - fix: https://github.com/redis/redis/pull/9733
+
+ So, don't use this with EVAL or EVALSHA.
+ """
+
+ __slots__ = ("commands", "node")
+
+ def __init__(self) -> None:
+ self.commands: Dict[str, Union[int, Dict[str, Any]]] = {}
+
+ async def initialize(self, node: Optional["ClusterNode"] = None) -> None:
+ if node:
+ self.node = node
+
+ commands = await self.node.command()
+ for cmd, command in commands.items():
+ if "movablekeys" in command["flags"]:
+ commands[cmd] = -1
+ elif command["first_key_pos"] == 0 and command["last_key_pos"] == 0:
+ commands[cmd] = 0
+ elif command["first_key_pos"] == 1 and command["last_key_pos"] == 1:
+ commands[cmd] = 1
+ self.commands = {cmd.upper(): command for cmd, command in commands.items()}
+
+ # As soon as this PR is merged into Redis, we should reimplement
+ # our logic to use COMMAND INFO changes to determine the key positions
+ # https://github.com/redis/redis/pull/8324
+ async def get_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
+ if len(args) < 2:
+ # The command has no keys in it
+ return None
+
+ try:
+ command = self.commands[args[0]]
+ except KeyError:
+ # try to split the command name and to take only the main command
+ # e.g. 'memory' for 'memory usage'
+ args = args[0].split() + list(args[1:])
+ cmd_name = args[0].upper()
+ if cmd_name not in self.commands:
+ # We'll try to reinitialize the commands cache, if the engine
+ # version has changed, the commands may not be current
+ await self.initialize()
+ if cmd_name not in self.commands:
+ raise RedisError(
+ f"{cmd_name} command doesn't exist in Redis commands"
+ )
+
+ command = self.commands[cmd_name]
+
+ if command == 1:
+ return (args[1],)
+ if command == 0:
+ return None
+ if command == -1:
+ return await self._get_moveable_keys(*args)
+
+ last_key_pos = command["last_key_pos"]
+ if last_key_pos < 0:
+ last_key_pos = len(args) + last_key_pos
+ return args[command["first_key_pos"] : last_key_pos + 1 : command["step_count"]]
+
+ async def _get_moveable_keys(self, *args: Any) -> Optional[Tuple[str, ...]]:
+ try:
+ keys = await self.node.command_getkeys(*args)
+ except ResponseError as e:
+ message = e.__str__()
+ if (
+ "Invalid arguments" in message
+ or "The command has no key arguments" in message
+ ):
+ return None
+ else:
+ raise e
+ return keys
diff --git a/redis/parsers/encoders.py b/redis/parsers/encoders.py
index 85fb292..60e8b1b 100644
--- a/redis/parsers/encoders.py
+++ b/redis/parsers/encoders.py
@@ -1,12 +1,10 @@
-from redis.typing import EncodableT, EncodedT
-
from ..exceptions import DataError
class Encoder:
"Encode strings to bytes-like and decode bytes-like to strings"
- def __init__(self, encoding, encoding_errors, decode_responses):
+ def __init__(self, encoding, encoding_errors, decode_responses: bool):
self.encoding = encoding
self.encoding_errors = encoding_errors
self.decode_responses = decode_responses
diff --git a/redis/parsers/hiredis.py b/redis/parsers/hiredis.py
index 44d5b81..93f05ba 100644
--- a/redis/parsers/hiredis.py
+++ b/redis/parsers/hiredis.py
@@ -148,12 +148,10 @@ class AsyncHiredisParser(AsyncBaseParser):
if not HIREDIS_AVAILABLE:
raise RedisError("Hiredis is not available.")
- import hiredis
-
super().__init__(socket_read_size=socket_read_size)
- self._reader: Optional[hiredis.Reader] = None
+ self._reader = None
- def on_connect(self, connection: "Connection"):
+ def on_connect(self, connection):
import hiredis
self._stream = connection._reader
@@ -201,7 +199,10 @@ class AsyncHiredisParser(AsyncBaseParser):
response = self._reader.gets()
while response is False:
await self.read_from_socket()
- response = self._reader.gets()
+ if disable_decoding:
+ response = self._reader.gets(False)
+ else:
+ response = self._reader.gets()
# if the response is a ConnectionError or the response is a list and
# the first item is a ConnectionError, raise it as something bad
diff --git a/redis/parsers/protocol.py b/redis/parsers/protocol.py
index 6faaddd..22c89ba 100644
--- a/redis/parsers/protocol.py
+++ b/redis/parsers/protocol.py
@@ -76,6 +76,12 @@ class BaseParser(ABC):
return exception_class(response)
return ResponseError(response)
+ def on_disconnect(self):
+ raise NotImplementedError()
+
+ def on_connect(self, connection):
+ raise NotImplementedError()
+
class AsyncBaseParser(BaseParser):
"""Base parsing class for the python-backed async parser"""
@@ -92,12 +98,6 @@ class AsyncBaseParser(BaseParser):
except Exception:
pass
- def on_disconnect(self):
- raise NotImplementedError()
-
- def on_connect(self, connection: "Connection"):
- raise NotImplementedError()
-
async def can_read_destructive(self) -> bool:
raise NotImplementedError()
diff --git a/redis/parsers/resp2.py b/redis/parsers/resp2.py
index 32872ea..9a47040 100644
--- a/redis/parsers/resp2.py
+++ b/redis/parsers/resp2.py
@@ -103,7 +103,7 @@ class AsyncRESP2Parser(AsyncBaseParser):
super().__init__(socket_read_size)
self.encoder: Optional[Encoder] = None
- def on_connect(self, connection: "Connection"):
+ def on_connect(self, connection):
"""Called when the stream connects"""
self._stream = connection._reader
if self._stream is None:
diff --git a/redis/utils.py b/redis/utils.py
index fa2d407..1171ed0 100644
--- a/redis/utils.py
+++ b/redis/utils.py
@@ -11,7 +11,7 @@ except ImportError:
HIREDIS_AVAILABLE = False
try:
- import ssl
+ import ssl # noqa
SSL_AVAILABLE = True
except ImportError:
diff --git a/tests/test_encoding.py b/tests/test_encoding.py
index ee52e4c..e1e33f8 100644
--- a/tests/test_encoding.py
+++ b/tests/test_encoding.py
@@ -97,7 +97,7 @@ class TestCommandsAreNotEncoded:
class TestInvalidUserInput:
-
+
def test_none_fails(self, r):
with pytest.raises(redis.DataError):
r.set("a", None)