From 9825b4112212005f508438105dedc6c82853b4ee Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 9 Jul 2018 19:13:45 +0900 Subject: [PATCH 01/14] fix return object --- pymemcache/client/base.py | 10 ++++------ pymemcache/client/hash.py | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 7ac2224d..52492979 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -309,17 +309,14 @@ def set_many(self, values, expire=0, noreply=None): self.default_noreply). Returns: - If no exception is raised, always returns True. Otherwise all, some - or none of the keys have been successfully set. If noreply is True - then a successful return does not guarantee that any keys were - successfully set (just that the keys were successfully sent). + Empty list """ # TODO: make this more performant by sending all the values first, then # waiting for all the responses. for key, value in six.iteritems(values): self.set(key, value, expire, noreply) - return True + return [] set_multi = set_many @@ -930,7 +927,8 @@ def set(self, key, value, expire=0, noreply=None): def set_many(self, values, expire=0, noreply=None): with self.client_pool.get_and_release(destroy_on_fail=True) as client: - return client.set_many(values, expire=expire, noreply=noreply) + client.set_many(values, expire=expire, noreply=noreply) + return [] set_multi = set_many diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index 9aa95ace..d78dfc17 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -241,13 +241,13 @@ def decr(self, key, *args, **kwargs): def set_many(self, values, *args, **kwargs): client_batches = {} - end = [] + failed = [] for key, value in values.items(): client = self._get_client(key) if client is None: - end.append(False) + failed.append(key) continue if client.server not in client_batches: @@ -261,11 +261,11 @@ def set_many(self, values, *args, **kwargs): new_args.insert(0, values) result = self._safely_run_func( client, - client.set_many, False, *new_args, **kwargs + client.set_many, values.keys(), *new_args, **kwargs ) - end.append(result) + failed.extend(result) - return all(end) + return failed set_multi = set_many From b18ce057ed0f0c54c6a6e4159f1f125e102fc680 Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 9 Jul 2018 19:31:48 +0900 Subject: [PATCH 02/14] fix test --- pymemcache/test/test_client.py | 20 +++++++++++++++----- pymemcache/test/test_client_hash.py | 2 +- pymemcache/test/utils.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pymemcache/test/test_client.py b/pymemcache/test/test_client.py index 2c173ead..b5e9c9f9 100644 --- a/pymemcache/test/test_client.py +++ b/pymemcache/test/test_client.py @@ -156,13 +156,13 @@ def test_set_noreply(self): def test_set_many_success(self): client = self.make_client([b'STORED\r\n']) result = client.set_many({b'key': b'value'}, noreply=False) - assert result is True + assert result == [] def test_set_multi_success(self): # Should just map to set_many client = self.make_client([b'STORED\r\n']) result = client.set_multi({b'key': b'value'}, noreply=False) - assert result is True + assert result == [] def test_add_stored(self): client = self.make_client([b'STORED\r', b'\n']) @@ -602,7 +602,7 @@ def _set(): def test_set_many_socket_handling(self): client = self.make_client([b'STORED\r\n']) result = client.set_many({b'key': b'value'}, noreply=False) - assert result is True + assert result == [] assert client.sock.closed is False assert len(client.sock.send_bufs) == 1 @@ -738,6 +738,11 @@ def _default_noreply_true(self, cmd, args, response): result = getattr(client, cmd)(*args) assert result is True + def _default_noreply_true_and_empty_list(self, cmd, args, response): + client = self.make_client(response, default_noreply=True) + result = getattr(client, cmd)(*args) + assert result == [] + def test_default_noreply_set(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( @@ -749,7 +754,7 @@ def test_default_noreply_set_many(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) - self._default_noreply_true( + self._default_noreply_true_and_empty_list( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) def test_default_noreply_add(self): @@ -854,6 +859,11 @@ def _default_noreply_true(self, cmd, args, response): result = getattr(client, cmd)(*args) assert result is True + def _default_noreply_true_and_empty_list(self, cmd, args, response): + client = self.make_client(response, default_noreply=True) + result = getattr(client, cmd)(*args) + assert result == [] + def test_default_noreply_set(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( @@ -865,7 +875,7 @@ def test_default_noreply_set_many(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) - self._default_noreply_true( + self._default_noreply_true_and_empty_list( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) def test_default_noreply_add(self): diff --git a/pymemcache/test/test_client_hash.py b/pymemcache/test/test_client_hash.py index e8437d3d..3f711c8e 100644 --- a/pymemcache/test/test_client_hash.py +++ b/pymemcache/test/test_client_hash.py @@ -200,7 +200,7 @@ def test_no_servers_left_with_set_many(self): ) result = client.set_many({'foo': 'bar'}) - assert result is False + assert result == ['foo'] def test_no_servers_left_with_get_many(self): from pymemcache.client.hash import HashClient diff --git a/pymemcache/test/utils.py b/pymemcache/test/utils.py index e453a622..7325f2d9 100644 --- a/pymemcache/test/utils.py +++ b/pymemcache/test/utils.py @@ -105,7 +105,7 @@ def set(self, key, value, expire=0, noreply=True): def set_many(self, values, expire=None, noreply=True): for key, value in six.iteritems(values): self.set(key, value, expire, noreply) - return True + return [] set_multi = set_many From 210d7382e12c75d339b3976db9b6c85a3905b031 Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 9 Jul 2018 19:35:42 +0900 Subject: [PATCH 03/14] fix pep8 --- pymemcache/client/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 52492979..5694c6bd 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -309,7 +309,7 @@ def set_many(self, values, expire=0, noreply=None): self.default_noreply). Returns: - Empty list + Empty list """ # TODO: make this more performant by sending all the values first, then From 1d99958432ceee039d026abed87bd5f88255ad77 Mon Sep 17 00:00:00 2001 From: opapy Date: Wed, 11 Jul 2018 16:19:31 +0900 Subject: [PATCH 04/14] fix ChangeLog.rst --- ChangeLog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.rst b/ChangeLog.rst index b08bd7ef..e756045d 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,5 +1,9 @@ Change Log ========== +New in draft +-------------------- +* Change set_many and set_multi api return value. see [pr](https://github.com/pinterest/pymemcache/pull/179) + New in version 1.4.4 -------------------- * pypy3 to travis test matrix From 8d1bc90bd688cf311aeebd2d709b90dbe09b7f9a Mon Sep 17 00:00:00 2001 From: opapy Date: Thu, 26 Jul 2018 17:00:39 +0900 Subject: [PATCH 05/14] add validation code with NOT_STORED --- pymemcache/client/base.py | 30 +++++++++++++++++++++++------- pymemcache/client/hash.py | 17 ++++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 5694c6bd..bc9be556 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -311,12 +311,17 @@ def set_many(self, values, expire=0, noreply=None): Returns: Empty list """ - # TODO: make this more performant by sending all the values first, then # waiting for all the responses. + if noreply is None: + noreply = self.default_noreply + + failed = [] for key, value in six.iteritems(values): - self.set(key, value, expire, noreply) - return [] + result = self._store_cmd(b'set', key, expire, noreply, value, None, (b'STORED', b'NOT_STORED')) + if not result: + failed.append(key) + return failed set_multi = set_many @@ -763,7 +768,15 @@ def _fetch_cmd(self, name, keys, expect_cas): return {} raise - def _store_cmd(self, name, key, expire, noreply, data, cas=None): + def _store_cmd( + self, + name, + key, + expire, + noreply, + data, + cas=None, + validate_store_results=None): key = self.check_key(key) if not self.sock: self._connect() @@ -801,7 +814,10 @@ def _store_cmd(self, name, key, expire, noreply, data, cas=None): buf, line = _readline(self.sock, buf) self._raise_errors(line, name) - if line in VALID_STORE_RESULTS[name]: + if validate_store_results is None: + validate_store_results = VALID_STORE_RESULTS[name] + + if line in validate_store_results: if line == b'STORED': return True if line == b'NOT_STORED': @@ -927,8 +943,8 @@ def set(self, key, value, expire=0, noreply=None): def set_many(self, values, expire=0, noreply=None): with self.client_pool.get_and_release(destroy_on_fail=True) as client: - client.set_many(values, expire=expire, noreply=noreply) - return [] + failed = client.set_many(values, expire=expire, noreply=noreply) + return failed set_multi = set_many diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index d78dfc17..d3778e03 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -257,13 +257,16 @@ def set_many(self, values, *args, **kwargs): for server, values in client_batches.items(): client = self.clients['%s:%s' % server] - new_args = list(args) - new_args.insert(0, values) - result = self._safely_run_func( - client, - client.set_many, values.keys(), *new_args, **kwargs - ) - failed.extend(result) + + for value in values: + new_args = list(args) + new_args.insert(0, value) + result = self._safely_run_func( + client, + client.set, False, *new_args, **kwargs + ) + if not result: + failed.extend(result) return failed From 08667dac0a66404a2d89059b5a3a432290c30621 Mon Sep 17 00:00:00 2001 From: opapy Date: Thu, 26 Jul 2018 17:06:40 +0900 Subject: [PATCH 06/14] fix pep8 --- pymemcache/client/base.py | 10 +++++++++- pymemcache/client/hash.py | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index bc9be556..53bb60e5 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -318,7 +318,15 @@ def set_many(self, values, expire=0, noreply=None): failed = [] for key, value in six.iteritems(values): - result = self._store_cmd(b'set', key, expire, noreply, value, None, (b'STORED', b'NOT_STORED')) + result = self._store_cmd( + b'set', + key, + expire, + noreply, + value, + None, + (b'STORED', b'NOT_STORED') + ) if not result: failed.append(key) return failed diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index d3778e03..3d225048 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -258,9 +258,10 @@ def set_many(self, values, *args, **kwargs): for server, values in client_batches.items(): client = self.clients['%s:%s' % server] - for value in values: + for key, value in values.items(): new_args = list(args) new_args.insert(0, value) + new_args.insert(0, key) result = self._safely_run_func( client, client.set, False, *new_args, **kwargs From 8bf60ad863029722609aeba28fca6362f0b232ba Mon Sep 17 00:00:00 2001 From: opapy Date: Thu, 26 Jul 2018 17:26:14 +0900 Subject: [PATCH 07/14] fix result --- pymemcache/client/hash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index 3d225048..2d0ec6d5 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -267,7 +267,7 @@ def set_many(self, values, *args, **kwargs): client.set, False, *new_args, **kwargs ) if not result: - failed.extend(result) + failed.extend(key) return failed From d6a8bdb5655820c59dafc778b5e0fe0b34c2edb3 Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 30 Jul 2018 14:14:52 +0900 Subject: [PATCH 08/14] fix implementation HashClient --- pymemcache/client/hash.py | 144 ++++++++++++++++++++++++--------- pymemcache/test/test_client.py | 12 +-- 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index 2d0ec6d5..00b32a98 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -13,6 +13,7 @@ class HashClient(object): """ A client for communicating with a cluster of memcached servers """ + def __init__( self, servers, @@ -138,6 +139,21 @@ def _get_client(self, key): client = self.clients[server] return client + def _set_many(self, client, values, *args, **kwargs): + failed = [] + succeeded = [] + try: + for key, value in values.items(): + result = client.set(key, value, *args, **kwargs) + if result: + succeeded.append(key) + else: + failed.append(key) + except Exception as e: + return succeeded, failed, e + + return succeeded, failed, None + def _safely_run_func(self, client, func, default_val, *args, **kwargs): try: if client.server in self._failed_clients: @@ -171,34 +187,7 @@ def _safely_run_func(self, client, func, default_val, *args, **kwargs): # Connecting to the server fail, we should enter # retry mode except socket.error: - # This client has never failed, lets mark it for failure - if ( - client.server not in self._failed_clients and - self.retry_attempts > 0 - ): - self._failed_clients[client.server] = { - 'failed_time': time.time(), - 'attempts': 0, - } - # We aren't allowing any retries, we should mark the server as - # dead immediately - elif ( - client.server not in self._failed_clients and - self.retry_attempts <= 0 - ): - self._failed_clients[client.server] = { - 'failed_time': time.time(), - 'attempts': 0, - } - logger.debug("marking server as dead %s", client.server) - self.remove_server(*client.server) - # This client has failed previously, we need to update the metadata - # to reflect that we have attempted it again - else: - failed_metadata = self._failed_clients[client.server] - failed_metadata['attempts'] += 1 - failed_metadata['failed_time'] = time.time() - self._failed_clients[client.server] = failed_metadata + self._mark_failed_server(client.server) # if we haven't enabled ignore_exc, don't move on gracefully, just # raise the exception @@ -214,6 +203,95 @@ def _safely_run_func(self, client, func, default_val, *args, **kwargs): return default_val + def _safely_run_set_many(self, client, values, *args, **kwargs): + failed = [] + succeeded = [] + try: + if client.server in self._failed_clients: + # This server is currently failing, lets check if it is in + # retry or marked as dead + failed_metadata = self._failed_clients[client.server] + + # we haven't tried our max amount yet, if it has been enough + # time lets just retry using it + if failed_metadata['attempts'] < self.retry_attempts: + failed_time = failed_metadata['failed_time'] + if time.time() - failed_time > self.retry_timeout: + logger.debug( + 'retrying failed server: %s', client.server + ) + succeeded, failed, err = self._set_many( + client, values, *args, **kwargs) + if err is not None: + raise err + # we were successful, lets remove it from the failed + # clients + self._failed_clients.pop(client.server) + return failed + return values.keys() + else: + # We've reached our max retry attempts, we need to mark + # the sever as dead + logger.debug('marking server as dead: %s', client.server) + self.remove_server(*client.server) + + succeeded, failed, err = self._set_many( + client, values, *args, **kwargs + ) + if err is not None: + raise err + + return failed + + # Connecting to the server fail, we should enter + # retry mode + except socket.error: + self._mark_failed_server(client.server) + + # if we haven't enabled ignore_exc, don't move on gracefully, just + # raise the exception + if not self.ignore_exc: + raise + + return values.keys() - succeeded + except Exception: + # any exceptions that aren't socket.error we need to handle + # gracefully as well + if not self.ignore_exc: + raise + + return values.keys() - succeeded + + def _mark_failed_server(self, server): + # This client has never failed, lets mark it for failure + if ( + server not in self._failed_clients and + self.retry_attempts > 0 + ): + self._failed_clients[server] = { + 'failed_time': time.time(), + 'attempts': 0, + } + # We aren't allowing any retries, we should mark the server as + # dead immediately + elif ( + server not in self._failed_clients and + self.retry_attempts <= 0 + ): + self._failed_clients[server] = { + 'failed_time': time.time(), + 'attempts': 0, + } + logger.debug("marking server as dead %s", server) + self.remove_server(*server) + # This client has failed previously, we need to update the metadata + # to reflect that we have attempted it again + else: + failed_metadata = self._failed_clients[server] + failed_metadata['attempts'] += 1 + failed_metadata['failed_time'] = time.time() + self._failed_clients[server] = failed_metadata + def _run_cmd(self, cmd, key, default_val, *args, **kwargs): client = self._get_client(key) @@ -259,15 +337,9 @@ def set_many(self, values, *args, **kwargs): client = self.clients['%s:%s' % server] for key, value in values.items(): - new_args = list(args) - new_args.insert(0, value) - new_args.insert(0, key) - result = self._safely_run_func( - client, - client.set, False, *new_args, **kwargs + failed = self._safely_run_set_many( + client, values, *args, **kwargs ) - if not result: - failed.extend(key) return failed diff --git a/pymemcache/test/test_client.py b/pymemcache/test/test_client.py index b5e9c9f9..9a684688 100644 --- a/pymemcache/test/test_client.py +++ b/pymemcache/test/test_client.py @@ -752,8 +752,9 @@ def test_default_noreply_set(self): def test_default_noreply_set_many(self): with pytest.raises(MemcacheUnknownError): - self._default_noreply_false( - 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) + client = self.make_client([b'UNKNOWN\r\n'], default_noreply=False) + result = client.set_many({b'key': b'value'}) + assert result == [b'key'] self._default_noreply_true_and_empty_list( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) @@ -873,8 +874,9 @@ def test_default_noreply_set(self): def test_default_noreply_set_many(self): with pytest.raises(MemcacheUnknownError): - self._default_noreply_false( - 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) + client = self.make_client([b'UNKNOWN\r\n'], default_noreply=False) + result = client.set_many({b'key': b'value'}) + assert result == [b'key'] self._default_noreply_true_and_empty_list( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) @@ -1018,5 +1020,5 @@ def test_recv(self): b'key1 0 6\r\nval', socket.error(errno.EINTR, "Interrupted system call"), b'ue1\r\nEND\r\n', - ]) + ]) assert client[b'key1'] == b'value1' From bd2a2413b40090f054b08987541de07242a72e36 Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 30 Jul 2018 17:46:42 +0900 Subject: [PATCH 09/14] fix test --- pymemcache/client/base.py | 24 ++++----------- pymemcache/client/hash.py | 45 +++++++++++++++-------------- pymemcache/test/test_client.py | 11 ++++--- pymemcache/test/test_client_hash.py | 36 +++++++++++++++++++++++ 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 53bb60e5..84f839e2 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -29,7 +29,7 @@ RECV_SIZE = 4096 VALID_STORE_RESULTS = { - b'set': (b'STORED',), + b'set': (b'STORED', b'NOT_STORED'), b'add': (b'STORED', b'NOT_STORED'), b'replace': (b'STORED', b'NOT_STORED'), b'append': (b'STORED', b'NOT_STORED'), @@ -107,11 +107,11 @@ def _check_key(key, allow_unicode_keys, key_prefix=b''): ) elif c == ord(b'\00'): raise MemcacheIllegalInputError( - "Key contains null character: '%r'" % (key,) + "Key contains null character: '%r'" % (key,) ) elif c == ord(b'\r'): raise MemcacheIllegalInputError( - "Key contains carriage return: '%r'" % (key,) + "Key contains carriage return: '%r'" % (key,) ) return key @@ -325,7 +325,6 @@ def set_many(self, values, expire=0, noreply=None): noreply, value, None, - (b'STORED', b'NOT_STORED') ) if not result: failed.append(key) @@ -666,7 +665,7 @@ def version(self): if not result.startswith(b'VERSION '): raise MemcacheUnknownError( - "Received unexpected response: %s" % (result, )) + "Received unexpected response: %s" % (result, )) return result[8:] @@ -776,15 +775,7 @@ def _fetch_cmd(self, name, keys, expect_cas): return {} raise - def _store_cmd( - self, - name, - key, - expire, - noreply, - data, - cas=None, - validate_store_results=None): + def _store_cmd(self, name, key, expire, noreply, data, cas=None): key = self.check_key(key) if not self.sock: self._connect() @@ -822,10 +813,7 @@ def _store_cmd( buf, line = _readline(self.sock, buf) self._raise_errors(line, name) - if validate_store_results is None: - validate_store_results = VALID_STORE_RESULTS[name] - - if line in validate_store_results: + if line in VALID_STORE_RESULTS[name]: if line == b'STORED': return True if line == b'NOT_STORED': diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index 00b32a98..c083c030 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -1,6 +1,7 @@ import socket import time import logging +import six from pymemcache.client.base import Client, PooledClient, _check_key from pymemcache.client.rendezvous import RendezvousHash @@ -139,21 +140,6 @@ def _get_client(self, key): client = self.clients[server] return client - def _set_many(self, client, values, *args, **kwargs): - failed = [] - succeeded = [] - try: - for key, value in values.items(): - result = client.set(key, value, *args, **kwargs) - if result: - succeeded.append(key) - else: - failed.append(key) - except Exception as e: - return succeeded, failed, e - - return succeeded, failed, None - def _safely_run_func(self, client, func, default_val, *args, **kwargs): try: if client.server in self._failed_clients: @@ -253,14 +239,14 @@ def _safely_run_set_many(self, client, values, *args, **kwargs): if not self.ignore_exc: raise - return values.keys() - succeeded + return list(set(values.keys()) - set(succeeded)) except Exception: # any exceptions that aren't socket.error we need to handle # gracefully as well if not self.ignore_exc: raise - return values.keys() - succeeded + return list(set(values.keys()) - set(succeeded)) def _mark_failed_server(self, server): # This client has never failed, lets mark it for failure @@ -305,6 +291,22 @@ def _run_cmd(self, cmd, key, default_val, *args, **kwargs): client, func, default_val, *args, **kwargs ) + def _set_many(self, client, values, *args, **kwargs): + failed = [] + succeeded = [] + + try: + for key, value in six.iteritems(values): + result = client.set(key, value, *args, **kwargs) + if result: + succeeded.append(key) + else: + failed.append(key) + except Exception as e: + return succeeded, failed, e + + return succeeded, failed, None + def set(self, key, *args, **kwargs): return self._run_cmd('set', key, False, *args, **kwargs) @@ -321,7 +323,7 @@ def set_many(self, values, *args, **kwargs): client_batches = {} failed = [] - for key, value in values.items(): + for key, value in six.iteritems(values): client = self._get_client(key) if client is None: @@ -336,10 +338,9 @@ def set_many(self, values, *args, **kwargs): for server, values in client_batches.items(): client = self.clients['%s:%s' % server] - for key, value in values.items(): - failed = self._safely_run_set_many( - client, values, *args, **kwargs - ) + failed = self._safely_run_set_many( + client, values, *args, **kwargs + ) return failed diff --git a/pymemcache/test/test_client.py b/pymemcache/test/test_client.py index 9a684688..a20b740f 100644 --- a/pymemcache/test/test_client.py +++ b/pymemcache/test/test_client.py @@ -746,7 +746,9 @@ def _default_noreply_true_and_empty_list(self, cmd, args, response): def test_default_noreply_set(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( - 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) + 'set', (b'key', b'value'), [b'UNKNOWN\r\n']) + self._default_noreply_false( + 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) self._default_noreply_true( 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) @@ -868,15 +870,16 @@ def _default_noreply_true_and_empty_list(self, cmd, args, response): def test_default_noreply_set(self): with pytest.raises(MemcacheUnknownError): self._default_noreply_false( - 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) + 'set', (b'key', b'value'), [b'UNKNOWN\r\n']) + self._default_noreply_false( + 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) self._default_noreply_true( 'set', (b'key', b'value'), [b'NOT_STORED\r\n']) def test_default_noreply_set_many(self): with pytest.raises(MemcacheUnknownError): client = self.make_client([b'UNKNOWN\r\n'], default_noreply=False) - result = client.set_many({b'key': b'value'}) - assert result == [b'key'] + client.set_many({b'key': b'value'}) self._default_noreply_true_and_empty_list( 'set_many', ({b'key': b'value'},), [b'NOT_STORED\r\n']) diff --git a/pymemcache/test/test_client_hash.py b/pymemcache/test/test_client_hash.py index 3f711c8e..3e8aae2b 100644 --- a/pymemcache/test/test_client_hash.py +++ b/pymemcache/test/test_client_hash.py @@ -213,4 +213,40 @@ def test_no_servers_left_with_get_many(self): result = client.get_many(['foo', 'bar']) assert result == {'foo': False, 'bar': False} + def test_ignore_exec_set_many(self): + values = { + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3' + } + + with pytest.raises(MemcacheUnknownError): + client = self.make_client(*[ + [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'], + [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'], + ]) + client.set_many(values, noreply=False) + + client = self.make_client(*[ + [b'STORED\r\n', b'UNKNOWN\r\n', b'STORED\r\n'], + ], ignore_exc=True) + result = client.set_many(values, noreply=False) + + assert len(result) == 2 + + def test_noreply_set_many(self): + client = self.make_client(*[ + [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'], + ]) + result = client.set_many( + {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}, noreply=False) + assert len(result) == 1 + + client = self.make_client(*[ + [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'], + ]) + result = client.set_many( + {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}, noreply=True) + assert result == [] + # TODO: Test failover logic From de69dd216dd65d20bfbc7610d5cbe0922341531a Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 30 Jul 2018 17:59:57 +0900 Subject: [PATCH 10/14] fix pep8 --- pymemcache/test/test_client_hash.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pymemcache/test/test_client_hash.py b/pymemcache/test/test_client_hash.py index 3e8aae2b..cea71bdf 100644 --- a/pymemcache/test/test_client_hash.py +++ b/pymemcache/test/test_client_hash.py @@ -235,18 +235,22 @@ def test_ignore_exec_set_many(self): assert len(result) == 2 def test_noreply_set_many(self): + values = { + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3' + } + client = self.make_client(*[ [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'], ]) - result = client.set_many( - {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}, noreply=False) + result = client.set_many(values, noreply=False) assert len(result) == 1 client = self.make_client(*[ [b'STORED\r\n', b'NOT_STORED\r\n', b'STORED\r\n'], ]) - result = client.set_many( - {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}, noreply=True) + result = client.set_many(values, noreply=True) assert result == [] # TODO: Test failover logic From ebca7398967ee3e5b337719745876a6647b10f66 Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 30 Jul 2018 18:10:53 +0900 Subject: [PATCH 11/14] fix failed keys --- pymemcache/client/hash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymemcache/client/hash.py b/pymemcache/client/hash.py index c083c030..63bae658 100644 --- a/pymemcache/client/hash.py +++ b/pymemcache/client/hash.py @@ -338,7 +338,7 @@ def set_many(self, values, *args, **kwargs): for server, values in client_batches.items(): client = self.clients['%s:%s' % server] - failed = self._safely_run_set_many( + failed += self._safely_run_set_many( client, values, *args, **kwargs ) From 2fe1c5a205dca2ca1eebba989f49a884ff7c2d2d Mon Sep 17 00:00:00 2001 From: opapy Date: Mon, 30 Jul 2018 18:37:41 +0900 Subject: [PATCH 12/14] refactor --- pymemcache/client/base.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 84f839e2..30e00d24 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -318,14 +318,7 @@ def set_many(self, values, expire=0, noreply=None): failed = [] for key, value in six.iteritems(values): - result = self._store_cmd( - b'set', - key, - expire, - noreply, - value, - None, - ) + result = self.set(key, value, expire, noreply) if not result: failed.append(key) return failed From 0ac3dc7a765a52c144f3fc4dbb83d3bdb23e9d11 Mon Sep 17 00:00:00 2001 From: opapy Date: Wed, 15 Aug 2018 15:11:09 +0900 Subject: [PATCH 13/14] fix comment for set_many --- pymemcache/client/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 30e00d24..5962a0af 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -309,7 +309,7 @@ def set_many(self, values, expire=0, noreply=None): self.default_noreply). Returns: - Empty list + Returns a list of keys that failed to be inserted. """ # TODO: make this more performant by sending all the values first, then # waiting for all the responses. From 8ce0737c63801855ce95d62aaf208203aa527eba Mon Sep 17 00:00:00 2001 From: opapy Date: Wed, 15 Aug 2018 15:13:23 +0900 Subject: [PATCH 14/14] fix comment for set_many --- pymemcache/client/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index 5962a0af..2892029c 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -310,6 +310,7 @@ def set_many(self, values, expire=0, noreply=None): Returns: Returns a list of keys that failed to be inserted. + If noreply is True, alwais returns empty list. """ # TODO: make this more performant by sending all the values first, then # waiting for all the responses.