Skip to content

Commit 5f61ad9

Browse files
authored
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
2 parents 53a9f6b + f1169b5 commit 5f61ad9

File tree

3 files changed

+51
-9
lines changed

3 files changed

+51
-9
lines changed

redis/client.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727

2828
SYM_EMPTY = b('')
29+
EMPTY_RESPONSE = 'EMPTY_RESPONSE'
2930

3031

3132
def list_or_args(keys, args):
@@ -757,7 +758,12 @@ def execute_command(self, *args, **options):
757758

758759
def parse_response(self, connection, command_name, **options):
759760
"Parses a response from the Redis server"
760-
response = connection.read_response()
761+
try:
762+
response = connection.read_response()
763+
except ResponseError:
764+
if EMPTY_RESPONSE in options:
765+
return options[EMPTY_RESPONSE]
766+
raise
761767
if command_name in self.response_callbacks:
762768
return self.response_callbacks[command_name](response, **options)
763769
return response
@@ -1120,7 +1126,10 @@ def mget(self, keys, *args):
11201126
Returns a list of values ordered identically to ``keys``
11211127
"""
11221128
args = list_or_args(keys, args)
1123-
return self.execute_command('MGET', *args)
1129+
options = {}
1130+
if not args:
1131+
options[EMPTY_RESPONSE] = []
1132+
return self.execute_command('MGET', *args, **options)
11241133

11251134
def mset(self, *args, **kwargs):
11261135
"""
@@ -3214,7 +3223,8 @@ def pipeline_execute_command(self, *args, **options):
32143223

32153224
def _execute_transaction(self, connection, commands, raise_on_error):
32163225
cmds = chain([(('MULTI', ), {})], commands, [(('EXEC', ), {})])
3217-
all_cmds = connection.pack_commands([args for args, _ in cmds])
3226+
all_cmds = connection.pack_commands([args for args, options in cmds
3227+
if EMPTY_RESPONSE not in options])
32183228
connection.send_packed_command(all_cmds)
32193229
errors = []
32203230

@@ -3229,12 +3239,15 @@ def _execute_transaction(self, connection, commands, raise_on_error):
32293239

32303240
# and all the other commands
32313241
for i, command in enumerate(commands):
3232-
try:
3233-
self.parse_response(connection, '_')
3234-
except ResponseError:
3235-
ex = sys.exc_info()[1]
3236-
self.annotate_exception(ex, i + 1, command[0])
3237-
errors.append((i, ex))
3242+
if EMPTY_RESPONSE in command[1]:
3243+
errors.append((i, command[1][EMPTY_RESPONSE]))
3244+
else:
3245+
try:
3246+
self.parse_response(connection, '_')
3247+
except ResponseError:
3248+
ex = sys.exc_info()[1]
3249+
self.annotate_exception(ex, i + 1, command[0])
3250+
errors.append((i, ex))
32383251

32393252
# parse the EXEC.
32403253
try:

tests/test_commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def test_keys(self, r):
427427
assert set(r.keys(pattern='test*')) == keys
428428

429429
def test_mget(self, r):
430+
assert r.mget([]) == []
430431
assert r.mget(['a', 'b']) == [None, None]
431432
r['a'] = '1'
432433
r['b'] = '2'

tests/test_pipeline.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ def test_exec_error_raised(self, r):
114114
assert pipe.set('z', 'zzz').execute() == [True]
115115
assert r['z'] == b('zzz')
116116

117+
def test_transaction_with_empty_error_command(self, r):
118+
"""
119+
Commands with custom EMPTY_ERROR functionality return their default
120+
values in the pipeline no matter the raise_on_error preference
121+
"""
122+
for error_switch in (True, False):
123+
with r.pipeline() as pipe:
124+
pipe.set('a', 1).mget([]).set('c', 3)
125+
result = pipe.execute(raise_on_error=error_switch)
126+
127+
assert result[0]
128+
assert result[1] == []
129+
assert result[2]
130+
131+
def test_pipeline_with_empty_error_command(self, r):
132+
"""
133+
Commands with custom EMPTY_ERROR functionality return their default
134+
values in the pipeline no matter the raise_on_error preference
135+
"""
136+
for error_switch in (True, False):
137+
with r.pipeline(transaction=False) as pipe:
138+
pipe.set('a', 1).mget([]).set('c', 3)
139+
result = pipe.execute(raise_on_error=error_switch)
140+
141+
assert result[0]
142+
assert result[1] == []
143+
assert result[2]
144+
117145
def test_parse_error_raised(self, r):
118146
with r.pipeline() as pipe:
119147
# the zrem is invalid because we don't pass any keys to it

0 commit comments

Comments
 (0)