diff options
author | Andy McCurdy <andy@andymccurdy.com> | 2018-11-05 12:27:41 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-05 12:27:41 -0800 |
commit | 5f61ad9135b649225d4a944211e96e297d56f585 (patch) | |
tree | ba620c666615f52c5442adce6d060bc3d32b0256 | |
parent | 53a9f6b048de885bb672abaa4a4a019e1849cc13 (diff) | |
parent | f1169b5fbae225032cdca69ed801f880e3ab83df (diff) | |
download | redis-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-x | redis/client.py | 31 | ||||
-rw-r--r-- | tests/test_commands.py | 1 | ||||
-rw-r--r-- | tests/test_pipeline.py | 28 |
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 |