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