summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy McCurdy <andy@andymccurdy.com>2018-11-04 01:09:37 -0700
committerAndy McCurdy <andy@andymccurdy.com>2018-11-04 01:09:37 -0700
commitf0f0192356ab45479101b6b9098cda10d58f24ff (patch)
tree298efa08787f943b941289ecd8d63aea6f5b4db9
parent53a9f6b048de885bb672abaa4a4a019e1849cc13 (diff)
downloadredis-py-f0f0192356ab45479101b6b9098cda10d58f24ff.tar.gz
hook for graceful command failure, even in pipelines
allow commands that expect 1 or more keys to fail gracefully when 0 keys are provided
-rwxr-xr-xredis/client.py31
-rw-r--r--tests/test_commands.py1
-rw-r--r--tests/test_pipeline.py14
3 files changed, 37 insertions, 9 deletions
diff --git a/redis/client.py b/redis/client.py
index a597d3a..e1f1103 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -26,6 +26,7 @@ from redis.exceptions import (
)
SYM_EMPTY = b('')
+EMPTY_ERROR = 'EMPTY_ERROR'
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_ERROR in options:
+ return options[EMPTY_ERROR]
+ 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_ERROR] = []
+ 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_ERROR 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_ERROR in command[1]:
+ errors.append((i, command[1][EMPTY_ERROR]))
+ 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..8de08d2 100644
--- a/tests/test_pipeline.py
+++ b/tests/test_pipeline.py
@@ -114,6 +114,20 @@ class TestPipeline(object):
assert pipe.set('z', 'zzz').execute() == [True]
assert r['z'] == b('zzz')
+ def test_command_with_on_error_option_returns_default_value(self, r):
+ """
+ Commands with custom ON_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_parse_error_raised(self, r):
with r.pipeline() as pipe:
# the zrem is invalid because we don't pass any keys to it