Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cloudpickle incompatibilities on early Python 3.5 versions #361

Merged
merged 33 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fcbce90
fix basic incompatibilities with old Python3.5
pierreglaser Apr 28, 2020
3fa3227
fix pickling of type hints in Python 3.5.[0-2]
pierreglaser Apr 28, 2020
732343e
modify typing test to account for Python 3.5.[0-2]
pierreglaser Apr 28, 2020
3adc5be
fixup! fix pickling of type hints in Python 3.5.[0-2]
pierreglaser Apr 28, 2020
d2b8aa6
fixup! fix pickling of type hints in Python 3.5.[0-2]
pierreglaser Apr 28, 2020
96c1d2f
update changelog
pierreglaser Apr 28, 2020
7996513
fix mistake in parametrized type hint checker
pierreglaser Apr 28, 2020
f164a29
try to test cloudpickle agains Python 3.5.0 in CI
pierreglaser Apr 28, 2020
a7e521f
try to use conda python in the CI
pierreglaser Apr 28, 2020
2c13f90
Merge branch 'master' into cloudpickle-py350
ogrisel Apr 28, 2020
4af5e8d
fixup! try to use conda python in the CI
pierreglaser Apr 28, 2020
47d981a
Merge branch 'cloudpickle-py350' of github.com:pierreglaser/cloudpick…
pierreglaser Apr 28, 2020
2c9e644
debug miniconda
pierreglaser Apr 28, 2020
d5e0586
fixup! debug miniconda
pierreglaser Apr 28, 2020
f50f87f
fixup! fixup! debug miniconda
pierreglaser Apr 28, 2020
726d86d
fixup! fixup! debug miniconda
pierreglaser Apr 28, 2020
0d834ce
fixup! fixup! fixup! debug miniconda
pierreglaser Apr 28, 2020
405b99a
a few last tries
pierreglaser Apr 28, 2020
b1eca25
fixup! a few last tries
pierreglaser Apr 28, 2020
abd46d7
I think I get it
pierreglaser Apr 28, 2020
1fb09a0
rollback CI changes
pierreglaser Apr 28, 2020
736127c
fix some Python version check
pierreglaser Apr 28, 2020
6e3621d
this cannot be done using version checks
pierreglaser Apr 28, 2020
ec41dee
this cannot be done using version checks
pierreglaser Apr 28, 2020
ae643fb
fix typing_extension tests for early Python 3.5
pierreglaser Apr 28, 2020
a9dadd6
revert TypeVar tracking
pierreglaser Apr 28, 2020
acd23e4
fix sys version checks
pierreglaser Apr 28, 2020
e573c37
add a few explanations
pierreglaser Apr 28, 2020
c9c8b46
Update cloudpickle/cloudpickle.py
ogrisel Apr 29, 2020
cf9e42e
Re-enable dynamic TypeVar tracking
ogrisel Apr 29, 2020
8c030a3
Apply suggestions from code review
ogrisel Apr 29, 2020
6359bc5
be more explicit when testing Final/Literal
pierreglaser Apr 29, 2020
e58c34b
Workaround for Python 3.5.3
ogrisel Apr 29, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python_version: [3.5, 3.6, 3.7, 3.8, "pypy3"]
python_version: ["3.5.0", 3.6, 3.7, 3.8, "pypy3"]
exclude:
# Do not test all minor versions on all platforms, especially if they
# are not the oldest/newest supported versions
Expand All @@ -50,31 +50,34 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python_version }}
uses: actions/setup-python@v1
uses: goanpeca/setup-miniconda@v1
with:
python-version: ${{ matrix.python_version }}
auto-update-conda: true
python-version: ${{ matrix.python-version }}
auto-activate-base: false
activate-environment: testenv
- name: Install project and dependencies
shell: bash
shell: bash -l {0}
run: |
python -m pip install --upgrade pip
python -m pip install -e .
python -m pip install -r dev-requirements.txt
python ci/install_coverage_subprocess_pth.py
export
- name: Install optional typing_extensions in Python 3.6
shell: bash
shell: bash -l {0}
run: python -m pip install typing-extensions
if: matrix.python_version == '3.6'
- name: Display Python version
shell: bash
shell: bash -l {0}
run: python -c "import sys; print(sys.version)"
- name: Look for syntax errors/undefined names
shell: bash
shell: bash -l {0}
run: |
python -m flake8 . --count --verbose --select=E901,E999,F821,F822,F823 \
--show-source --statistics
- name: Test with pytest
shell: bash
shell: bash -l {0}
run: |
COVERAGE_PROCESS_START=$GITHUB_WORKSPACE/.coveragerc \
PYTHONPATH='.:tests' python -m pytest -r s
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
1.4.1 (in development)
======================

- Fix incompatibilities between cloudpickle 1.4.0 and Python 3.5.0/1/2
introduced by the new support of cloudpickle for pickling typing constructs.
([issue #360](https://github.com/cloudpipe/cloudpickle/issues/360))

- Restore compat with loading dynamic classes pickled with cloudpickle
version 1.2.1 that would reference the `types.ClassType` attribute.
([PR #359](https://github.com/cloudpipe/cloudpickle/pull/359))
Expand Down
64 changes: 53 additions & 11 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
import typing
from enum import Enum

from typing import Generic, Union, Tuple, Callable, ClassVar
from typing import Generic, Union, Tuple, Callable
from pickle import _Pickler as Pickler
from pickle import _getattribute
from io import BytesIO
Expand All @@ -73,6 +73,11 @@
except ImportError:
_typing_extensions = Literal = Final = None

if sys.version_info > (3, 5, 2):
from typing import ClassVar
else: # pragma: no cover
ClassVar = None


# cloudpickle is meant for inter process communication: we expect all
# communicating processes to run the same Python version hence we favor
Expand Down Expand Up @@ -432,10 +437,24 @@ def _extract_class_dict(cls):
if sys.version_info[:2] < (3, 7): # pragma: no branch
def _is_parametrized_type_hint(obj):
# This is very cheap but might generate false positives.
origin = getattr(obj, '__origin__', None) # typing Constructs
values = getattr(obj, '__values__', None) # typing_extensions.Literal
type_ = getattr(obj, '__type__', None) # typing_extensions.Final
return origin is not None or values is not None or type_ is not None
# genral typing Constructs
pierreglaser marked this conversation as resolved.
Show resolved Hide resolved
ogrisel marked this conversation as resolved.
Show resolved Hide resolved
is_typing = getattr(obj, '__origin__', None) is not None

# typing_extensions.Literal
is_litteral = getattr(obj, '__values__', None) is not None

# typing_extensions.Final
is_final = getattr(obj, '__type__', None) is not None

# typing.Union/Tuple for old Python 3.5
is_union = getattr(obj, '__union_params__', None) is not None
is_tuple = getattr(obj, '__tuple_params__', None) is not None
is_callable = (
getattr(obj, '__result__', None) is not None and
getattr(obj, '__args__', None) is not None
)
return any((is_typing, is_litteral, is_final, is_union, is_tuple,
is_callable))

def _create_parametrized_type_hint(origin, args):
return origin[args]
Expand Down Expand Up @@ -971,14 +990,37 @@ def _save_parametrized_type_hint(self, obj):
initargs = (Final, obj.__type__)
elif type(obj) is type(ClassVar):
initargs = (ClassVar, obj.__type__)
elif type(obj) in [type(Union), type(Tuple), type(Generic)]:
initargs = (obj.__origin__, obj.__args__)
elif type(obj) is type(Generic):
if sys.version_info < (3, 5, 2): # pragma: no cover
pierreglaser marked this conversation as resolved.
Show resolved Hide resolved
initargs = (obj.__origin__, obj.__parameters__)
else:
initargs = (obj.__origin__, obj.__args__)
elif type(obj) is type(Union):
if sys.version_info < (3, 5, 2): # pragma: no cover
initargs = (Union, obj.__union_params__)
else:
initargs = (Union, obj.__args__)
elif type(obj) is type(Tuple):
if sys.version_info < (3, 5, 2): # pragma: no cover
initargs = (Tuple, obj.__tuple_params__)
else:
initargs = (Tuple, obj.__args__)
elif type(obj) is type(Callable):
args = obj.__args__
if args[0] is Ellipsis:
initargs = (obj.__origin__, args)
if sys.version_info < (3, 5, 2): # pragma: no cover
args = obj.__args__
result = obj.__result__
if args != Ellipsis:
if isinstance(args, tuple):
args = list(args)
else:
args = [args]
else:
initargs = (obj.__origin__, (list(args[:-1]), args[-1]))
(*args, result) = obj.__args__
if len(args) == 1 and args[0] is Ellipsis:
args = Ellipsis
else:
args = list(args)
initargs = (Callable, (args, result))
else: # pragma: no cover
raise pickle.PicklingError(
"Cloudpickle Error: Unknown type {}".format(type(obj))
Expand Down
29 changes: 20 additions & 9 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2083,12 +2083,18 @@ class C(typing.Generic[T]):

def check_generic(generic, origin, type_value):
assert generic.__origin__ is origin
assert len(generic.__args__) == 1
assert generic.__args__[0] is type_value

assert len(origin.__orig_bases__) == 1
ob = origin.__orig_bases__[0]
assert ob.__origin__ is typing.Generic
if sys.version_info > (3, 5, 2):
assert len(generic.__args__) == 1
assert generic.__args__[0] is type_value
assert len(origin.__orig_bases__) == 1
ob = origin.__orig_bases__[0]
assert ob.__origin__ is typing.Generic
else: # Python 3.5.[0-1-2]
ogrisel marked this conversation as resolved.
Show resolved Hide resolved
assert len(generic.__parameters__) == 1
assert generic.__parameters__[0] is type_value
assert len(origin.__bases__) == 1
ob = origin.__bases__[0]
assert len(ob.__parameters__) == 1

return "ok"
Expand Down Expand Up @@ -2182,21 +2188,26 @@ def _all_types_to_test():
class C(typing.Generic[T]):
pass

return [
types_to_test = [
C, C[int],
T, typing.Any, typing.NoReturn, typing.Optional,
typing.Generic, typing.Union, typing.ClassVar,
T, typing.Any, typing.Optional,
typing.Generic, typing.Union,
typing.Optional[int],
typing.Generic[T],
typing.Callable[[int], typing.Any],
typing.Callable[..., typing.Any],
typing.Callable[[], typing.Any],
typing.Tuple[int, ...],
typing.Tuple[int, C[int]],
typing.ClassVar[C[int]],
typing.List[int],
typing.Dict[int, str],
]
if sys.version_info > (3, 5, 2):
types_to_test.append(typing.ClassVar)
types_to_test.append(typing.ClassVar[C[int]])
if sys.version_info > (3, 5, 3):
types_to_test.append(typing.NoReturn)
return types_to_test


if __name__ == '__main__':
Expand Down