Hello community, here is the log from the commit of package python-cloudpickle for openSUSE:Factory checked in at 2019-09-30 15:56:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-cloudpickle (Old) and /work/SRC/openSUSE:Factory/.python-cloudpickle.new.2352 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-cloudpickle" Mon Sep 30 15:56:31 2019 rev:8 rq:733396 version:1.2.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-cloudpickle/python-cloudpickle.changes 2019-07-31 14:15:18.638794685 +0200 +++ /work/SRC/openSUSE:Factory/.python-cloudpickle.new.2352/python-cloudpickle.changes 2019-09-30 15:56:32.597672606 +0200 @@ -1,0 +2,8 @@ +Thu Sep 26 10:40:19 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.2.2: + * Revert the change introduced in (issue #276) attempting to pickle functions annotations for Python 3.4 to 3.6. It is not possible to pickle complex typing constructs for those versions (see issue #193) + * Fix a bug affecting bound classmethod saving on Python 2. (issue #288) + * Add support for pickling "getset" descriptors (issue #290) + +------------------------------------------------------------------- Old: ---- cloudpickle-1.2.1.tar.gz New: ---- cloudpickle-1.2.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-cloudpickle.spec ++++++ --- /var/tmp/diff_new_pack.OuVfmM/_old 2019-09-30 15:56:33.149671137 +0200 +++ /var/tmp/diff_new_pack.OuVfmM/_new 2019-09-30 15:56:33.149671137 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-cloudpickle -Version: 1.2.1 +Version: 1.2.2 Release: 0 Summary: Extended pickling support for Python objects License: BSD-3-Clause ++++++ cloudpickle-1.2.1.tar.gz -> cloudpickle-1.2.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/PKG-INFO new/cloudpickle-1.2.2/PKG-INFO --- old/cloudpickle-1.2.1/PKG-INFO 2019-06-10 21:56:01.000000000 +0200 +++ new/cloudpickle-1.2.2/PKG-INFO 2019-09-10 14:27:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: cloudpickle -Version: 1.2.1 +Version: 1.2.2 Summary: Extended pickling support for Python objects Home-page: https://github.com/cloudpipe/cloudpickle Author: Cloudpipe @@ -23,11 +23,15 @@ along with **functions and classes defined interactively** in the `__main__` module (for instance in a script, a shell or a Jupyter notebook). - **`cloudpickle` uses `pickle.HIGHEST_PROTOCOL` by default**: it is meant to - send objects between processes running the **same version of Python**. + Cloudpickle can only be used to send objects between the **exact same version + of Python**. Using `cloudpickle` for **long-term object storage is not supported and - discouraged.** + strongly discouraged.** + + **Security notice**: one should **only load pickle data from trusted sources** as + otherwise `pickle.load` can lead to arbitrary code execution resulting in a critical + security vulnerability. Installation @@ -89,6 +93,25 @@ PYTHONPATH='.:tests' py.test + Note about function Annotations + ------------------------------- + + Note that because of design issues `Python`'s `typing` module, `cloudpickle` + supports pickling type annotations of dynamic functions for `Python` 3.7 and + later. On `Python` 3.4, 3.5 and 3.6, those type annotations will be dropped + silently during pickling (example below): + + ```python + >>> import typing + >>> import cloudpickle + >>> def f(x: typing.Union[list, int]): + ... return x + >>> f + <function __main__.f(x:Union[list, int])> + >>> cloudpickle.loads(cloudpickle.dumps(f)) # drops f's annotations + <function __main__.f(x)> + ``` + History ------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/README.md new/cloudpickle-1.2.2/README.md --- old/cloudpickle-1.2.1/README.md 2019-04-17 11:27:32.000000000 +0200 +++ new/cloudpickle-1.2.2/README.md 2019-09-10 14:19:13.000000000 +0200 @@ -15,11 +15,15 @@ along with **functions and classes defined interactively** in the `__main__` module (for instance in a script, a shell or a Jupyter notebook). -**`cloudpickle` uses `pickle.HIGHEST_PROTOCOL` by default**: it is meant to -send objects between processes running the **same version of Python**. +Cloudpickle can only be used to send objects between the **exact same version +of Python**. Using `cloudpickle` for **long-term object storage is not supported and -discouraged.** +strongly discouraged.** + +**Security notice**: one should **only load pickle data from trusted sources** as +otherwise `pickle.load` can lead to arbitrary code execution resulting in a critical +security vulnerability. Installation @@ -81,6 +85,25 @@ PYTHONPATH='.:tests' py.test +Note about function Annotations +------------------------------- + +Note that because of design issues `Python`'s `typing` module, `cloudpickle` +supports pickling type annotations of dynamic functions for `Python` 3.7 and +later. On `Python` 3.4, 3.5 and 3.6, those type annotations will be dropped +silently during pickling (example below): + +```python +>>> import typing +>>> import cloudpickle +>>> def f(x: typing.Union[list, int]): +... return x +>>> f +<function __main__.f(x:Union[list, int])> +>>> cloudpickle.loads(cloudpickle.dumps(f)) # drops f's annotations +<function __main__.f(x)> +``` + History ------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/cloudpickle/__init__.py new/cloudpickle-1.2.2/cloudpickle/__init__.py --- old/cloudpickle-1.2.1/cloudpickle/__init__.py 2019-06-10 21:55:30.000000000 +0200 +++ new/cloudpickle-1.2.2/cloudpickle/__init__.py 2019-09-10 14:19:34.000000000 +0200 @@ -8,4 +8,4 @@ if sys.version_info[:2] >= (3, 8): from cloudpickle.cloudpickle_fast import CloudPickler, dumps, dump -__version__ = '1.2.1' +__version__ = '1.2.2' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/cloudpickle/cloudpickle.py new/cloudpickle-1.2.2/cloudpickle/cloudpickle.py --- old/cloudpickle-1.2.1/cloudpickle/cloudpickle.py 2019-06-10 21:54:13.000000000 +0200 +++ new/cloudpickle-1.2.2/cloudpickle/cloudpickle.py 2019-09-10 14:19:13.000000000 +0200 @@ -274,41 +274,80 @@ return subimports -def _make_cell_set_template_code(): - """Get the Python compiler to emit LOAD_FAST(arg); STORE_DEREF - - Notes - ----- - In Python 3, we could use an easier function: - - .. code-block:: python - - def f(): - cell = None +def cell_set(cell, value): + """Set the value of a closure cell. - def _stub(value): - nonlocal cell - cell = value + The point of this function is to set the cell_contents attribute of a cell + after its creation. This operation is necessary in case the cell contains a + reference to the function the cell belongs to, as when calling the + function's constructor + ``f = types.FunctionType(code, globals, name, argdefs, closure)``, + closure will not be able to contain the yet-to-be-created f. + + In Python3.7, cell_contents is writeable, so setting the contents of a cell + can be done simply using + >>> cell.cell_contents = value + + In earlier Python3 versions, the cell_contents attribute of a cell is read + only, but this limitation can be worked around by leveraging the Python 3 + ``nonlocal`` keyword. + + In Python2 however, this attribute is read only, and there is no + ``nonlocal`` keyword. For this reason, we need to come up with more + complicated hacks to set this attribute. + + The chosen approach is to create a function with a STORE_DEREF opcode, + which sets the content of a closure variable. Typically: + + >>> def inner(value): + ... lambda: cell # the lambda makes cell a closure + ... cell = value # cell is a closure, so this triggers a STORE_DEREF + + (Note that in Python2, A STORE_DEREF can never be triggered from an inner + function. The function g for example here + >>> def f(var): + ... def g(): + ... var += 1 + ... return g + + will not modify the closure variable ``var```inplace, but instead try to + load a local variable var and increment it. As g does not assign the local + variable ``var`` any initial value, calling f(1)() will fail at runtime.) + + Our objective is to set the value of a given cell ``cell``. So we need to + somewhat reference our ``cell`` object into the ``inner`` function so that + this object (and not the smoke cell of the lambda function) gets affected + by the STORE_DEREF operation. + + In inner, ``cell`` is referenced as a cell variable (an enclosing variable + that is referenced by the inner function). If we create a new function + cell_set with the exact same code as ``inner``, but with ``cell`` marked as + a free variable instead, the STORE_DEREF will be applied on its closure - + ``cell``, which we can specify explicitly during construction! The new + cell_set variable thus actually sets the contents of a specified cell! + + Note: we do not make use of the ``nonlocal`` keyword to set the contents of + a cell in early python3 versions to limit possible syntax errors in case + test and checker libraries decide to parse the whole file. + """ - return _stub + if sys.version_info[:2] >= (3, 7): # pragma: no branch + cell.cell_contents = value + else: + _cell_set = types.FunctionType( + _cell_set_template_code, {}, '_cell_set', (), (cell,),) + _cell_set(value) - _cell_set_template_code = f().__code__ - This function is _only_ a LOAD_FAST(arg); STORE_DEREF, but that is - invalid syntax on Python 2. If we use this function we also don't need - to do the weird freevars/cellvars swap below - """ - def inner(value): - lambda: cell # make ``cell`` a closure so that we get a STORE_DEREF +def _make_cell_set_template_code(): + def _cell_set_factory(value): + lambda: cell cell = value - co = inner.__code__ + co = _cell_set_factory.__code__ - # NOTE: we are marking the cell variable as a free variable intentionally - # so that we simulate an inner function instead of the outer function. This - # is what gives us the ``nonlocal`` behavior in a Python 2 compatible way. if PY2: # pragma: no branch - return types.CodeType( + _cell_set_template_code = types.CodeType( co.co_argcount, co.co_nlocals, co.co_stacksize, @@ -321,62 +360,32 @@ co.co_name, co.co_firstlineno, co.co_lnotab, - co.co_cellvars, # this is the trickery - (), + co.co_cellvars, # co_freevars is initialized with co_cellvars + (), # co_cellvars is made empty ) else: - if hasattr(types.CodeType, "co_posonlyargcount"): # pragma: no branch - return types.CodeType( - co.co_argcount, - co.co_posonlyargcount, # Python3.8 with PEP570 - co.co_kwonlyargcount, - co.co_nlocals, - co.co_stacksize, - co.co_flags, - co.co_code, - co.co_consts, - co.co_names, - co.co_varnames, - co.co_filename, - co.co_name, - co.co_firstlineno, - co.co_lnotab, - co.co_cellvars, # this is the trickery - (), - ) - else: - return types.CodeType( - co.co_argcount, - co.co_kwonlyargcount, - co.co_nlocals, - co.co_stacksize, - co.co_flags, - co.co_code, - co.co_consts, - co.co_names, - co.co_varnames, - co.co_filename, - co.co_name, - co.co_firstlineno, - co.co_lnotab, - co.co_cellvars, # this is the trickery - (), - ) - -_cell_set_template_code = _make_cell_set_template_code() - + _cell_set_template_code = types.CodeType( + co.co_argcount, + co.co_kwonlyargcount, # Python 3 only argument + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + co.co_cellvars, # co_freevars is initialized with co_cellvars + (), # co_cellvars is made empty + ) + return _cell_set_template_code -def cell_set(cell, value): - """Set the value of a closure cell. - """ - return types.FunctionType( - _cell_set_template_code, - {}, - '_cell_set_inner', - (), - (cell,), - )(value) +if sys.version_info[:2] < (3, 7): + _cell_set_template_code = _make_cell_set_template_code() # relevant opcodes STORE_GLOBAL = opcode.opmap['STORE_GLOBAL'] @@ -738,7 +747,9 @@ 'doc': func.__doc__, '_cloudpickle_submodules': submodules } - if hasattr(func, '__annotations__') and sys.version_info >= (3, 4): + if hasattr(func, '__annotations__') and sys.version_info >= (3, 7): + # Although annotations were added in Python3.4, It is not possible + # to properly pickle them until Python3.7. (See #193) state['annotations'] = func.__annotations__ if hasattr(func, '__qualname__'): state['qualname'] = func.__qualname__ @@ -839,6 +850,11 @@ method_descriptor = type(str.upper) dispatch[method_descriptor] = save_builtin_function_or_method + def save_getset_descriptor(self, obj): + return self.save_reduce(getattr, (obj.__objclass__, obj.__name__)) + + dispatch[types.GetSetDescriptorType] = save_getset_descriptor + def save_global(self, obj, name=None, pack=struct.pack): """ Save a "global". @@ -873,8 +889,9 @@ if PY3: # pragma: no branch self.save_reduce(types.MethodType, (obj.__func__, obj.__self__), obj=obj) else: - self.save_reduce(types.MethodType, (obj.__func__, obj.__self__, obj.__self__.__class__), - obj=obj) + self.save_reduce( + types.MethodType, + (obj.__func__, obj.__self__, type(obj.__self__)), obj=obj) dispatch[types.MethodType] = save_instancemethod diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/cloudpickle/cloudpickle_fast.py new/cloudpickle-1.2.2/cloudpickle/cloudpickle_fast.py --- old/cloudpickle-1.2.1/cloudpickle/cloudpickle_fast.py 2019-06-07 19:01:53.000000000 +0200 +++ new/cloudpickle-1.2.2/cloudpickle/cloudpickle_fast.py 2019-08-02 21:50:49.000000000 +0200 @@ -260,6 +260,10 @@ return _file_reconstructor, (retval,) +def _getset_descriptor_reduce(obj): + return getattr, (obj.__objclass__, obj.__name__) + + def _mappingproxy_reduce(obj): return types.MappingProxyType, (dict(obj),) @@ -405,6 +409,7 @@ dispatch[staticmethod] = _classmethod_reduce dispatch[types.CellType] = _cell_reduce dispatch[types.CodeType] = _code_reduce + dispatch[types.GetSetDescriptorType] = _getset_descriptor_reduce dispatch[types.ModuleType] = _module_reduce dispatch[types.MethodType] = _method_reduce dispatch[types.MappingProxyType] = _mappingproxy_reduce diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/cloudpickle.egg-info/PKG-INFO new/cloudpickle-1.2.2/cloudpickle.egg-info/PKG-INFO --- old/cloudpickle-1.2.1/cloudpickle.egg-info/PKG-INFO 2019-06-10 21:56:01.000000000 +0200 +++ new/cloudpickle-1.2.2/cloudpickle.egg-info/PKG-INFO 2019-09-10 14:27:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: cloudpickle -Version: 1.2.1 +Version: 1.2.2 Summary: Extended pickling support for Python objects Home-page: https://github.com/cloudpipe/cloudpickle Author: Cloudpipe @@ -23,11 +23,15 @@ along with **functions and classes defined interactively** in the `__main__` module (for instance in a script, a shell or a Jupyter notebook). - **`cloudpickle` uses `pickle.HIGHEST_PROTOCOL` by default**: it is meant to - send objects between processes running the **same version of Python**. + Cloudpickle can only be used to send objects between the **exact same version + of Python**. Using `cloudpickle` for **long-term object storage is not supported and - discouraged.** + strongly discouraged.** + + **Security notice**: one should **only load pickle data from trusted sources** as + otherwise `pickle.load` can lead to arbitrary code execution resulting in a critical + security vulnerability. Installation @@ -89,6 +93,25 @@ PYTHONPATH='.:tests' py.test + Note about function Annotations + ------------------------------- + + Note that because of design issues `Python`'s `typing` module, `cloudpickle` + supports pickling type annotations of dynamic functions for `Python` 3.7 and + later. On `Python` 3.4, 3.5 and 3.6, those type annotations will be dropped + silently during pickling (example below): + + ```python + >>> import typing + >>> import cloudpickle + >>> def f(x: typing.Union[list, int]): + ... return x + >>> f + <function __main__.f(x:Union[list, int])> + >>> cloudpickle.loads(cloudpickle.dumps(f)) # drops f's annotations + <function __main__.f(x)> + ``` + History ------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cloudpickle-1.2.1/tests/cloudpickle_test.py new/cloudpickle-1.2.2/tests/cloudpickle_test.py --- old/cloudpickle-1.2.1/tests/cloudpickle_test.py 2019-06-10 21:54:13.000000000 +0200 +++ new/cloudpickle-1.2.2/tests/cloudpickle_test.py 2019-09-10 14:19:13.000000000 +0200 @@ -431,6 +431,15 @@ self.assertEqual(A.test_sm(), "sm") self.assertEqual(A.test_cm(), "cm") + def test_bound_classmethod(self): + class A: + @classmethod + def test_cm(cls): + return "cm" + + A.test_cm = pickle_depickle(A.test_cm, protocol=self.protocol) + self.assertEqual(A.test_cm(), "cm") + def test_method_descriptors(self): f = pickle_depickle(str.upper) self.assertEqual(f('abc'), 'ABC') @@ -661,10 +670,9 @@ # builtin function from the __builtin__ module assert pickle_depickle(zip, protocol=self.protocol) is zip - from sys import getcheckinterval + from os import mkdir # builtin function from a "regular" module - assert pickle_depickle( - getcheckinterval, protocol=self.protocol) is getcheckinterval + assert pickle_depickle(mkdir, protocol=self.protocol) is mkdir @pytest.mark.skipif(platform.python_implementation() == 'PyPy' and sys.version_info[:2] == (3, 5), @@ -975,6 +983,11 @@ # logging.Logger object self.check_logger('cloudpickle.dummy_test_logger') + def test_getset_descriptor(self): + assert isinstance(float.real, types.GetSetDescriptorType) + depickled_descriptor = pickle_depickle(float.real) + self.assertIs(depickled_descriptor, float.real) + def test_abc(self): @abc.abstractmethod @@ -1607,9 +1620,9 @@ self.assertEqual(f2.__doc__, f.__doc__) - @unittest.skipIf(sys.version_info < (3, 4), - """This syntax won't work on py2 and pickling annotations - isn't supported for py34 and below.""") + @unittest.skipIf(sys.version_info < (3, 7), + "This syntax won't work on py2 and pickling annotations " + "isn't supported for py37 and below.") def test_wraps_preserves_function_annotations(self): from functools import wraps @@ -1626,6 +1639,16 @@ self.assertEqual(f2.__annotations__, f.__annotations__) + @unittest.skipIf(sys.version_info < (3, 7), + """This syntax won't work on py2 and pickling annotations + isn't supported for py37 and below.""") + def test_type_hint(self): + # Try to pickle compound typing constructs. This would typically fail + # on Python < 3.7 (See #193) + import typing + t = typing.Union[list, int] + assert pickle_depickle(t) == t + def test_instance_with_slots(self): for slots in [["registered_attribute"], "registered_attribute"]: class ClassWithSlots(object):