44from datetime import date , datetime , time , timedelta
55from decimal import Decimal
66from 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+ )
820
921from typing_extensions import get_args
1022
2032 "get_set_values" ,
2133 "get_set_bits" ,
2234 "decompose" ,
35+ "members" ,
2336]
2437
2538
39+ PROPERTIES_ENABLED = find_spec ("enum_properties" )
40+ """
41+ True if enum-properties is installed, False otherwise.
42+ """
43+
2644T = TypeVar ("T" )
45+ E = TypeVar ("E" , bound = Enum )
2746F = TypeVar ("F" , bound = Flag )
2847
2948SupportedPrimitive = Union [
@@ -54,7 +73,7 @@ def with_typehint(baseclass: Type[T]) -> Type[T]:
5473
5574
5675def choices (
57- enum_cls : Optional [Type [Enum ]], override : bool = False
76+ enum_cls : Optional [Type [Enum ]], override : bool = False , aliases : bool = True
5877) -> List [Tuple [Any , str ]]:
5978 """
6079 Get the Django choices for an enumeration type. If the enum type has a
@@ -67,6 +86,7 @@ def choices(
6786
6887 :param enum_cls: The enumeration type
6988 :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)
7090 :return: A list of (value, label) pairs
7191 """
7292 return (
@@ -80,7 +100,7 @@ def choices(
80100 ),
81101 * [
82102 (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 )
84104 ],
85105 ]
86106 )
@@ -89,24 +109,24 @@ def choices(
89109 )
90110
91111
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 ]:
93115 """
94116 Return a list of names to use for the enumeration type. This is used
95117 for compat with enums that do not inherit from Django's Choices type.
96118
97119 :param enum_cls: The enumeration type
98120 :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)
99122 :return: A list of labels
100123 """
101124 return (
102125 (getattr (enum_cls , "names" , []) if not override else [])
103126 or (
104127 [
105128 * (["__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 )],
110130 ]
111131 )
112132 if enum_cls
@@ -189,6 +209,16 @@ def determine_primitive(enum: Type[Enum]) -> Optional[Type]:
189209 return primitive
190210
191211
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+
192222def decimal_params (
193223 enum : Optional [Type [Enum ]],
194224 decimal_places : Optional [int ] = None ,
@@ -264,9 +294,49 @@ class Permissions(IntFlag):
264294 """
265295 if not flags :
266296 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 ()
271328 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