From 6abc0badca89d85216eb4debbb5fbf0add5f3583 Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Wed, 15 Feb 2023 19:11:38 -0800 Subject: [PATCH] fix python 3.11+ usage and add member/nonmember decorator tests for #29 --- doc/source/usage.rst | 17 +++++----- enum_properties/__init__.py | 11 ++++++- enum_properties/tests/tests.py | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 606b061..29656de 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -261,15 +261,16 @@ Nested Classes .. note:: - Nested classes behave normally on enums that inherit from + In python <3.13, nested classes behave normally on enums that inherit from :py:class:`~enum_properties.EnumProperties` and that specify at least one - property. - -On enums that inherit from Enum_ nested classes become enumeration values -because types may be values and a quirk of Python makes it difficult to -determine if a type on a class is declared as a nested class during __new__. -For enums with properties we can distinguish declared classes because values -must be tuples. + property. In python 3.13 this behavior will remain unchanged in + enum-properties and normal Enum_ classes will adopt it. + +On enums that inherit from Enum_ in python < 3.13 nested classes become +enumeration values because types may be values and a quirk of Python makes it +difficult to determine if a type on a class is declared as a nested class +during __new__. For enums with properties we can distinguish declared classes +because values must be tuples. Using :py:class:`~enum_properties.EnumProperties` this is possible: diff --git a/enum_properties/__init__.py b/enum_properties/__init__.py index bf888f2..4cdc6cb 100644 --- a/enum_properties/__init__.py +++ b/enum_properties/__init__.py @@ -320,6 +320,7 @@ def __setitem__(self, key, value): len(class_dict._member_names) > before and # base class lets nested classes through! see: # https://github.com/bckohan/enum-properties/issues/29 + # todo remove below when minimum python >= 3.13 not isinstance(value, type) ): try: @@ -354,9 +355,17 @@ def __setitem__(self, key, value): super().__setitem__(key, value) if remove: + # todo remove when minimum python >= 3.13 # base class lets nested classes through! see: # https://github.com/bckohan/enum-properties/issues/29 - self._member_names.remove(key) + if isinstance( + self._member_names, + list + ): # pragma: no cover + self._member_names.remove(key) + else: # pragma: no cover + # >= python 3.11 + del self._member_names[key] else: super().__setitem__(key, value) diff --git a/enum_properties/tests/tests.py b/enum_properties/tests/tests.py index ee18c8b..30c0295 100644 --- a/enum_properties/tests/tests.py +++ b/enum_properties/tests/tests.py @@ -1,4 +1,5 @@ import pickle +import sys from collections.abc import Hashable from enum import Enum, auto from io import BytesIO @@ -1288,6 +1289,7 @@ class Type3: def test_example(self): class MyEnum(EnumProperties, p('label')): + class Type1: pass @@ -1309,3 +1311,58 @@ class Type3: self.assertTrue(MyEnum.Type1().__class__ is MyEnum.Type1) self.assertTrue(MyEnum.Type2().__class__ is MyEnum.Type2) self.assertTrue(MyEnum.Type3().__class__ is MyEnum.Type3) + + if sys.version_info >= (3, 11): # pragma: no cover + def test_nonmember_decorator(self): + from enum import nonmember + + class MyEnum(EnumProperties, p('label')): + + @nonmember + class Type1: + pass + + @nonmember + class Type2: + pass + + @nonmember + class Type3: + pass + + VALUE1 = Type1, 'label1' + VALUE2 = Type2, 'label2' + VALUE3 = Type3, 'label3' + VALUE4 = nonmember((Type3, 'label4')) + + # nested classes are usable like normal + self.assertEqual(MyEnum.Type1, MyEnum.VALUE1.value) + self.assertEqual(MyEnum.Type2, MyEnum.VALUE2.value) + self.assertEqual(MyEnum.Type3, MyEnum.VALUE3.value) + self.assertEqual(len(MyEnum), 3) + self.assertEqual(MyEnum.VALUE4, (MyEnum.Type3, 'label4')) + self.assertTrue(MyEnum.Type1().__class__ is MyEnum.Type1) + self.assertTrue(MyEnum.Type2().__class__ is MyEnum.Type2) + self.assertTrue(MyEnum.Type3().__class__ is MyEnum.Type3) + + def test_member_decorator(self): + from enum import member + + with self.assertRaises(ValueError): + class MyEnum(EnumProperties, p('label')): + + @member + class Type1: + pass + + @member + class Type2: + pass + + @member + class Type3: + pass + + VALUE1 = Type1, 'label1' + VALUE2 = Type2, 'label2' + VALUE3 = Type3, 'label3'