Hello community,

here is the log from the commit of package python-pymemcache for 
openSUSE:Factory checked in at 2020-03-20 23:59:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pymemcache (Old)
 and      /work/SRC/openSUSE:Factory/.python-pymemcache.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pymemcache"

Fri Mar 20 23:59:38 2020 rev:5 rq:786856 version:3.0.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pymemcache/python-pymemcache.changes      
2019-09-11 10:36:46.543272143 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pymemcache.new.3160/python-pymemcache.changes
    2020-03-21 00:03:03.853154029 +0100
@@ -1,0 +2,17 @@
+Fri Mar 20 12:04:30 UTC 2020 - Marketa Calabkova <[email protected]>
+
+- Update to 3.0.0
+  * The serialization API has been reworked. Instead of consuming a serializer
+    and deserializer as separate arguments, client objects now expect an 
argument
+    ``serde`` to be an object which implements ``serialize`` and 
``deserialize``
+    as methods. (``serialize`` and ``deserialize`` are still supported but
+    considered deprecated.)
+  * Validate integer inputs for ``expire``, ``delay``, ``incr``, ``decr``, and
+    ``memlimit`` -- non-integer values now raise ``MemcacheIllegalInputError``
+  * Validate inputs for ``cas`` -- values which are not integers or strings of
+    0-9 now raise ``MemcacheIllegalInputError``
+  * Add ``prepend`` and ``append`` support to ``MockMemcacheClient``.
+  * Add the ``touch`` method to ``HashClient``.
+  * Added official support for Python 3.8.
+
+-------------------------------------------------------------------

Old:
----
  pymemcache-2.2.2.tar.gz

New:
----
  pymemcache-3.0.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pymemcache.spec ++++++
--- /var/tmp/diff_new_pack.MxgoJd/_old  2020-03-21 00:03:05.557154963 +0100
+++ /var/tmp/diff_new_pack.MxgoJd/_new  2020-03-21 00:03:05.645155011 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pymemcache
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 # Copyright (c) 2014 Thomas Bechtold <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -19,7 +19,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-pymemcache
-Version:        2.2.2
+Version:        3.0.0
 Release:        0
 Summary:        A pure Python memcached client
 License:        Apache-2.0

++++++ pymemcache-2.2.2.tar.gz -> pymemcache-3.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/ChangeLog.rst 
new/pymemcache-3.0.0/ChangeLog.rst
--- old/pymemcache-2.2.2/ChangeLog.rst  2019-08-06 21:06:33.000000000 +0200
+++ new/pymemcache-3.0.0/ChangeLog.rst  2020-01-02 22:34:43.000000000 +0100
@@ -1,6 +1,21 @@
 Changelog
 =========
 
+New in version 3.0.0
+--------------------
+* The serialization API has been reworked. Instead of consuming a serializer
+  and deserializer as separate arguments, client objects now expect an argument
+  ``serde`` to be an object which implements ``serialize`` and ``deserialize``
+  as methods. (``serialize`` and ``deserialize`` are still supported but
+  considered deprecated.)
+* Validate integer inputs for ``expire``, ``delay``, ``incr``, ``decr``, and
+  ``memlimit`` -- non-integer values now raise ``MemcacheIllegalInputError``
+* Validate inputs for ``cas`` -- values which are not integers or strings of
+  0-9 now raise ``MemcacheIllegalInputError``
+* Add ``prepend`` and ``append`` support to ``MockMemcacheClient``.
+* Add the ``touch`` method to ``HashClient``.
+* Added official support for Python 3.8.
+
 New in version 2.2.2
 --------------------
 * Fix ``long_description`` string in Python packaging.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/PKG-INFO 
new/pymemcache-3.0.0/PKG-INFO
--- old/pymemcache-2.2.2/PKG-INFO       2019-08-06 21:07:47.000000000 +0200
+++ new/pymemcache-3.0.0/PKG-INFO       2020-01-02 22:43:14.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pymemcache
-Version: 2.2.2
+Version: 3.0.0
 Summary: "A comprehensive, fast, pure Python memcached client"
 Home-page: https://github.com/pinterest/pymemcache
 Author: Charles Gordon
@@ -9,9 +9,6 @@
 Description: pymemcache
         ==========
         
-        .. image:: https://travis-ci.org/pinterest/pymemcache.svg
-            :target: https://travis-ci.org/pinterest/pymemcache
-        
         .. image:: https://img.shields.io/pypi/v/pymemcache.svg
             :target: https://pypi.python.org/pypi/pymemcache
         
@@ -76,10 +73,10 @@
         dependency on libmemcached poses challenges (e.g., it must be built 
against
         the same version of libmemcached that it will use at runtime).
         
-        Python-memcache
-        ---------------
+        python-memcached
+        ----------------
         
-        The python-memcache library implements the entire memcached text 
protocol, has
+        The python-memcached library implements the entire memcached text 
protocol, has
         a single timeout for all socket calls and has a flexible approach to
         serialization and deserialization. It is also written entirely in 
Python, so
         it works well with libraries like gevent. However, it is tied to using 
thread
@@ -134,6 +131,7 @@
         * `Nicholas Charriere <https://github.com/nichochar>`_
         * `Joe Gordon <https://github.com/jogo>`_
         * `Jon Parise <https://github.com/jparise>`_
+        * `Stephen Rosen <https://github.com/sirosen>`_
         
         We're Hiring!
         =============
@@ -143,6 +141,21 @@
         Changelog
         =========
         
+        New in version 3.0.0
+        --------------------
+        * The serialization API has been reworked. Instead of consuming a 
serializer
+          and deserializer as separate arguments, client objects now expect an 
argument
+          ``serde`` to be an object which implements ``serialize`` and 
``deserialize``
+          as methods. (``serialize`` and ``deserialize`` are still supported 
but
+          considered deprecated.)
+        * Validate integer inputs for ``expire``, ``delay``, ``incr``, 
``decr``, and
+          ``memlimit`` -- non-integer values now raise 
``MemcacheIllegalInputError``
+        * Validate inputs for ``cas`` -- values which are not integers or 
strings of
+          0-9 now raise ``MemcacheIllegalInputError``
+        * Add ``prepend`` and ``append`` support to ``MockMemcacheClient``.
+        * Add the ``touch`` method to ``HashClient``.
+        * Added official support for Python 3.8.
+        
         New in version 2.2.2
         --------------------
         * Fix ``long_description`` string in Python packaging.
@@ -277,6 +290,7 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Topic :: Database
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/README.rst 
new/pymemcache-3.0.0/README.rst
--- old/pymemcache-2.2.2/README.rst     2019-04-20 01:32:18.000000000 +0200
+++ new/pymemcache-3.0.0/README.rst     2019-11-25 17:37:00.000000000 +0100
@@ -1,9 +1,6 @@
 pymemcache
 ==========
 
-.. image:: https://travis-ci.org/pinterest/pymemcache.svg
-    :target: https://travis-ci.org/pinterest/pymemcache
-
 .. image:: https://img.shields.io/pypi/v/pymemcache.svg
     :target: https://pypi.python.org/pypi/pymemcache
 
@@ -68,10 +65,10 @@
 dependency on libmemcached poses challenges (e.g., it must be built against
 the same version of libmemcached that it will use at runtime).
 
-Python-memcache
----------------
+python-memcached
+----------------
 
-The python-memcache library implements the entire memcached text protocol, has
+The python-memcached library implements the entire memcached text protocol, has
 a single timeout for all socket calls and has a flexible approach to
 serialization and deserialization. It is also written entirely in Python, so
 it works well with libraries like gevent. However, it is tied to using thread
@@ -126,6 +123,7 @@
 * `Nicholas Charriere <https://github.com/nichochar>`_
 * `Joe Gordon <https://github.com/jogo>`_
 * `Jon Parise <https://github.com/jparise>`_
+* `Stephen Rosen <https://github.com/sirosen>`_
 
 We're Hiring!
 =============
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/__init__.py 
new/pymemcache-3.0.0/pymemcache/__init__.py
--- old/pymemcache-2.2.2/pymemcache/__init__.py 2019-08-06 21:06:33.000000000 
+0200
+++ new/pymemcache-3.0.0/pymemcache/__init__.py 2020-01-02 22:33:26.000000000 
+0100
@@ -1,4 +1,4 @@
-__version__ = '2.2.2'
+__version__ = '3.0.0'
 
 from pymemcache.client.base import Client  # noqa
 from pymemcache.client.base import PooledClient  # noqa
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/client/base.py 
new/pymemcache-3.0.0/pymemcache/client/base.py
--- old/pymemcache-2.2.2/pymemcache/client/base.py      2019-08-05 
21:52:32.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache/client/base.py      2019-09-11 
02:03:41.000000000 +0200
@@ -17,6 +17,7 @@
 
 from pymemcache import pool
 
+from pymemcache.serde import LegacyWrappingSerde
 from pymemcache.exceptions import (
     MemcacheClientError,
     MemcacheUnknownCommandError,
@@ -144,36 +145,38 @@
      just calling encode on the string (using UTF-8, for instance).
 
      If you intend to use anything but str as a value, it is a good idea to use
-     a serializer and deserializer. The pymemcache.serde library has some
-     already implemented serializers, including one that is compatible with
-     the python-memcache library.
+     a serializer. The pymemcache.serde library has an already implemented
+     serializer which pickles and unpickles data.
 
     *Serialization and Deserialization*
 
-     The constructor takes two optional functions, one for "serialization" of
-     values, and one for "deserialization". The serialization function takes
-     two arguments, a key and a value, and returns a tuple of two elements, the
-     serialized value, and an integer in the range 0-65535 (the "flags"). The
-     deserialization function takes three parameters, a key, value and flags
-     and returns the deserialized value.
+     The constructor takes an optional object, the "serializer/deserializer"
+     ("serde"), which is responsible for both serialization and deserialization
+     of objects. That object must satisfy the serializer interface by providing
+     two methods: `serialize` and `deserialize`. `serialize` takes two
+     arguments, a key and a value, and returns a tuple of two elements, the
+     serialized value, and an integer in the range 0-65535 (the "flags").
+     `deserialize` takes three parameters, a key, value, and flags, and returns
+     the deserialized value.
 
      Here is an example using JSON for non-str values:
 
      .. code-block:: python
 
-         def serialize_json(key, value):
-             if type(value) == str:
-                 return value, 1
-             return json.dumps(value), 2
-
-         def deserialize_json(key, value, flags):
-             if flags == 1:
-                 return value
+         class JSONSerde(object):
+             def serialize(self, key, value):
+                 if isinstance(value, str):
+                     return value, 1
+                 return json.dumps(value), 2
+
+             def deserialize(self, key, value, flags):
+                 if flags == 1:
+                     return value
 
-             if flags == 2:
-                 return json.loads(value)
+                 if flags == 2:
+                     return json.loads(value)
 
-             raise Exception("Unknown flags for value: {1}".format(flags))
+                 raise Exception("Unknown flags for value: {1}".format(flags))
 
     .. note::
 
@@ -205,6 +208,7 @@
 
     def __init__(self,
                  server,
+                 serde=None,
                  serializer=None,
                  deserializer=None,
                  connect_timeout=None,
@@ -221,8 +225,9 @@
 
         Args:
           server: tuple(hostname, port) or string containing a UNIX socket 
path.
-          serializer: optional function, see notes in the class docs.
-          deserializer: optional function, see notes in the class docs.
+          serde: optional seralizer object, see notes in the class docs.
+          serializer: deprecated serialization function
+          deserializer: deprecated deserialization function
           connect_timeout: optional float, seconds to wait for a connection to
             the memcached server. Defaults to "forever" (uses the underlying
             default socket timeout, which can be very long).
@@ -249,8 +254,7 @@
           call to a method on the object will do that.
         """
         self.server = server
-        self.serializer = serializer
-        self.deserializer = deserializer
+        self.serde = serde or LegacyWrappingSerde(serializer, deserializer)
         self.connect_timeout = connect_timeout
         self.timeout = timeout
         self.no_delay = no_delay
@@ -368,9 +372,10 @@
                   flags
 
         Returns:
-          If noreply is True, the return value is always True. Otherwise the
-          return value is True if the value was stored, and False if it was
-          not (because the key already existed).
+          If ``noreply`` is True (or if it is unset and 
``self.default_noreply``
+          is True), the return value is always True. Otherwise the return value
+          is True if the value was stored, and False if it was not (because
+          the key already existed).
         """
         if noreply is None:
             noreply = self.default_noreply
@@ -392,7 +397,8 @@
                 flags
 
         Returns:
-          If noreply is True, always returns True. Otherwise returns True if
+          If ``noreply`` is True (or if it is unset and 
``self.default_noreply``
+          is True), the return value is always True. Otherwise returns True if
           the value was stored and False if it wasn't (because the key didn't
           already exist).
         """
@@ -460,7 +466,8 @@
                 flags
 
         Returns:
-          If noreply is True, always returns True. Otherwise returns None if
+          If ``noreply`` is True (or if it is unset and 
``self.default_noreply``
+          is True), the return value is always True. Otherwise returns None if
           the key didn't exist, False if it existed but had a different cas
           value and True if it existed and was changed.
         """
@@ -542,7 +549,8 @@
                    self.default_noreply).
 
         Returns:
-          If noreply is True, always returns True. Otherwise returns True if
+          If ``noreply`` is True (or if it is unset and 
``self.default_noreply``
+          is True), the return value is always True. Otherwise returns True if
           the key was deleted, and False if it wasn't found.
         """
         if noreply is None:
@@ -602,7 +610,8 @@
           value of the key, or None if the key wasn't found.
         """
         key = self.check_key(key)
-        cmd = b'incr ' + key + b' ' + 
six.text_type(value).encode(self.encoding)
+        value = self._check_integer(value, "value")
+        cmd = b'incr ' + key + b' ' + value
         if noreply:
             cmd += b' noreply'
         cmd += b'\r\n'
@@ -627,7 +636,8 @@
           value of the key, or None if the key wasn't found.
         """
         key = self.check_key(key)
-        cmd = b'decr ' + key + b' ' + 
six.text_type(value).encode(self.encoding)
+        value = self._check_integer(value, "value")
+        cmd = b'decr ' + key + b' ' + value
         if noreply:
             cmd += b' noreply'
         cmd += b'\r\n'
@@ -656,9 +666,8 @@
         if noreply is None:
             noreply = self.default_noreply
         key = self.check_key(key)
-        cmd = (
-            b'touch ' + key + b' ' + 
six.text_type(expire).encode(self.encoding)
-        )
+        expire = self._check_integer(expire, "expire")
+        cmd = b'touch ' + key + b' ' + expire
         if noreply:
             cmd += b' noreply'
         cmd += b'\r\n'
@@ -704,8 +713,8 @@
         Returns:
           If no exception is raised, always returns True.
         """
-
-        self._fetch_cmd(b'cache_memlimit', [str(int(memlimit))], False)
+        memlimit = self._check_integer(memlimit, "memlimit")
+        self._fetch_cmd(b'cache_memlimit', [memlimit], False)
         return True
 
     def version(self):
@@ -739,7 +748,8 @@
         """
         if noreply is None:
             noreply = self.default_noreply
-        cmd = b'flush_all ' + six.text_type(delay).encode(self.encoding)
+        delay = self._check_integer(delay, "delay")
+        cmd = b'flush_all ' + delay
         if noreply:
             cmd += b' noreply'
         cmd += b'\r\n'
@@ -772,6 +782,41 @@
             error = line[line.find(b' ') + 1:]
             raise MemcacheServerError(error)
 
+    def _check_integer(self, value, name):
+        """Check that a value is an integer and encode it as a binary string"""
+        if not isinstance(value, six.integer_types):  # includes "long" on py2
+            raise MemcacheIllegalInputError(
+                '%s must be integer, got bad value: %r' % (name, value)
+            )
+
+        return six.text_type(value).encode(self.encoding)
+
+    def _check_cas(self, cas):
+        """Check that a value is a valid input for 'cas' -- either an int or a
+        string containing only 0-9
+
+        The value will be (re)encoded so that we can accept strings or bytes.
+        """
+        # convert non-binary values to binary
+        if isinstance(cas, (six.integer_types, VALID_STRING_TYPES)):
+            try:
+                cas = six.text_type(cas).encode(self.encoding)
+            except UnicodeEncodeError:
+                raise MemcacheIllegalInputError(
+                    'non-ASCII cas value: %r' % cas)
+        elif not isinstance(cas, six.binary_type):
+            raise MemcacheIllegalInputError(
+                'cas must be integer, string, or bytes, got bad value: %r' % 
cas
+            )
+
+        if not cas.isdigit():
+            raise MemcacheIllegalInputError(
+                'cas must only contain values in 0-9, got bad value: %r'
+                % cas
+            )
+
+        return cas
+
     def _extract_value(self, expect_cas, line, buf, remapped_keys,
                        prefixed_keys):
         """
@@ -789,8 +834,7 @@
 
         buf, value = _readvalue(self.sock, buf, int(size))
         key = remapped_keys[key]
-        if self.deserializer:
-            value = self.deserializer(key, value, int(flags))
+        value = self.serde.deserialize(key, value, int(flags))
 
         if expect_cas:
             return key, (value, cas), buf
@@ -843,20 +887,18 @@
 
         extra = b''
         if cas is not None:
+            cas = self._check_cas(cas)
             extra += b' ' + cas
         if noreply:
             extra += b' noreply'
-        expire = six.text_type(expire).encode(self.encoding)
+        expire = self._check_integer(expire, "expire")
 
         for key, data in six.iteritems(values):
             # must be able to reliably map responses back to the original order
             keys.append(key)
 
             key = self.check_key(key)
-            if self.serializer:
-                data, data_flags = self.serializer(key, data)
-            else:
-                data_flags = 0
+            data, data_flags = self.serde.serialize(key, data)
 
             # If 'flags' was explicitly provided, it overrides the value
             # returned by the serializer.
@@ -954,10 +996,14 @@
                       eventlet lock or semaphore could be used instead)
 
     Further arguments are interpreted as for :py:class:`.Client` constructor.
+
+    Note: if `serde` is given, the same object will be used for *all* clients
+    in the pool. Your serde object must therefore be thread-safe.
     """
 
     def __init__(self,
                  server,
+                 serde=None,
                  serializer=None,
                  deserializer=None,
                  connect_timeout=None,
@@ -972,8 +1018,7 @@
                  allow_unicode_keys=False,
                  encoding='ascii'):
         self.server = server
-        self.serializer = serializer
-        self.deserializer = deserializer
+        self.serde = serde or LegacyWrappingSerde(serializer, deserializer)
         self.connect_timeout = connect_timeout
         self.timeout = timeout
         self.no_delay = no_delay
@@ -1000,8 +1045,7 @@
 
     def _create_client(self):
         client = Client(self.server,
-                        serializer=self.serializer,
-                        deserializer=self.deserializer,
+                        serde=self.serde,
                         connect_timeout=self.connect_timeout,
                         timeout=self.timeout,
                         no_delay=self.no_delay,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/client/hash.py 
new/pymemcache-3.0.0/pymemcache/client/hash.py
--- old/pymemcache-2.2.2/pymemcache/client/hash.py      2019-06-08 
00:30:13.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache/client/hash.py      2020-01-02 
22:28:28.000000000 +0100
@@ -19,6 +19,7 @@
         self,
         servers,
         hasher=RendezvousHash,
+        serde=None,
         serializer=None,
         deserializer=None,
         connect_timeout=None,
@@ -81,6 +82,7 @@
             'no_delay': no_delay,
             'socket_module': socket_module,
             'key_prefix': key_prefix,
+            'serde': serde,
             'serializer': serializer,
             'deserializer': deserializer,
             'allow_unicode_keys': allow_unicode_keys,
@@ -420,6 +422,9 @@
     def replace(self, key, *args, **kwargs):
         return self._run_cmd('replace', key, False, *args, **kwargs)
 
+    def touch(self, key, *args, **kwargs):
+        return self._run_cmd('touch', key, False, *args, **kwargs)
+
     def flush_all(self):
         for _, client in self.clients.items():
             self._safely_run_func(client, client.flush_all, False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/serde.py 
new/pymemcache-3.0.0/pymemcache/serde.py
--- old/pymemcache-2.2.2/pymemcache/serde.py    2019-01-08 16:29:27.000000000 
+0100
+++ new/pymemcache-3.0.0/pymemcache/serde.py    2019-08-26 20:12:07.000000000 
+0200
@@ -105,3 +105,55 @@
             return None
 
     return value
+
+
+class PickleSerde(object):
+    """
+    An object which implements the serialization/deserialization protocol for
+    :py:class:`pymemcache.client.base.Client` and its descendants using 
pickle_.
+
+    Serialization and deserialization are implemented as methods of this class.
+    To implement a custom serialization/deserialization method for pymemcache,
+    you should implement the same interface as the one provided by this object
+    -- :py:meth:`pymemcache.serde.PickleSerde.serialize` and
+    :py:meth:`pymemcache.serde.PickleSerde.deserialize`. Then,
+    pass your custom object to the pymemcache client object in place of
+    `PickleSerde`.
+
+    For more details on the serialization protocol, see the class documentation
+    for :py:class:`pymemcache.client.base.Client`
+
+    .. pickle: https://docs.python.org/3/library/pickle.html
+    """
+    def __init__(self, pickle_version=DEFAULT_PICKLE_VERSION):
+        self._serialize_func = get_python_memcache_serializer(pickle_version)
+
+    def serialize(self, key, value):
+        return self._serialize_func(key, value)
+
+    def deserialize(self, key, value, flags):
+        return python_memcache_deserializer(key, value, flags)
+
+
+class LegacyWrappingSerde(object):
+    """
+    This class defines how to wrap legacy de/serialization functions into a
+    'serde' object which implements '.serialize' and '.deserialize' methods.
+    It is used automatically by pymemcache.client.base.Client when the
+    'serializer' or 'deserializer' arguments are given.
+
+    The serializer_func and deserializer_func are expected to be None in the
+    case that they are missing.
+    """
+    def __init__(self, serializer_func, deserializer_func):
+        self.serialize = serializer_func or self._default_serialize
+        self.deserialize = deserializer_func or self._default_deserialize
+
+    def _default_serialize(self, key, value):
+        return value, 0
+
+    def _default_deserialize(self, key, value, flags):
+        return value
+
+
+pickle_serde = PickleSerde()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/test_client.py 
new/pymemcache-3.0.0/pymemcache/test/test_client.py
--- old/pymemcache-2.2.2/pymemcache/test/test_client.py 2019-08-05 
21:52:32.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache/test/test_client.py 2019-09-03 
20:54:27.000000000 +0200
@@ -471,34 +471,47 @@
         result = client.prepend(b'key', b'value', noreply=False)
         assert result is True
 
+    def test_cas_malformed(self):
+        client = self.make_client([b'STORED\r\n'])
+        with pytest.raises(MemcacheIllegalInputError):
+            client.cas(b'key', b'value', 'nonintegerstring', noreply=False)
+
+        with pytest.raises(MemcacheIllegalInputError):
+            # even a space makes it a noninteger string
+            client.cas(b'key', b'value', '123 ', noreply=False)
+
+        with pytest.raises(MemcacheIllegalInputError):
+            # non-ASCII digit
+            client.cas(b'key', b'value', u'⁰', noreply=False)
+
     def test_cas_stored(self):
         client = self.make_client([b'STORED\r\n'])
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is True
 
         # unit test for encoding passed in __init__()
         client = self.make_client([b'STORED\r\n'], encoding='utf-8')
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is True
 
     def test_cas_exists(self):
         client = self.make_client([b'EXISTS\r\n'])
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is False
 
         # unit test for encoding passed in __init__()
         client = self.make_client([b'EXISTS\r\n'], encoding='utf-8')
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is False
 
     def test_cas_not_found(self):
         client = self.make_client([b'NOT_FOUND\r\n'])
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is None
 
         # unit test for encoding passed in __init__()
         client = self.make_client([b'NOT_FOUND\r\n'], encoding='utf-8')
-        result = client.cas(b'key', b'value', b'cas', noreply=False)
+        result = client.cas(b'key', b'value', b'123', noreply=False)
         assert result is None
 
     def test_cr_nl_boundaries(self):
@@ -665,10 +678,14 @@
         assert result is False
 
     def test_serialization(self):
-        def _ser(key, value):
-            return json.dumps(value), 0
+        class JsonSerde(object):
+            def serialize(self, key, value):
+                return json.dumps(value).encode('ascii'), 0
+
+            def deserialize(self, key, value, flags):
+                return json.loads(value.decode('ascii'))
 
-        client = self.make_client([b'STORED\r\n'], serializer=_ser)
+        client = self.make_client([b'STORED\r\n'], serde=JsonSerde())
         client.set('key', {'c': 'd'})
         assert client.sock.send_bufs == [
             b'set key 0 0 10 noreply\r\n{"c": "d"}\r\n'
@@ -797,6 +814,23 @@
         with pytest.raises(MemcacheIllegalInputError):
             _set()
 
+    def test_set_key_with_noninteger_expire(self):
+        client = self.make_client([b''])
+
+        class _OneLike(object):
+            """object that looks similar to the int 1"""
+            def __str__(self):
+                return "1"
+
+        for noreply in (True, False):
+            for expire in (1.5, _OneLike(), "1"):
+                def _set():
+                    client.set(b'finekey', b'finevalue',
+                               noreply=noreply, expire=expire)
+
+                with pytest.raises(MemcacheIllegalInputError):
+                    _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)
@@ -1188,22 +1222,23 @@
         assert result == b'value'
 
     def test_deserialization(self):
-        def _serializer(key, value):
-            if isinstance(value, dict):
-                return json.dumps(value).encode('UTF-8'), 1
-            return value, 0
-
-        def _deserializer(key, value, flags):
-            if flags == 1:
-                return json.loads(value.decode('UTF-8'))
-            return value
+        class JsonSerde(object):
+            def serialize(self, key, value):
+                if isinstance(value, dict):
+                    return json.dumps(value).encode('UTF-8'), 1
+                return value, 0
+
+            def deserialize(self, key, value, flags):
+                if flags == 1:
+                    return json.loads(value.decode('UTF-8'))
+                return value
 
         client = self.make_client([
             b'STORED\r\n',
             b'VALUE key1 0 5\r\nhello\r\nEND\r\n',
             b'STORED\r\n',
             b'VALUE key2 0 18\r\n{"hello": "world"}\r\nEND\r\n',
-        ], serializer=_serializer, deserializer=_deserializer)
+        ], serde=JsonSerde())
 
         result = client.set(b'key1', b'hello', noreply=False)
         result = client.get(b'key1')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/test_client_hash.py 
new/pymemcache-3.0.0/pymemcache/test/test_client_hash.py
--- old/pymemcache-2.2.2/pymemcache/test/test_client_hash.py    2019-01-08 
16:29:27.000000000 +0100
+++ new/pymemcache-3.0.0/pymemcache/test/test_client_hash.py    2020-01-02 
22:28:28.000000000 +0100
@@ -143,6 +143,21 @@
         assert (result ==
                 {b'key1': (b'value1', b'1'), b'key3': (b'value2', b'1')})
 
+    def test_touch_not_found(self):
+        client = self.make_client([b'NOT_FOUND\r\n'])
+        result = client.touch(b'key', noreply=False)
+        assert result is False
+
+    def test_touch_no_expiry_found(self):
+        client = self.make_client([b'TOUCHED\r\n'])
+        result = client.touch(b'key', noreply=False)
+        assert result is True
+
+    def test_touch_with_expiry_found(self):
+        client = self.make_client([b'TOUCHED\r\n'])
+        result = client.touch(b'key', 1, noreply=False)
+        assert result is True
+
     def test_no_servers_left(self):
         from pymemcache.client.hash import HashClient
         client = HashClient(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/test_integration.py 
new/pymemcache-3.0.0/pymemcache/test/test_integration.py
--- old/pymemcache-2.2.2/pymemcache/test/test_integration.py    2019-01-08 
16:29:27.000000000 +0100
+++ new/pymemcache-3.0.0/pymemcache/test/test_integration.py    2020-01-02 
22:28:28.000000000 +0100
@@ -26,9 +26,8 @@
     MemcacheClientError
 )
 from pymemcache.serde import (
-    get_python_memcache_serializer,
-    python_memcache_serializer,
-    python_memcache_deserializer
+    PickleSerde,
+    pickle_serde
 )
 
 
@@ -152,8 +151,13 @@
     result = client.set(b'key', b'value', noreply=False)
     assert result is True
 
+    # binary, string, and raw int all match -- should all be encoded as b'1'
     result = client.cas(b'key', b'value', b'1', noreply=False)
     assert result is False
+    result = client.cas(b'key', b'value', '1', noreply=False)
+    assert result is False
+    result = client.cas(b'key', b'value', 1, noreply=False)
+    assert result is False
 
     result, cas = client.gets(b'key')
     assert result == b'value'
@@ -226,6 +230,24 @@
 
 
 @pytest.mark.integration()
+def test_touch(client_class, host, port, socket_module):
+    client = client_class((host, port), socket_module=socket_module)
+    client.flush_all()
+
+    result = client.touch(b'key', noreply=False)
+    assert result is False
+
+    result = client.set(b'key', b'0', 1, noreply=False)
+    assert result is True
+
+    result = client.touch(b'key', noreply=False)
+    assert result is True
+
+    result = client.touch(b'key', 1, noreply=False)
+    assert result is True
+
+
[email protected]()
 def test_misc(client_class, host, port, socket_module):
     client = Client((host, port), socket_module=socket_module)
     client.flush_all()
@@ -233,15 +255,16 @@
 
 @pytest.mark.integration()
 def test_serialization_deserialization(host, port, socket_module):
-    def _ser(key, value):
-        return json.dumps(value).encode('ascii'), 1
-
-    def _des(key, value, flags):
-        if flags == 1:
-            return json.loads(value.decode('ascii'))
-        return value
+    class JsonSerde(object):
+        def serialize(self, key, value):
+            return json.dumps(value).encode('ascii'), 1
+
+        def deserialize(self, key, value, flags):
+            if flags == 1:
+                return json.loads(value.decode('ascii'))
+            return value
 
-    client = Client((host, port), serializer=_ser, deserializer=_des,
+    client = Client((host, port), serde=JsonSerde(),
                     socket_module=socket_module)
     client.flush_all()
 
@@ -252,15 +275,14 @@
 
 
 def serde_serialization_helper(client_class, host, port,
-                               socket_module, serializer):
+                               socket_module, serde):
     def check(value):
         client.set(b'key', value, noreply=False)
         result = client.get(b'key')
         assert result == value
         assert type(result) is type(value)
 
-    client = client_class((host, port), serializer=serializer,
-                          deserializer=python_memcache_deserializer,
+    client = client_class((host, port), serde=serde,
                           socket_module=socket_module)
     client.flush_all()
 
@@ -281,7 +303,7 @@
 @pytest.mark.integration()
 def test_serde_serialization(client_class, host, port, socket_module):
     serde_serialization_helper(client_class, host, port,
-                               socket_module, python_memcache_serializer)
+                               socket_module, pickle_serde)
 
 
 @pytest.mark.integration()
@@ -289,7 +311,7 @@
     serde_serialization_helper(
         client_class, host, port,
         socket_module,
-        get_python_memcache_serializer(pickle_version=0))
+        PickleSerde(pickle_version=0))
 
 
 @pytest.mark.integration()
@@ -297,7 +319,7 @@
     serde_serialization_helper(
         client_class, host, port,
         socket_module,
-        get_python_memcache_serializer(pickle_version=2))
+        PickleSerde(pickle_version=2))
 
 
 @pytest.mark.integration()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/test_serde.py 
new/pymemcache-3.0.0/pymemcache/test/test_serde.py
--- old/pymemcache-2.2.2/pymemcache/test/test_serde.py  2019-01-08 
16:29:27.000000000 +0100
+++ new/pymemcache-3.0.0/pymemcache/test/test_serde.py  2019-08-26 
20:12:07.000000000 +0200
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 from unittest import TestCase
 
-from pymemcache.serde import (python_memcache_serializer,
-                              get_python_memcache_serializer,
-                              python_memcache_deserializer, FLAG_BYTES,
+from pymemcache.serde import (pickle_serde,
+                              PickleSerde,
+                              FLAG_BYTES,
                               FLAG_PICKLE, FLAG_INTEGER, FLAG_LONG, FLAG_TEXT)
 import pytest
 import six
@@ -22,10 +22,10 @@
 
 @pytest.mark.unit()
 class TestSerde(TestCase):
-    serializer = python_memcache_serializer
+    serde = pickle_serde
 
     def check(self, value, expected_flags):
-        serialized, flags = self.serializer(b'key', value)
+        serialized, flags = self.serde.serialize(b'key', value)
         assert flags == expected_flags
 
         # pymemcache stores values as byte strings, so we immediately the value
@@ -33,7 +33,7 @@
         if not isinstance(serialized, six.binary_type):
             serialized = six.text_type(serialized).encode('ascii')
 
-        deserialized = python_memcache_deserializer(b'key', serialized, flags)
+        deserialized = self.serde.deserialize(b'key', serialized, flags)
         assert deserialized == value
 
     def test_bytes(self):
@@ -66,20 +66,21 @@
 
 @pytest.mark.unit()
 class TestSerdePickleVersion0(TestCase):
-    serializer = get_python_memcache_serializer(pickle_version=0)
+    serde = PickleSerde(pickle_version=0)
 
 
 @pytest.mark.unit()
 class TestSerdePickleVersion1(TestCase):
-    serializer = get_python_memcache_serializer(pickle_version=1)
+    serde = PickleSerde(pickle_version=1)
 
 
 @pytest.mark.unit()
 class TestSerdePickleVersion2(TestCase):
-    serializer = get_python_memcache_serializer(pickle_version=2)
+    serde = PickleSerde(pickle_version=2)
 
 
 @pytest.mark.unit()
 class TestSerdePickleVersionHighest(TestCase):
-    serializer = get_python_memcache_serializer(
-        pickle_version=pickle.HIGHEST_PROTOCOL)
+    serde = PickleSerde(
+        pickle_version=pickle.HIGHEST_PROTOCOL
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/test_utils.py 
new/pymemcache-3.0.0/pymemcache/test/test_utils.py
--- old/pymemcache-2.2.2/pymemcache/test/test_utils.py  2019-05-07 
19:37:31.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache/test/test_utils.py  2019-12-22 
03:35:18.000000000 +0100
@@ -103,3 +103,13 @@
 
     client.decr(b"k", 2)
     assert client.get(b"k") == 4
+
+
[email protected]()
+def test_prepand_append():
+    client = MockMemcacheClient()
+
+    client.set(b"k", '1')
+    client.append(b"k", 'a')
+    client.prepend(b"k", 'p')
+    assert client.get(b"k") == b'p1a'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache/test/utils.py 
new/pymemcache-3.0.0/pymemcache/test/utils.py
--- old/pymemcache-2.2.2/pymemcache/test/utils.py       2019-07-05 
19:11:21.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache/test/utils.py       2019-12-22 
03:35:18.000000000 +0100
@@ -10,6 +10,7 @@
 import six
 
 from pymemcache.exceptions import MemcacheIllegalInputError
+from pymemcache.serde import LegacyWrappingSerde
 
 
 class MockMemcacheClient(object):
@@ -20,8 +21,7 @@
 
     def __init__(self,
                  server=None,
-                 serializer=None,
-                 deserializer=None,
+                 serde=None,
                  connect_timeout=None,
                  timeout=None,
                  no_delay=False,
@@ -32,8 +32,7 @@
 
         self._contents = {}
 
-        self.serializer = serializer
-        self.deserializer = deserializer
+        self.serde = serde or LegacyWrappingSerde(None, None)
         self.allow_unicode_keys = allow_unicode_keys
 
         # Unused, but present for interface compatibility
@@ -63,9 +62,7 @@
             del self._contents[key]
             return default
 
-        if self.deserializer:
-            return self.deserializer(key, value, flags)
-        return value
+        return self.serde.deserialize(key, value, flags)
 
     def get_many(self, keys):
         out = {}
@@ -94,9 +91,7 @@
             except (UnicodeEncodeError, UnicodeDecodeError):
                 raise MemcacheIllegalInputError
 
-        flags = 0
-        if self.serializer:
-            value, flags = self.serializer(key, value)
+        value, flags = self.serde.serialize(key, value)
 
         if expire:
             expire += time.time()
@@ -143,6 +138,30 @@
             self.delete(key, noreply)
         return True
 
+    def prepend(self, key, value, expire=0, noreply=None, flags=None):
+        current = self.get(key)
+        if current is not None:
+            if (isinstance(value, six.string_types) and
+                    not isinstance(value, six.binary_type)):
+                try:
+                    value = value.encode(self.encoding)
+                except (UnicodeEncodeError, UnicodeDecodeError):
+                    raise MemcacheIllegalInputError
+            self.set(key, value + current)
+        return True
+
+    def append(self, key, value, expire=0, noreply=None, flags=None):
+        current = self.get(key)
+        if current is not None:
+            if (isinstance(value, six.string_types) and
+                    not isinstance(value, six.binary_type)):
+                try:
+                    value = value.encode(self.encoding)
+                except (UnicodeEncodeError, UnicodeDecodeError):
+                    raise MemcacheIllegalInputError
+            self.set(key, current + value)
+        return True
+
     delete_multi = delete_many
 
     def stats(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/pymemcache.egg-info/PKG-INFO 
new/pymemcache-3.0.0/pymemcache.egg-info/PKG-INFO
--- old/pymemcache-2.2.2/pymemcache.egg-info/PKG-INFO   2019-08-06 
21:07:47.000000000 +0200
+++ new/pymemcache-3.0.0/pymemcache.egg-info/PKG-INFO   2020-01-02 
22:43:14.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pymemcache
-Version: 2.2.2
+Version: 3.0.0
 Summary: "A comprehensive, fast, pure Python memcached client"
 Home-page: https://github.com/pinterest/pymemcache
 Author: Charles Gordon
@@ -9,9 +9,6 @@
 Description: pymemcache
         ==========
         
-        .. image:: https://travis-ci.org/pinterest/pymemcache.svg
-            :target: https://travis-ci.org/pinterest/pymemcache
-        
         .. image:: https://img.shields.io/pypi/v/pymemcache.svg
             :target: https://pypi.python.org/pypi/pymemcache
         
@@ -76,10 +73,10 @@
         dependency on libmemcached poses challenges (e.g., it must be built 
against
         the same version of libmemcached that it will use at runtime).
         
-        Python-memcache
-        ---------------
+        python-memcached
+        ----------------
         
-        The python-memcache library implements the entire memcached text 
protocol, has
+        The python-memcached library implements the entire memcached text 
protocol, has
         a single timeout for all socket calls and has a flexible approach to
         serialization and deserialization. It is also written entirely in 
Python, so
         it works well with libraries like gevent. However, it is tied to using 
thread
@@ -134,6 +131,7 @@
         * `Nicholas Charriere <https://github.com/nichochar>`_
         * `Joe Gordon <https://github.com/jogo>`_
         * `Jon Parise <https://github.com/jparise>`_
+        * `Stephen Rosen <https://github.com/sirosen>`_
         
         We're Hiring!
         =============
@@ -143,6 +141,21 @@
         Changelog
         =========
         
+        New in version 3.0.0
+        --------------------
+        * The serialization API has been reworked. Instead of consuming a 
serializer
+          and deserializer as separate arguments, client objects now expect an 
argument
+          ``serde`` to be an object which implements ``serialize`` and 
``deserialize``
+          as methods. (``serialize`` and ``deserialize`` are still supported 
but
+          considered deprecated.)
+        * Validate integer inputs for ``expire``, ``delay``, ``incr``, 
``decr``, and
+          ``memlimit`` -- non-integer values now raise 
``MemcacheIllegalInputError``
+        * Validate inputs for ``cas`` -- values which are not integers or 
strings of
+          0-9 now raise ``MemcacheIllegalInputError``
+        * Add ``prepend`` and ``append`` support to ``MockMemcacheClient``.
+        * Add the ``touch`` method to ``HashClient``.
+        * Added official support for Python 3.8.
+        
         New in version 2.2.2
         --------------------
         * Fix ``long_description`` string in Python packaging.
@@ -277,6 +290,7 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: License :: OSI Approved :: Apache Software License
 Classifier: Topic :: Database
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymemcache-2.2.2/setup.cfg 
new/pymemcache-3.0.0/setup.cfg
--- old/pymemcache-2.2.2/setup.cfg      2019-08-06 21:07:47.000000000 +0200
+++ new/pymemcache-3.0.0/setup.cfg      2020-01-02 22:43:14.000000000 +0100
@@ -15,6 +15,7 @@
        Programming Language :: Python :: 3.5
        Programming Language :: Python :: 3.6
        Programming Language :: Python :: 3.7
+       Programming Language :: Python :: 3.8
        Programming Language :: Python :: Implementation :: PyPy
        License :: OSI Approved :: Apache Software License
        Topic :: Database


Reply via email to