summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy McCurdy <andy@andymccurdy.com>2018-11-05 12:27:41 -0800
committerGitHub <noreply@github.com>2018-11-05 12:27:41 -0800
commit5f61ad9135b649225d4a944211e96e297d56f585 (patch)
treeba620c666615f52c5442adce6d060bc3d32b0256
parent53a9f6b048de885bb672abaa4a4a019e1849cc13 (diff)
parentf1169b5fbae225032cdca69ed801f880e3ab83df (diff)
downloadredis-py-5f61ad9135b649225d4a944211e96e297d56f585.tar.gz
Merge pull request #1052 from andymccurdy/error_defaults
Fail gracefully with a default return value when 0 keys are are provided to a command expecting at least 1 key
-rwxr-xr-xredis/client.py31
-rw-r--r--tests/test_commands.py1
-rw-r--r--tests/test_pipeline.py28
3 files changed, 51 insertions, 9 deletions
diff --git a/redis/client.py b/redis/client.py
index a597d3a..13aa689 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -26,6 +26,7 @@ from redis.exceptions import (
)
SYM_EMPTY = b('')
+EMPTY_RESPONSE = 'EMPTY_RESPONSE'
def list_or_args(keys, args):
@@ -757,7 +758,12 @@ class StrictRedis(object):
def parse_response(self, connection, command_name, **options):
"Parses a response from the Redis server"
- response = connection.read_response()
+ try:
+ response = connection.read_response()
+ except ResponseError:
+ if EMPTY_RESPONSE in options:
+ return options[EMPTY_RESPONSE]
+ raise
if command_name in self.response_callbacks:
return self.response_callbacks[command_name](response, **options)
return response
@@ -1120,7 +1126,10 @@ class StrictRedis(object):
Returns a list of values ordered identically to ``keys``
"""
args = list_or_args(keys, args)
- return self.execute_command('MGET', *args)
+ options = {}
+ if not args:
+ options[EMPTY_RESPONSE] = []
+ return self.execute_command('MGET', *args, **options)
def mset(self, *args, **kwargs):
"""
@@ -3214,7 +3223,8 @@ class BasePipeline(object):
def _execute_transaction(self, connection, commands, raise_on_error):
cmds = chain([(('MULTI', ), {})], commands, [(('EXEC', ), {})])
- all_cmds = connection.pack_commands([args for args, _ in cmds])
+ all_cmds = connection.pack_commands([args for args, options in cmds
+ if EMPTY_RESPONSE not in options])
connection.send_packed_command(all_cmds)
errors = []
@@ -3229,12 +3239,15 @@ class BasePipeline(object):
# and all the other commands
for i, command in enumerate(commands):
- try:
- self.parse_response(connection, '_')
- except ResponseError:
- ex = sys.exc_info()[1]
- self.annotate_exception(ex, i + 1, command[0])
- errors.append((i, ex))
+ if EMPTY_RESPONSE in command[1]:
+ errors.append((i, command[1][EMPTY_RESPONSE]))
+ else:
+ try:
+ self.parse_response(connection, '_')
+ except ResponseError:
+ ex = sys.exc_info()[1]
+ self.annotate_exception(ex, i + 1, command[0])
+ errors.append((i, ex))
# parse the EXEC.
try:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 6394a7a..b6636b0 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -427,6 +427,7 @@ class TestRedisCommands(object):
assert set(r.keys(pattern='test*')) == keys
def test_mget(self, r):
+ assert r.mget([]) == []
assert r.mget(['a', 'b']) == [None, None]
r['a'] = '1'
r['b'] = '2'
diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py
index 46fc994..1f3947e 100644
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -114,6 +114,34 @@ class TestPipeline(object):
assert pipe.set('z', 'zzz').execute() == [True]
assert r['z'] == b('zzz')
+ def test_transaction_with_empty_error_command(self, r):
+ """
+ Commands with custom EMPTY_ERROR functionality return their default
+ values in the pipeline no matter the raise_on_error preference
+ """
+ for error_switch in (True, False):
+ with r.pipeline() as pipe:
+ pipe.set('a', 1).mget([]).set('c', 3)
+ result = pipe.execute(raise_on_error=error_switch)
+
+ assert result[0]
+ assert result[1] == []
+ assert result[2]
+
+ def test_pipeline_with_empty_error_command(self, r):
+ """
+ Commands with custom EMPTY_ERROR functionality return their default
+ values in the pipeline no matter the raise_on_error preference
+ """
+ for error_switch in (True, False):
+ with r.pipeline(transaction=False) as pipe:
+ pipe.set('a', 1).mget([]).set('c', 3)
+ result = pipe.execute(raise_on_error=error_switch)
+
+ assert result[0]
+ assert result[1] == []
+ assert result[2]
+
def test_parse_error_raised(self, r):
with r.pipeline() as pipe:
# the zrem is invalid because we don't pass any keys to it