From ab33490dec3644f5d123994e56088b345f1bfd3b Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 10:46:36 +0200 Subject: [PATCH 1/6] Pickle enums by value instead of name (restores pre-3.11 behavior) to support `dataclasses.asdict` --- construct_typed/tenum.py | 14 +++++++++++ tests/test_typed.py | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/construct_typed/tenum.py b/construct_typed/tenum.py index 3712957..08e4a4b 100644 --- a/construct_typed/tenum.py +++ b/construct_typed/tenum.py @@ -74,6 +74,13 @@ class EnumBase(enum.IntEnum): return pseudo_member return None # will raise the ValueError in Enum.__new__ + def __reduce_ex__(self, proto: t.Any): + """ + Pickle enums by value instead of name (restores pre-3.11 behavior). + See https://github.com/python/cpython/pull/26658 for why this exists. + """ + return self.__class__, (self._value_,) + EnumType = t.TypeVar("EnumType", bound=EnumBase) @@ -171,6 +178,13 @@ class FlagsEnumBase(enum.IntFlag): new_member.__doc__ = "missing value" return new_member + def __reduce_ex__(self, proto: t.Any): + """ + Pickle enums by value instead of name (restores pre-3.11 behavior). + See https://github.com/python/cpython/pull/26658 for why this exists. + """ + return self.__class__, (self._value_,) + FlagsEnumType = t.TypeVar("FlagsEnumType", bound=FlagsEnumBase) diff --git a/tests/test_typed.py b/tests/test_typed.py index 756b0f6..4dd9528 100644 --- a/tests/test_typed.py +++ b/tests/test_typed.py @@ -384,6 +384,32 @@ def test_tenum_no_enumbase() -> None: assert raises(lambda: cst.TEnum(cs.Byte, cls)) == TypeError +def test_tenum_asdict(): + # see: https://github.com/timrid/construct-typing/issues/21 + import construct_typed as cst + import dataclasses + + class TestEnum(cst.EnumBase): + one = 1 + two = 2 + four = 4 + eight = 8 + + @dataclasses.dataclass + class SomeDataclass: + a: TestEnum + + dc = SomeDataclass(TestEnum.one) + dc_dict = dataclasses.asdict(dc) + assert dc_dict["a"] == dc.a + assert dc_dict["a"] is dc.a + + dc = SomeDataclass(TestEnum(5)) + dc_dict = dataclasses.asdict(dc) + assert dc_dict["a"] == dc.a + assert dc_dict["a"] is dc.a + + def test_tenum_docstring() -> None: class TestEnum(cst.EnumBase): """ @@ -472,6 +498,31 @@ def test_tenum_flags() -> None: assert raises(d.build, 2) == TypeError +def test_tenum_flags_asdict(): + import construct_typed as cst + import dataclasses + + class TestEnum(cst.FlagsEnumBase): + one = 1 + two = 2 + four = 4 + eight = 8 + + @dataclasses.dataclass + class SomeDataclass: + a: TestEnum + + dc = SomeDataclass(TestEnum.one) + dc_dict = dataclasses.asdict(dc) + assert dc_dict["a"] == dc.a + assert dc_dict["a"] is dc.a + + dc = SomeDataclass(TestEnum(5)) + dc_dict = dataclasses.asdict(dc) + assert dc_dict["a"] == dc.a + assert dc_dict["a"] is dc.a + + def test_tenum_flags_docstring() -> None: class TestEnum(cst.FlagsEnumBase): """ From 50a2f34fc68d3f81262814ba6d8980e5b056a0ec Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 11:12:42 +0200 Subject: [PATCH 2/6] fixed mypy issue --- construct_typed/tenum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/construct_typed/tenum.py b/construct_typed/tenum.py index 08e4a4b..a71fb7f 100644 --- a/construct_typed/tenum.py +++ b/construct_typed/tenum.py @@ -74,7 +74,7 @@ class EnumBase(enum.IntEnum): return pseudo_member return None # will raise the ValueError in Enum.__new__ - def __reduce_ex__(self, proto: t.Any): + def __reduce_ex__(self, proto: t.Any) -> t.Tuple[t.Any, ...]: """ Pickle enums by value instead of name (restores pre-3.11 behavior). See https://github.com/python/cpython/pull/26658 for why this exists. @@ -178,7 +178,7 @@ class FlagsEnumBase(enum.IntFlag): new_member.__doc__ = "missing value" return new_member - def __reduce_ex__(self, proto: t.Any): + def __reduce_ex__(self, proto: t.Any) -> t.Tuple[t.Any, ...]: """ Pickle enums by value instead of name (restores pre-3.11 behavior). See https://github.com/python/cpython/pull/26658 for why this exists. From 3a676478ee56349b6fa80e5ecef12752a6bd1fd4 Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 11:27:19 +0200 Subject: [PATCH 3/6] fixed mypy issues --- construct_typed/dataclass_struct.py | 4 +++- tests/test_typed.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/construct_typed/dataclass_struct.py b/construct_typed/dataclass_struct.py index 78d2e59..d72ce5c 100644 --- a/construct_typed/dataclass_struct.py +++ b/construct_typed/dataclass_struct.py @@ -27,6 +27,8 @@ class DataclassMixin: methods exists and every name can be used. """ + __dataclass_fields__: t.ClassVar[dict[str, dataclasses.Field[t.Any]]] + def __getitem__(self, key: str) -> t.Any: return getattr(self, key) @@ -269,4 +271,4 @@ TBitStruct = DataclassBitStruct TContainerMixin = DataclassMixin TContainerBase = DataclassMixin TStructField = csfield -sfield = csfield \ No newline at end of file +sfield = csfield diff --git a/tests/test_typed.py b/tests/test_typed.py index 4dd9528..7d726a3 100644 --- a/tests/test_typed.py +++ b/tests/test_typed.py @@ -384,7 +384,7 @@ def test_tenum_no_enumbase() -> None: assert raises(lambda: cst.TEnum(cs.Byte, cls)) == TypeError -def test_tenum_asdict(): +def test_tenum_asdict() -> None: # see: https://github.com/timrid/construct-typing/issues/21 import construct_typed as cst import dataclasses @@ -498,7 +498,7 @@ def test_tenum_flags() -> None: assert raises(d.build, 2) == TypeError -def test_tenum_flags_asdict(): +def test_tenum_flags_asdict() -> None: import construct_typed as cst import dataclasses From d969fab1d3d6e3fa6e136beb70d25101d629f9e3 Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 11:31:18 +0200 Subject: [PATCH 4/6] fixed python 3.7 "TypeError: 'type' object is not subscriptable" --- construct_typed/dataclass_struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/construct_typed/dataclass_struct.py b/construct_typed/dataclass_struct.py index d72ce5c..6e6c2d7 100644 --- a/construct_typed/dataclass_struct.py +++ b/construct_typed/dataclass_struct.py @@ -27,7 +27,7 @@ class DataclassMixin: methods exists and every name can be used. """ - __dataclass_fields__: t.ClassVar[dict[str, dataclasses.Field[t.Any]]] + __dataclass_fields__: "t.ClassVar[dict[str, dataclasses.Field[t.Any]]]" def __getitem__(self, key: str) -> t.Any: return getattr(self, key) From 6185a95e74da8ebe03171f2b5c9a13b7da4800b5 Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 11:44:09 +0200 Subject: [PATCH 5/6] ignored typing error --- construct_typed/dataclass_struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/construct_typed/dataclass_struct.py b/construct_typed/dataclass_struct.py index 6e6c2d7..b7dbe14 100644 --- a/construct_typed/dataclass_struct.py +++ b/construct_typed/dataclass_struct.py @@ -212,7 +212,7 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): value = obj[field.name] setattr(dc, field.name, value) - return dc + return dc # type: ignore def _encode( self, obj: DataclassType, context: Context, path: PathType From a44ea8429984505f79318ac1d147c13ed300b2c1 Mon Sep 17 00:00:00 2001 From: Tim Riddermann Date: Tue, 9 May 2023 11:47:10 +0200 Subject: [PATCH 6/6] fixed mypy error --- construct_typed/dataclass_struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/construct_typed/dataclass_struct.py b/construct_typed/dataclass_struct.py index b7dbe14..6ecae05 100644 --- a/construct_typed/dataclass_struct.py +++ b/construct_typed/dataclass_struct.py @@ -27,7 +27,7 @@ class DataclassMixin: methods exists and every name can be used. """ - __dataclass_fields__: "t.ClassVar[dict[str, dataclasses.Field[t.Any]]]" + __dataclass_fields__: "t.ClassVar[t.Dict[str, dataclasses.Field[t.Any]]]" def __getitem__(self, key: str) -> t.Any: return getattr(self, key)