- Notifications
You must be signed in to change notification settings - Fork 183
Support pickling dynamic classes subclassing typing.Generic instances on 3.7+ #351
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
Changes from all commits
7b4bb6d 7fd4412 d67aad0 20a8835 59e648e dc25224 82b5f31 5326c8c 43ef753 6854502 5791b84 File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -50,6 +50,7 @@ | |
| | ||
| from .testutils import subprocess_pickle_echo | ||
| from .testutils import assert_run_python_script | ||
| from .testutils import subprocess_worker | ||
| | ||
| | ||
| _TEST_GLOBAL_VARIABLE = "default_value" | ||
| | @@ -2121,6 +2122,12 @@ def test_pickle_dynamic_typevar(self): | |
| for attr in attr_list: | ||
| assert getattr(T, attr) == getattr(depickled_T, attr) | ||
| | ||
| def test_pickle_dynamic_typevar_memoization(self): | ||
| T = typing.TypeVar('T') | ||
| depickled_T1, depickled_T2 = pickle_depickle((T, T), | ||
| protocol=self.protocol) | ||
| assert depickled_T1 is depickled_T2 | ||
| | ||
| def test_pickle_importable_typevar(self): | ||
| from .mypkg import T | ||
| T1 = pickle_depickle(T, protocol=self.protocol) | ||
| | @@ -2130,6 +2137,61 @@ def test_pickle_importable_typevar(self): | |
| from typing import AnyStr | ||
| assert AnyStr is pickle_depickle(AnyStr, protocol=self.protocol) | ||
| | ||
| @unittest.skipIf(sys.version_info < (3, 7), | ||
| "Pickling generics not supported below py37") | ||
| def test_generic_type(self): | ||
| T = typing.TypeVar('T') | ||
| | ||
| class C(typing.Generic[T]): | ||
| pass | ||
| | ||
| assert pickle_depickle(C, protocol=self.protocol) is C | ||
| assert pickle_depickle(C[int], protocol=self.protocol) is C[int] | ||
| | ||
| with subprocess_worker(protocol=self.protocol) as worker: | ||
| | ||
| 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 | ||
| assert len(ob.__parameters__) == 1 | ||
| | ||
| return "ok" | ||
| | ||
| assert check_generic(C[int], C, int) == "ok" | ||
| assert worker.run(check_generic, C[int], C, int) == "ok" | ||
| | ||
| @unittest.skipIf(sys.version_info < (3, 7), | ||
| "Pickling type hints not supported below py37") | ||
| def test_locally_defined_class_with_type_hints(self): | ||
| Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name used in this test is a little bit confusing: are we testing classes inheriting from Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I my initial commit, this test was testing for all kinds of type annotations, both simple literal types and more complex custom types based on generics and their subclasses. | ||
| with subprocess_worker(protocol=self.protocol) as worker: | ||
| for type_ in _all_types_to_test(): | ||
| # The type annotation syntax causes a SyntaxError on Python 3.5 | ||
| code = textwrap.dedent("""\ | ||
| class MyClass: | ||
| attribute: type_ | ||
| Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make things clear: pickling class A(typing.Generic[T]): ...semantic. the type hint used in Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Maybe, but it was not properly tested. So better take the opportunity to increase the test coverage in general, not just the specific fix from this PR. | ||
| | ||
| def method(self, arg: type_) -> type_: | ||
| return arg | ||
| """) | ||
| ns = {"type_": type_} | ||
| exec(code, ns) | ||
| MyClass = ns["MyClass"] | ||
| | ||
| def check_annotations(obj, expected_type): | ||
| assert obj.__annotations__["attribute"] is expected_type | ||
| assert obj.method.__annotations__["arg"] is expected_type | ||
ogrisel marked this conversation as resolved. Show resolved Hide resolved | ||
| assert obj.method.__annotations__["return"] is expected_type | ||
| return "ok" | ||
| | ||
| obj = MyClass() | ||
| assert check_annotations(obj, type_) == "ok" | ||
| assert worker.run(check_annotations, obj, type_) == "ok" | ||
| | ||
| | ||
| class Protocol2CloudPickleTest(CloudPickleTest): | ||
| | ||
| | @@ -2161,5 +2223,28 @@ def test_lookup_module_and_qualname_stdlib_typevar(): | |
| assert name == 'AnyStr' | ||
| | ||
| | ||
| def _all_types_to_test(): | ||
| T = typing.TypeVar('T') | ||
| | ||
| class C(typing.Generic[T]): | ||
| pass | ||
| | ||
| return [ | ||
| C, C[int], | ||
| T, typing.Any, typing.NoReturn, typing.Optional, | ||
| typing.Generic, typing.Union, typing.ClassVar, | ||
| 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 __name__ == '__main__': | ||
| unittest.main() | ||
Uh oh!
There was an error while loading. Please reload this page.