-
Notifications
You must be signed in to change notification settings - Fork 193
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 8 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,38 @@ 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): | ||
| for obj in _generic_objects_to_test(): | ||
| pickle_depickle(obj, protocol=self.protocol) | ||
|
|
||
| @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 _generic_objects_to_test(): | ||
|
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 would be in favor of using this test to check not only generic types, but all kinds of type annotations, including simple literals from the typing module as was done before.
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. Trying to understand here: why would you want the pickling of these constructs to be tested in the same, single test (and not 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. All type annotations should be preserved, dynamic or non-dynamic. We could have additional tests for things that are specific to dynamic ones but we should check that we do not break the pickling of annotations with non-dynamic types too, no?
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. I agree. I simply want [my/your/any-cloudpickle-contributor/maintainer] future-self to be able to quickly understand why a specific line/commit exists, and this through atomic unit tests (we can also have composite ones, but to me atomic ones are necessary), related to the exact feature we decided to introduce in the said line/commit. In our case, this PR introduces support for pickling classes subclassing
But why mixing everything up in one single test? The test suite of Two final remarks:
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. +1 for an atomic test for public Generic type subclasses alone (without the subprocess thing). But I like to also have a big integration test that test dynamic classes annotated with all the possible kinds of types (literal, typevars, generic and generic subclasses). |
||
| # 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.
|
||
| return "ok" | ||
|
|
||
| obj = MyClass() | ||
| assert check_annotations(obj, type_) == "ok" | ||
| assert worker.run(check_annotations, obj, type_) == "ok" | ||
|
|
||
|
|
||
| class Protocol2CloudPickleTest(CloudPickleTest): | ||
|
|
||
|
|
@@ -2161,5 +2200,15 @@ def test_lookup_module_and_qualname_stdlib_typevar(): | |
| assert name == 'AnyStr' | ||
|
|
||
|
|
||
| def _generic_objects_to_test(): | ||
| T = typing.TypeVar('T') | ||
|
|
||
| class C(typing.Generic[T]): | ||
| pass | ||
|
|
||
| yield C | ||
| yield C[int] | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
Uh oh!
There was an error while loading. Please reload this page.