Skip to content

Commit 60787e1

Browse files
committed
fixes #29
1 parent 30e1e8e commit 60787e1

File tree

6 files changed

+243
-11
lines changed

6 files changed

+243
-11
lines changed

doc/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ sphinxcontrib-htmlhelp==2.0.1; python_version >= "3.5"
66
sphinxcontrib-jsmath==1.0.1; python_version >= "3.5"
77
sphinxcontrib-qthelp==1.0.3; python_version >= "3.5"
88
sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.5"
9-
enum-properties==1.3.1
9+
enum-properties==1.3.2

doc/source/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
Change Log
33
==========
44

5+
v1.3.2
6+
======
7+
8+
* Fixed `Nested classes are incompatible with EnumProperties. <https://github.com/bckohan/enum-properties/issues/29>`_
9+
10+
511
v1.3.1
612
======
713

doc/source/usage.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,49 @@ values or symmetric values.
254254
metaclass=EnumPropertiesMeta
255255
):
256256
...
257+
258+
259+
Nested Classes
260+
--------------
261+
262+
.. note::
263+
264+
Nested classes behave normally on enums that inherit from
265+
:py:class:`~enum_properties.EnumProperties` and that specify at least one
266+
property.
267+
268+
On enums that inherit from Enum_ nested classes become enumeration values
269+
because types may be values and a quirk of Python makes it difficult to
270+
determine if a type on a class is declared as a nested class during __new__.
271+
For enums with properties we can distinguish declared classes because values
272+
must be tuples.
273+
274+
Using :py:class:`~enum_properties.EnumProperties` this is possible:
275+
276+
.. code-block:: python
277+
278+
class MyEnum(EnumProperties, p('label')):
279+
280+
class Type1:
281+
pass
282+
283+
class Type2:
284+
pass
285+
286+
class Type3:
287+
pass
288+
289+
VALUE1 = Type1, 'label1'
290+
VALUE2 = Type2, 'label2'
291+
VALUE3 = Type3, 'label3'
292+
293+
# only the expected values become enumeration values
294+
assert MyEnum.Type1 == MyEnum.VALUE1.value
295+
assert MyEnum.Type2 == MyEnum.VALUE2.value
296+
assert MyEnum.Type3 == MyEnum.VALUE3.value
297+
assert len(MyEnum) == 3
298+
299+
# nested classes behave as expected
300+
assert MyEnum.Type1().__class__ is MyEnum.Type1
301+
assert MyEnum.Type2().__class__ is MyEnum.Type2
302+
assert MyEnum.Type3().__class__ is MyEnum.Type3

enum_properties/__init__.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
Hashable,
2424
Iterable,
2525
)
26-
from datetime import datetime
2726

2827
try:
2928
from functools import cached_property
@@ -38,7 +37,7 @@
3837
__version__ = '.'.join(str(i) for i in VERSION)
3938
__author__ = 'Brian Kohan'
4039
__license__ = 'MIT'
41-
__copyright__ = f'Copyright 2022-{datetime.now().year} Brian Kohan'
40+
__copyright__ = 'Copyright 2022-2023 Brian Kohan'
4241

4342
__all__ = [
4443
'VERSION',
@@ -316,7 +315,13 @@ def __setitem__(self, key, value):
316315
# ensures robust fidelity to Enum behavior.
317316
before = len(class_dict._member_names)
318317
class_dict[key] = value
319-
if len(class_dict._member_names) > before:
318+
remove = False
319+
if (
320+
len(class_dict._member_names) > before and
321+
# base class lets nested classes through! see:
322+
# https://github.com/bckohan/enum-properties/issues/29
323+
not isinstance(value, type)
324+
):
320325
try:
321326
num_vals = len(value) - len(self._ep_properties_)
322327
if (
@@ -342,7 +347,16 @@ def __setitem__(self, key, value):
342347
f'{key} must have {len(self._ep_properties_)} '
343348
f'property values.'
344349
) from type_err
350+
351+
elif key in class_dict._member_names:
352+
remove = True
353+
345354
super().__setitem__(key, value)
355+
356+
if remove:
357+
# base class lets nested classes through! see:
358+
# https://github.com/bckohan/enum-properties/issues/29
359+
self._member_names.remove(key)
346360
else:
347361
super().__setitem__(key, value)
348362

enum_properties/tests/pickle_enums.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
from enum import auto
2+
13
from enum_properties import (
24
EnumProperties,
3-
IntFlagProperties,
45
FlagProperties,
6+
IntFlagProperties,
7+
p,
58
s,
6-
p
79
)
8-
from enum import auto
910

1011

1112
class IntPerm(

enum_properties/tests/tests.py

Lines changed: 169 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import pickle
12
from collections.abc import Hashable
23
from enum import Enum, auto
4+
from io import BytesIO
35
from unittest import TestCase
46

57
from enum_properties import (
@@ -11,8 +13,6 @@
1113
p,
1214
s,
1315
)
14-
from io import BytesIO
15-
import pickle
1616

1717

1818
class Unhashable:
@@ -1078,7 +1078,7 @@ def do_pickle_test(self, ipt):
10781078
return ipt is opt
10791079

10801080
def test_pickle(self):
1081-
from enum_properties.tests.pickle_enums import PriorityEx, Color
1081+
from enum_properties.tests.pickle_enums import Color, PriorityEx
10821082
self.assertTrue(self.do_pickle_test(PriorityEx.ONE))
10831083
self.assertTrue(self.do_pickle_test(PriorityEx.TWO))
10841084
self.assertTrue(self.do_pickle_test(PriorityEx.THREE))
@@ -1092,7 +1092,7 @@ def test_pickle(self):
10921092
self.assertTrue(self.do_pickle_test(Color.BLUE))
10931093

10941094
def test_flag_pickle(self):
1095-
from enum_properties.tests.pickle_enums import Perm, IntPerm
1095+
from enum_properties.tests.pickle_enums import IntPerm, Perm
10961096
self.assertTrue(self.do_pickle_test(Perm.R))
10971097
self.assertTrue(self.do_pickle_test(Perm.W))
10981098
self.assertTrue(self.do_pickle_test(Perm.X))
@@ -1113,6 +1113,49 @@ class TestNestedClassOnEnum(TestCase):
11131113
Should be able to nest classes on Enumerations!
11141114
"""
11151115

1116+
def test_enum_can_be_types(self):
1117+
1118+
class Type1:
1119+
pass
1120+
1121+
class Type2:
1122+
pass
1123+
1124+
class Type3:
1125+
pass
1126+
1127+
class TestEnum(EnumProperties, s('label')):
1128+
1129+
VALUE1 = Type1, 'value1'
1130+
VALUE2 = Type2, 'value2'
1131+
VALUE3 = Type3, 'value3'
1132+
1133+
self.assertEqual(
1134+
[en for en in TestEnum],
1135+
[TestEnum.VALUE1, TestEnum.VALUE2, TestEnum.VALUE3]
1136+
)
1137+
1138+
self.assertEqual(
1139+
[en.label for en in TestEnum],
1140+
[
1141+
TestEnum.VALUE1.label,
1142+
TestEnum.VALUE2.label,
1143+
TestEnum.VALUE3.label
1144+
]
1145+
)
1146+
self.assertEqual(
1147+
[en for en in TestEnum],
1148+
[TestEnum('value1'), TestEnum('value2'), TestEnum('value3')]
1149+
)
1150+
self.assertEqual(
1151+
[en for en in TestEnum],
1152+
[TestEnum(Type1), TestEnum(Type2), TestEnum(Type3)]
1153+
)
1154+
self.assertEqual(
1155+
[en.value for en in TestEnum],
1156+
[Type1, Type2, Type3]
1157+
)
1158+
11161159
def test_nested_classes(self):
11171160

11181161
class TestEnum(EnumProperties, s('label')):
@@ -1121,12 +1164,28 @@ class TestEnum(EnumProperties, s('label')):
11211164
VALUE2 = auto(), 'value2'
11221165
VALUE3 = auto(), 'value3'
11231166

1167+
def function(self):
1168+
return self.value
1169+
1170+
@classmethod
1171+
def default(cls):
1172+
return cls.VALUE1
1173+
1174+
@staticmethod
1175+
def static_property():
1176+
return 'static_prop'
1177+
11241178
class NestedClass:
11251179

11261180
@property
11271181
def prop(self):
11281182
return 'nested'
11291183

1184+
self.assertEqual(TestEnum.VALUE1.function(), TestEnum.VALUE1.value)
1185+
self.assertEqual(TestEnum.VALUE2.function(), TestEnum.VALUE2.value)
1186+
self.assertEqual(TestEnum.VALUE3.function(), TestEnum.VALUE3.value)
1187+
self.assertEqual(TestEnum.default(), TestEnum.VALUE1)
1188+
self.assertEqual(TestEnum.static_property(), 'static_prop')
11301189
self.assertEqual(TestEnum.NestedClass().prop, 'nested')
11311190
self.assertEqual(
11321191
[en for en in TestEnum],
@@ -1144,3 +1203,109 @@ def prop(self):
11441203
[en for en in TestEnum],
11451204
[TestEnum('value1'), TestEnum('value2'), TestEnum('value3')]
11461205
)
1206+
1207+
def test_nested_classes_as_values(self):
1208+
1209+
class TestEnum(EnumProperties, s('label')):
1210+
1211+
class Type1:
1212+
pass
1213+
1214+
class Type2:
1215+
pass
1216+
1217+
class Type3:
1218+
pass
1219+
1220+
VALUE1 = Type1, 'value1'
1221+
VALUE2 = Type2, 'value2'
1222+
VALUE3 = Type3, 'value3'
1223+
1224+
self.assertEqual(
1225+
[en for en in TestEnum],
1226+
[TestEnum.VALUE1, TestEnum.VALUE2, TestEnum.VALUE3]
1227+
)
1228+
1229+
self.assertEqual(
1230+
[en.label for en in TestEnum],
1231+
[
1232+
TestEnum.VALUE1.label,
1233+
TestEnum.VALUE2.label,
1234+
TestEnum.VALUE3.label
1235+
]
1236+
)
1237+
self.assertEqual(
1238+
[en for en in TestEnum],
1239+
[TestEnum('value1'), TestEnum('value2'), TestEnum('value3')]
1240+
)
1241+
self.assertEqual(
1242+
[en for en in TestEnum],
1243+
[
1244+
TestEnum(TestEnum.Type1),
1245+
TestEnum(TestEnum.Type2),
1246+
TestEnum(TestEnum.Type3)
1247+
]
1248+
)
1249+
self.assertEqual(
1250+
[en.value for en in TestEnum],
1251+
[TestEnum.Type1, TestEnum.Type2, TestEnum.Type3]
1252+
)
1253+
1254+
def test_nested_classes_as_values_no_props(self):
1255+
1256+
class TestEnum(EnumProperties):
1257+
1258+
class Type1:
1259+
pass
1260+
1261+
class Type2:
1262+
pass
1263+
1264+
class Type3:
1265+
pass
1266+
1267+
VALUE1 = Type1
1268+
VALUE2 = Type2
1269+
VALUE3 = Type3
1270+
1271+
self.assertEqual(
1272+
[en for en in TestEnum],
1273+
[TestEnum.VALUE1, TestEnum.VALUE2, TestEnum.VALUE3]
1274+
)
1275+
self.assertEqual(
1276+
[en for en in TestEnum],
1277+
[
1278+
TestEnum(TestEnum.Type1),
1279+
TestEnum(TestEnum.Type2),
1280+
TestEnum(TestEnum.Type3)
1281+
]
1282+
)
1283+
self.assertEqual(
1284+
[en.value for en in TestEnum],
1285+
[TestEnum.Type1, TestEnum.Type2, TestEnum.Type3]
1286+
)
1287+
1288+
def test_example(self):
1289+
1290+
class MyEnum(EnumProperties, p('label')):
1291+
class Type1:
1292+
pass
1293+
1294+
class Type2:
1295+
pass
1296+
1297+
class Type3:
1298+
pass
1299+
1300+
VALUE1 = Type1, 'label1'
1301+
VALUE2 = Type2, 'label2'
1302+
VALUE3 = Type3, 'label3'
1303+
1304+
# nested classes are usable like normal
1305+
self.assertEqual(MyEnum.Type1, MyEnum.VALUE1.value)
1306+
self.assertEqual(MyEnum.Type2, MyEnum.VALUE2.value)
1307+
self.assertEqual(MyEnum.Type3, MyEnum.VALUE3.value)
1308+
self.assertEqual(len(MyEnum), 3)
1309+
self.assertTrue(MyEnum.Type1().__class__ is MyEnum.Type1)
1310+
self.assertTrue(MyEnum.Type2().__class__ is MyEnum.Type2)
1311+
self.assertTrue(MyEnum.Type3().__class__ is MyEnum.Type3)

0 commit comments

Comments
 (0)