11# enum.py
2- # version="1.2.4 "
2+ # version="1.2.5 "
33
44
55class EnumValue :
66 # An immutable object representing a specific enum member
7- def __init__ (self , value , name ):
8- object .__setattr__ (self , "value" , value )
9- object .__setattr__ (self , "name" , name )
7+ def __init__ (self , v , n ):
8+ object .__setattr__ (self , "value" , v )
9+ object .__setattr__ (self , "name" , n )
1010
1111 def __repr__ (self ):
1212 return f"{ self .name } : { self .value } "
1313
1414 def __call__ (self ):
1515 return self .value
1616
17- def __eq__ (self , other ):
18- if isinstance (other , EnumValue ):
19- return self .value == other .value
20- return self .value == other
17+ def __eq__ (self , o ):
18+ return self .value == (o .value if isinstance (o , EnumValue ) else o )
2119
22- def __setattr__ (self , key , value ):
20+ def __setattr__ (self , k , v ):
2321 raise AttributeError ("EnumValue is immutable" )
2422
2523
2624class Enum :
2725 def __new__ (cls , name = None , names = None ):
28- # Scenario 1: Reverse lookup by value (e.g., Status(1))
29- if name is not None and names is None :
30- if cls is not Enum :
31- return cls ._lookup (name )
32-
33- # Scenario 2: Functional API (e.g., Enum("Color", {"RED": 1}))
34- return super (Enum , cls ).__new__ (cls )
26+ # If a name and names are provided, create a NEW subclass of Enum
27+ if name and names :
28+ # Support Functional API: Enum("Name", {"KEY": VALUE})
29+ # Dynamically create: class <name>
30+ new_cls = type (name , (cls , ), {})
31+ for k , v in names .items ():
32+ new_cls ._up (k , v )
33+ new_cls ._inited = True
34+ return super ().__new__ (new_cls )
35+
36+ # Reverse lookup by value or name (e.g., Color(1) or Color("RED"))
37+ if name and not names and cls is not Enum :
38+ return cls ._lookup (name )
39+
40+ return super ().__new__ (cls )
3541
3642 def __init__ (self , name = None , names = None ):
37- if hasattr (self , "_initialized" ):
38- return
39-
40- # 1. Convert class-level attributes (constants) to EnumValue objects
41- self ._scan_class_attrs ()
42-
43- # Support Functional API: Enum("Name", {"KEY": VALUE})
44- if name is not None and isinstance (names , dict ):
45- for key , value in names .items ():
46- # Prevent addition if the key already exists
47- if not hasattr (self , key ):
48- self ._update (key , value )
49-
50- object .__setattr__ (self , "_initialized" , True )
43+ if "_inited" not in self .__class__ .__dict__ :
44+ self ._scan ()
5145
5246 @classmethod
53- def _lookup (cls , value ):
54- # Finds an EnumValue by its raw value
55- for key in dir (cls ):
56- if key .startswith ("_" ):
57- continue
58- attr = getattr (cls , key )
59- if isinstance (attr , EnumValue ) and (attr .value == value or attr .name == value ):
60- return attr
61- if not callable (attr ) and attr == value :
62- # Wrap static numbers found in class definition
63- return EnumValue (attr , key )
64- raise AttributeError (f"{ value } is not in { cls .__name__ } " )
47+ def _lookup (cls , v ):
48+ if "_inited" not in cls .__dict__ :
49+ cls ._scan ()
50+
51+ # Finds an EnumValue by its raw value or name
52+ for k in dir (cls ):
53+ a = getattr (cls , k )
54+ if isinstance (a , EnumValue ) and (a .value == v or a .name == v ):
55+ return a
56+ raise AttributeError (f"{ v } is not in { cls .__name__ } " )
6557
6658 @classmethod
6759 def __iter__ (cls ):
68- if "_initialized" not in cls .__dict__ :
69- cls ._scan_class_attrs ()
70- setattr (cls , "_initialized" , True )
71-
72- for key in dir (cls ):
73- if key .startswith ("_" ):
74- continue
75- attr = getattr (cls , key )
60+ if "_inited" not in cls .__dict__ :
61+ cls ._scan ()
62+
63+ for k in dir (cls ):
64+ attr = getattr (cls , k )
7665 if isinstance (attr , EnumValue ):
7766 yield attr
7867
7968 @classmethod
8069 def list (cls ):
81- if "_initialized" not in cls .__dict__ :
82- cls ._scan_class_attrs ()
83- setattr (cls , "_initialized" , True )
70+ if "_inited" not in cls .__dict__ :
71+ cls ._scan ()
8472
8573 # Returns a list of all members
86- return [getattr (cls , key ) for key in dir (cls ) if isinstance (getattr (cls , key ), EnumValue )]
74+ return [getattr (cls , k ) for k in dir (cls ) if isinstance (getattr (cls , k ), EnumValue )]
8775
8876 @classmethod
89- def _update (cls , key , value ):
90- setattr (cls , key , EnumValue (value , key ))
77+ def _up (cls , k , v ):
78+ setattr (cls , k , EnumValue (v , k ))
9179
9280 @classmethod
93- def _scan_class_attrs (cls ):
94- # Converts static class attributes into EnumValue objects
95- # List of methods and internal names that should not be converted
96- ignored = ("is_value" , "list" )
97- for key in dir (cls ):
98- # Skip internal names and methods
99- if key .startswith ("_" ) or key in ignored :
100- continue
101-
102- value = getattr (cls , key )
103- # Convert only constants, not methods
104- if not callable (value ) and not isinstance (value , EnumValue ):
105- cls ._update (key , value )
106-
107- def is_value (self , value ):
108- return any (member .value == value for member in self )
81+ def _scan (cls ):
82+ # Convert class-level attributes (constants) to EnumValue objects
83+ for k , v in list (cls .__dict__ .items ()):
84+ if not k .startswith ("_" ) and not callable (v ) and not isinstance (v , EnumValue ):
85+ cls ._up (k , v )
86+ cls ._inited = True
87+
88+ def is_value (self , v ):
89+ return any (m .value == v for m in self )
10990
11091 def __repr__ (self ):
11192 # Supports the condition: obj == eval(repr(obj))
112- members = {member .name : member .value for member in self }
113- if self .__class__ .__name__ == "Enum" :
114- return f"Enum(name='Enum', names={ members } )"
115- # Return a string like: Name(names={"KEY1": VALUE1, "KEY2": VALUE2, ..})
116- return f"{ self .__class__ .__name__ } (names={ members } )"
117-
118- def __call__ (self , value ):
119- if not hasattr (self , "_initialized" ):
120- self ._scan_class_attrs ()
121- object .__setattr__ (self , "_initialized" , True )
122-
123- for member in self :
124- if member .value == value or member .name == value :
125- return member
126- raise AttributeError (f"{ value } is not in { self .__class__ .__name__ } " )
127-
128- def __setattr__ (self , key , value ):
129- if hasattr (self , "_initialized" ):
130- raise AttributeError (f"Enum '{ self .__class__ .__name__ } ' is static" )
131- super ().__setattr__ (key , value )
132-
133- def __delattr__ (self , key ):
134- if hasattr (self , key ) and isinstance (getattr (self , key ), EnumValue ):
135- raise AttributeError ("Enum members cannot be deleted" )
136- super ().__delattr__ (key )
93+ d = {m .name : m .value for m in self }
94+ # Return a string like: Enum(name='Name', names={'KEY1': VALUE1, 'KEY2': VALUE2, ..})
95+ return f"Enum(name='{ self .__class__ .__name__ } ', names={ d } )"
96+
97+ def __call__ (self , v ):
98+ if "_inited" in self .__class__ .__dict__ :
99+ self ._scan ()
100+
101+ return self ._lookup (v )
102+
103+ def __setattr__ (self , k , v ):
104+ if "_inited" in self .__class__ .__dict__ :
105+ raise AttributeError (f"Enum '{ self .__class__ .__name__ } ' is immutable" )
106+ super ().__setattr__ (k , v )
107+
108+ def __delattr__ (self , k ):
109+ raise AttributeError ("Enum is immutable" )
137110
138111 def __len__ (self ):
139112 return sum (1 for _ in self )
140113
141- def __eq__ (self , other ):
142- if not isinstance (other , Enum ):
114+ def __eq__ (self , o ):
115+ if not isinstance (o , Enum ):
143116 return False
144- return self .list () == other .list ()
117+ return self .list () == o .list ()
145118
146119
147120if __name__ == "__main__" :
@@ -151,15 +124,14 @@ class Color(Enum):
151124 RED = 1
152125 GREEN = 2
153126 BLUE = 3
154-
127+
155128 print ("Color.list():" , Color .list ())
156- print ("Color().list():" , Color ().list ())
157-
129+
158130 # Iteration
159131 print ("Members list:" , [member for member in Color ()])
160132 print ("Names list:" , [member .name for member in Color ()])
161133 print ("Values list:" , [member .value for member in Color ()])
162-
134+
163135 # Create instance
164136 c = Color ()
165137 print (f"Enum c: { c } " )
@@ -174,15 +146,11 @@ class Color(Enum):
174146 assert c .RED () == 1
175147
176148 # Reverse Lookup via instance call
177- print (f"c(1) lookup object: { c (1 )} , Name ={ c (1 ).name } , value={ c (1 ).value } " ) # RED
149+ print (f"c(1) lookup object: { c (1 )} , name ={ c (1 ).name } , value={ c (1 ).value } " ) # RED
178150 assert c (1 ).name == "RED"
179151 assert c (1 ).value == 1
180152 assert c (1 ) == 1
181153
182- # Iteration
183- print ("Values list:" , [member .value for member in c ])
184- print ("Names list:" , [member .name for member in c ])
185-
186154 try :
187155 c (999 )
188156 except AttributeError as e :
@@ -200,14 +168,15 @@ class Status(Enum):
200168 received_byte = 1
201169 status = Status (received_byte )
202170 print (f"Lookup check: Received { received_byte } -> { status } " )
171+ assert status == received_byte
203172 assert status == Status .RUNNING
204173 assert status .name == "RUNNING"
174+ assert status .value == received_byte
205175
206176 # Test: Comparisons
207177 print (f"Comparison check: { status } == 1 is { status == 1 } " )
208178 assert status == 1
209179 assert status != 0
210- assert status == Status .RUNNING
211180
212181 # Immutability Check
213182 try :
@@ -228,19 +197,75 @@ class Status(Enum):
228197 print (f"\n AttributeError: Invalid lookup check: Caught expected error -> { e } \n " )
229198
230199 # --- Example 3: Functional API and serialization ---
231- print ("\n --- Functional API and Eval Check ---" )
200+ print ("--- Functional API and Eval Check ---" )
232201
233202 # Verify that eval(repr(obj)) restores the object
234- c2 = eval (repr (c ))
235- print (f"Original: { repr (c )} " )
236- print (f"Restored: { repr (c2 )} " )
237- print (f"Objects are equal: { c == c2 } " )
238- assert c == c2
203+ c_repr = repr (c )
204+ c_restored = eval (c_repr )
205+ print (f"Original: { c_repr } " )
206+ print (f"Restored: { repr (c_restored )} " )
207+ print (f"Objects are equal: { c == c_restored } " )
208+ assert c == c_restored
239209
240210 # Direct creation using the Enum base class
241211 state = eval ("Enum(name='State', names={'ON':1, 'OFF':2})" )
242212 print (f"Functional Enum instance (state): { state } " )
213+ print (type (state ))
243214 assert state .ON == 1
244215 assert state .ON .name == "ON"
245216
217+ # --- 1. Unique Data Types & Class Methods ---
218+ # Enums can hold more than just integers; here we use strings and add a method.
219+ class HttpMethod (Enum ):
220+ GET = "GET"
221+ POST = "POST"
222+ DELETE = "DELETE"
223+
224+ def is_safe (self ):
225+ # Demonstrates that custom logic can coexist with Enum members
226+ return self .list ()[0 ] == self .GET # Simplistic example check
227+
228+ api_call = HttpMethod ()
229+ print (f"Member with string value: { api_call .GET } " )
230+ assert api_call .GET == "GET"
231+
232+ # --- 2. Advanced Reverse Lookup Scenarios ---
233+ # Demonstrates lookup by both name string and raw value string.
234+ print (f"Lookup by value 'POST': { api_call ('POST' )} " )
235+ print (f"Lookup by name 'DELETE': { api_call ('DELETE' )} " )
236+ assert api_call ("GET" ).name == "GET"
237+
238+ # --- 3. Empty Enum Handling ---
239+ # Verifies behavior when no members are defined.
240+ class Empty (Enum ):
241+ pass
242+
243+ empty_inst = Empty ()
244+ print (f"Empty Enum list: { empty_inst .list ()} " )
245+ assert len (empty_inst ) == 0
246+
247+ # --- 4. Deep Functional API & Serialization ---
248+ # Testing complex name strings and verifying the 'eval' round-trip for functional enums.
249+ complex_enum = Enum (name = 'Config' , names = {'MAX_RETRY' : 5 , 'TIMEOUT_SEC' : 30 })
250+
251+ # Verify serialization maintains the dynamic class name
252+ repr_str = repr (complex_enum )
253+ restored = eval (repr_str )
254+
255+ print (f"Restored Functional Enum: { restored } " )
256+ assert restored .MAX_RETRY == 5
257+ assert type (restored ).__name__ == 'Config'
258+
259+ # --- 5. Immutability & Integrity Guard ---
260+ # Ensuring the Enum structure cannot be tampered with after creation.
261+ try :
262+ api_call .NEW_METHOD = "PATCH"
263+ except AttributeError as e :
264+ print (f"Caught expected mutation error: { e } " )
265+
266+ try :
267+ del api_call .GET
268+ except AttributeError as e :
269+ print (f"Caught expected deletion error: { e } " )
270+
246271 print ("\n All tests passed successfully!" )
0 commit comments