4
4
from datetime import date , datetime , time , timedelta
5
5
from decimal import Decimal
6
6
from enum import Enum , Flag , IntFlag
7
- from typing import TYPE_CHECKING , Any , Dict , List , Optional , Tuple , Type , TypeVar , Union
7
+ from importlib .util import find_spec
8
+ from typing import (
9
+ TYPE_CHECKING ,
10
+ Any ,
11
+ Dict ,
12
+ Generator ,
13
+ List ,
14
+ Optional ,
15
+ Tuple ,
16
+ Type ,
17
+ TypeVar ,
18
+ Union ,
19
+ )
8
20
9
21
from typing_extensions import get_args
10
22
20
32
"get_set_values" ,
21
33
"get_set_bits" ,
22
34
"decompose" ,
35
+ "members" ,
23
36
]
24
37
25
38
39
+ PROPERTIES_ENABLED = find_spec ("enum_properties" )
40
+ """
41
+ True if enum-properties is installed, False otherwise.
42
+ """
43
+
26
44
T = TypeVar ("T" )
45
+ E = TypeVar ("E" , bound = Enum )
27
46
F = TypeVar ("F" , bound = Flag )
28
47
29
48
SupportedPrimitive = Union [
@@ -54,7 +73,7 @@ def with_typehint(baseclass: Type[T]) -> Type[T]:
54
73
55
74
56
75
def choices (
57
- enum_cls : Optional [Type [Enum ]], override : bool = False
76
+ enum_cls : Optional [Type [Enum ]], override : bool = False , aliases : bool = True
58
77
) -> List [Tuple [Any , str ]]:
59
78
"""
60
79
Get the Django choices for an enumeration type. If the enum type has a
@@ -67,6 +86,7 @@ def choices(
67
86
68
87
:param enum_cls: The enumeration type
69
88
:param override: Do not defer to choices attribute on the class if True
89
+ :param aliases: Include first-class aliases in the result if True (default: True)
70
90
:return: A list of (value, label) pairs
71
91
"""
72
92
return (
@@ -80,7 +100,7 @@ def choices(
80
100
),
81
101
* [
82
102
(member .value , getattr (member , "label" , getattr (member , "name" )))
83
- for member in list (enum_cls ) or enum_cls . __members__ . values ( )
103
+ for member in members (enum_cls , aliases = aliases )
84
104
],
85
105
]
86
106
)
@@ -89,24 +109,24 @@ def choices(
89
109
)
90
110
91
111
92
- def names (enum_cls : Optional [Type [Enum ]], override : bool = False ) -> List [Any ]:
112
+ def names (
113
+ enum_cls : Optional [Type [Enum ]], override : bool = False , aliases : bool = True
114
+ ) -> List [Any ]:
93
115
"""
94
116
Return a list of names to use for the enumeration type. This is used
95
117
for compat with enums that do not inherit from Django's Choices type.
96
118
97
119
:param enum_cls: The enumeration type
98
120
:param override: Do not defer to names attribute on the class if True
121
+ :param aliases: Include first-class aliases in the result if True (default: True)
99
122
:return: A list of labels
100
123
"""
101
124
return (
102
125
(getattr (enum_cls , "names" , []) if not override else [])
103
126
or (
104
127
[
105
128
* (["__empty__" ] if hasattr (enum_cls , "__empty__" ) else []),
106
- * [
107
- member .name
108
- for member in list (enum_cls ) or enum_cls .__members__ .values ()
109
- ],
129
+ * [member .name for member in members (enum_cls , aliases = aliases )],
110
130
]
111
131
)
112
132
if enum_cls
@@ -189,6 +209,16 @@ def determine_primitive(enum: Type[Enum]) -> Optional[Type]:
189
209
return primitive
190
210
191
211
212
+ def is_power_of_two (n : int ) -> bool :
213
+ """
214
+ Check if an integer is a power of two.
215
+
216
+ :param n: The integer to check
217
+ :return: True if the number is a power of two, False otherwise
218
+ """
219
+ return n != 0 and (n & (n - 1 )) == 0
220
+
221
+
192
222
def decimal_params (
193
223
enum : Optional [Type [Enum ]],
194
224
decimal_places : Optional [int ] = None ,
@@ -264,9 +294,49 @@ class Permissions(IntFlag):
264
294
"""
265
295
if not flags :
266
296
return []
267
- if sys .version_info < (3 , 11 ):
268
- return [
269
- flg for flg in type (flags ) if flg in flags and flg is not type (flags )(0 )
270
- ]
297
+ return [
298
+ flg
299
+ for flg in type (flags ).__members__ .values ()
300
+ if flg in flags and flg is not type (flags )(0 )
301
+ ]
302
+
303
+
304
+ def members (enum : Type [E ], aliases : bool = True ) -> Generator [E , None , None ]:
305
+ """
306
+ Get the members of an enumeration class. This can be tricky to do
307
+ in a python version agnostic way, so it is recommended to
308
+ use this function.
309
+
310
+ .. note:
311
+
312
+ Composite flag values, such as `A | B` when named on a
313
+ :class:`~enum.IntFlag` class are considered aliases by this function.
314
+
315
+ :param enum_cls: The enumeration class
316
+ :param aliases: Include aliases in the result if True (default: True)
317
+ :return: A generator that yields the enumeration members
318
+ """
319
+ if aliases :
320
+ if PROPERTIES_ENABLED :
321
+ from enum_properties import SymmetricMixin
322
+
323
+ if issubclass (enum , SymmetricMixin ):
324
+ for member in enum .__first_class_members__ :
325
+ yield enum [member ] # type: ignore[index]
326
+ return
327
+ yield from enum .__members__ .values ()
271
328
else :
272
- return list (flags ) # type: ignore[arg-type]
329
+ if issubclass (enum , Flag ) and (
330
+ issubclass (enum , int )
331
+ or isinstance (next (iter (enum .__members__ .values ())).value , int )
332
+ ):
333
+ for name in enum ._member_names_ :
334
+ en = enum [name ]
335
+ value = en .value
336
+ if value < 0 or is_power_of_two (value ):
337
+ yield en # type: ignore[misc]
338
+ elif sys .version_info [:2 ] >= (3 , 11 ):
339
+ yield from enum # type: ignore[misc]
340
+ else :
341
+ for name in enum ._member_names_ :
342
+ yield enum [name ]
0 commit comments