From 9458691c5978baf9450cf4bf2e778f0d0d87a40a Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Wed, 5 Mar 2025 12:24:05 +0200 Subject: [PATCH 1/5] python-stdlib\enum\enum.py: Add Enum class. Signed-off-by: Ihor Nehrutsa --- python-stdlib/enum/README.md | 201 ++++++++++++++++++++++++ python-stdlib/enum/enum.py | 269 ++++++++++++++++++++++++++++++++ python-stdlib/enum/manifest.py | 3 + python-stdlib/enum/test_enum.py | 141 +++++++++++++++++ tools/ci.sh | 1 + 5 files changed, 615 insertions(+) create mode 100644 python-stdlib/enum/README.md create mode 100644 python-stdlib/enum/enum.py create mode 100644 python-stdlib/enum/manifest.py create mode 100644 python-stdlib/enum/test_enum.py mode change 100755 => 100644 tools/ci.sh diff --git a/python-stdlib/enum/README.md b/python-stdlib/enum/README.md new file mode 100644 index 000000000..6d1184bc4 --- /dev/null +++ b/python-stdlib/enum/README.md @@ -0,0 +1,201 @@ +# Enum Library + +This library provides a lightweight, memory-efficient `Enum` implementation designed for MicroPython environments. It focuses on immutability, reverse lookup capabilities, and serialization support without the complexity of metaclasses. + +--- + +## Core Features +* **Immutability**: Enum members (`EnumValue`) are protected against modification. Any attempt to change their name or value raises an `AttributeError`. +* **Static Design**: Once an Enum instance is initialized, it is "frozen." You cannot add new attributes or delete existing members. +* **Dual Reverse Lookup**: + * **Class Constructor**: Retrieve a member by value using the class name (e.g., `Status(1)`). + * **Instance Call**: Retrieve a member by value by calling the instance (e.g., `s(1)`). +* **Serialization Support**: Implements `__repr__` such that `obj == eval(repr(obj))`, allowing easy restoration of Enum states. +* **Functional API**: Supports dynamic creation of Enums at runtime. + +--- + +## Usage Examples + +### 1. Standard Class Definition +Define your enumeration by inheriting from the `Enum` class. Class-level constants are automatically converted into `EnumValue` objects upon initialization. + +```python +from enum import Enum + +class Color(Enum): + RED = 'red' + GREEN = 'green' + +# Initialize the enum to process attributes +c = Color() + +print(c.RED) # Output: RED: red +print(c.RED.name) # Output: RED +print(c.RED.value) # Output: red +print(c.RED()) # Output: red +print(c.is_value("RED")) # Output: true +print(c.is_value(Color.RED)) # Output: true +print(c.is_value('red')) # Output: true +print(c.list()) # Output: [Color.RED: red, Color.GREEN: green] +print([m for m in c]) # Output: [Color.RED: red, Color.GREEN: green] +print([m.name for m in c]) # Output: ['RED', 'GREEN'] +print([m.value for m in c]) # Output: ['red', 'green'] +``` + + +### 2. Reverse Lookup +The library provides two ways to find a member based on its raw value. + +```python +class Status(Enum): + IDLE = 0 + RUNNING = 1 + +# Method A: Via Class (Simulates interpreting hardware/network bytes) +# Uses __new__ logic to return the correct EnumValue +current_status = Status(1) +print(current_status.name) # Output: RUNNING +print(current_status.value) # Output: 1 +print(current_status) # Output: Status.RUNNING: 1 +print(current_status()) # Output: 1 + +# Method B: Via Instance Call +s = Status() +print(s(0).name) # Output: IDLE +print(s(0).value) # Output: 0 +print(s(0)) # Output: Status.IDLE: 0 +print(s(0)()) # Output: 0 +``` + + +### 3. Functional API (Dynamic Creation) +If you need to create an Enum from external data (like a JSON config), use the functional constructor. + +```python +# Create a dynamic Enum instance +State = Enum(name='State', names={'ON': 1, 'OFF': 2}) + +print(State) # Output: Enum(name='State', names={'ON': 1, 'OFF': 2}) +print(State.ON) # Output: State.ON: 1 +print(State.ON.name) # Output: ON +print(State.ON.value) # Output: 1 +print(State.ON()) # Output: 1 +assert State.ON == 1 # Comparison +assert State.ON() == 1 # +assert State.ON.value == 1 # +assert State.ON.name == "ON" # +``` + + +### 4. Serialization (Repr / Eval) +The library ensures that the string representation can be used to perfectly reconstruct the object. + +```python +from enum import Enum + +class Color(Enum): + RED = 'red' + GREEN = 'green' + BLUE = 3 + +colors = Color() +# Get serialized string +serialized = repr(colors) +# Reconstruct object +restored_colors = eval(serialized) + +print(f"Original: {colors}") # Output: Original: Enum(name='Color', names={'BLUE': 3, 'RED': 'red', 'GREEN': 'green'}) +print(f"Restored: {restored_colors}") # Output: Restored: Enum(name='Color', names={'BLUE': 3, 'RED': 'red', 'GREEN': 'green'}) +print(colors == restored_colors) # Output: True +``` + + +--- + +## API Reference + +### `EnumValue` +The object representing a specific member of an Enum. +* `.name`: The string name of the member. +* `.value`: The raw value associated with the member. +* `()`: Calling the member object returns its raw value (e.g., `c.RED() -> 'red'`). + +### `Enum` +The base class for all enumerations. +* `list()`: Returns a list of all defined members. +* `is_value(value)`: Returns `True` if the provided raw value exists within the Enum. +* `__len__`: Returns the total number of members. +* `__iter__`: Allows looping through members (e.g., `[m.name for m in color_inst]`). + +--- + +## Error Handling +* **`AttributeError`**: + * Raised when attempting to modify an `EnumValue`. + * Raised when attempting to add new members to an initialized Enum. + * Raised when a class-level lookup (`Status(999)`) fails. + * Raised when an instance-level lookup (`s(999)`) fails. + +## Compare with CPython + +```python +# Run on MicroPython v1.28.0 on 2026-04-06; Generic ESP32 module with ESP32 +# Run on Python 3.12.10 +from enum import Enum + +# class syntax +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +# OR +# functional syntax +# Color = Enum('Color', {'RED': 1, 'GREEN': 2, 'BLUE': 3}) + +# List enum members +try: + print(list(Color)) +# [, , ] +except: + print(Color.list()) +# [RED: 1, GREEN: 2, BLUE: 3] + +# Accessing enum member by name +print(Color.GREEN, type(Color.GREEN)) +# Color.GREEN +# GREEN: 2 + +# Accessing enum member by name +try: + print(Color['GREEN']) +# Color.GREEN +except: + print(Color('GREEN')) +# GREEN: 2 + +# Accessing enum member by value +print(Color(2)) +# Color.GREEN + +# Accessing enum member name +print(Color.GREEN.name, type(Color.GREEN.name)) +# GREEN + +# Accessing enum member value +print(Color.GREEN.value, type(Color.GREEN.value)) +# 2 +``` + +### Output is: + +| MicroPython v1.28.0 | Python 3.12.10 | +| :--- | :--- | +| [Color.RED: 1, Color.GREEN: 2, Color.BLUE: 3] | [, , ] | +| Color.GREEN: 2 | Color.GREEN | +| Color.GREEN: 2 | Color.GREEN | +| Color.GREEN: 2 | Color.GREEN | +| GREEN | GREEN | +| 2 | 2 | + diff --git a/python-stdlib/enum/enum.py b/python-stdlib/enum/enum.py new file mode 100644 index 000000000..28472b4e7 --- /dev/null +++ b/python-stdlib/enum/enum.py @@ -0,0 +1,269 @@ +# enum.py +# version="1.3.0" + + +def _make_enum(v, n, e): + T = type(v) + + def _setattr(self, k, v): + raise AttributeError(f"{self.__class__.__name__} is immutable") + + # Create class: type(name, bases, dict), which inherits a base type (int, str, etc.) + return type( + "EnumValue", + (T,), + { + "name": n, + "value": property(lambda s: v), + "__repr__": lambda s: f"{e}.{n}: {v}", + "__str__": lambda s: f"{e}.{n}: {v}", + "__call__": lambda s: v, + "__setattr__": _setattr, + }, + )(v) + + +class Enum: + def __new__(cls, name=None, names=None): + # If a name and names are provided, create a NEW subclass of Enum + if name and names: + # Support Functional API: Enum("Name", {"KEY1": VALUE1, "KEY2": VALUE2, ..}) + # Dynamically create: class + new_cls = type(name, (cls,), {"_i": True}) # _inited + for k, v in names.items(): + setattr(new_cls, k, _make_enum(v, k, name)) + return super().__new__(new_cls) + + # Reverse lookup by value or name (e.g., Color(1) or Color("RED")) + if name and cls is not Enum: + return cls._lookup(name) + + return super().__new__(cls) + + def __init__(self, name=None, names=None): + if "_i" not in self.__class__.__dict__: + self.list() + + @classmethod + def _lookup(cls, v): + for m in cls.list(): + if m.value == v or m.name == v: + return m + raise AttributeError(f"{v} is not in {cls.__name__}") + + @classmethod + def __iter__(cls): + return iter(cls.list()) + + @classmethod + def list(cls): + if "_i" not in cls.__dict__: + # Copy dict.items() to avoid RuntimeError when changing the dictionary + for k, v in list(cls.__dict__.items()): + if not k.startswith("_") and not callable(v): + setattr(cls, k, _make_enum(v, k, cls.__name__)) + cls._i = True + return [ + m for k in dir(cls) if not k.startswith("_") and hasattr(m := getattr(cls, k), "name") + ] + + @classmethod + def is_value(cls, v): + return any(m.value == v or m.name == v for m in cls.list()) + + def __repr__(self): + # Supports the condition: obj == eval(repr(obj)) + d = {m.name: m.value for m in self.__class__.list()} + # Return a string like: Enum(name='Name', names={'KEY1': VALUE1, 'KEY2': VALUE2, ..}) + return f"Enum(name='{self.__class__.__name__}', names={d})" + + def __call__(self, v): + return self._lookup(v) + + def __setattr__(self, k, v): + if "_i" in self.__class__.__dict__: + raise AttributeError(f"{self.__class__.__name__} is immutable") + super().__setattr__(k, v) + + def __delattr__(self, k): + raise AttributeError(f"{self.__class__.__name__} is immutable") + + @classmethod + def __len__(cls): + return len(cls.list()) + + def __eq__(self, o): + if not isinstance(o, Enum): + return False + return self.list() == o.list() + + +if __name__ == "__main__": + # --- Usage Example 1 --- + # Standard Class Definition + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + # Basic access + print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, {Color(1).name} ") + print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()} ") + assert Color(1).value == 1 + assert Color.BLUE.value >= Color.GREEN.value + + print("Color.list():", Color.list()) + + # Iteration + print("Members list:", [member for member in Color()]) + print("Names list:", [member.name for member in Color()]) + print("Values list:", [member.value for member in Color()]) + print() + + # Create instance + c = Color() + print(f"Enum c: {c}") + + # Basic access + print(f"RED: name={c.RED.name}, value={c.RED.value}, str={str(c.RED)}, call={c.RED()} ") + + # Assertions + assert c.RED.name == "RED" + assert c.RED.value == 1 + assert c.RED == 1 + assert c.RED() == 1 + + # Reverse Lookup via instance call + o = c(1) + print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}") + assert c(1).name == "RED" + assert c(1).value == 1 + assert c(1) == 1 + + try: + c(999) + 0 / 0 + except AttributeError as e: + print(f"\nAttributeError: {e}: {c}\n") + + # --- Usage Example 2 --- + # Define an Enum class + class Status(Enum): + IDLE = 0 + RUNNING = 1 + ERROR = 2 + + # 2. Test: Reverse Lookup + # This simulates receiving a byte from the hardware + received_byte = 1 + status = Status(received_byte) + print(f"Lookup check: Received {received_byte} -> {status}") + print(Status.__len__()) + print(len(Status())) + assert status == received_byte + assert status == Status.RUNNING + assert status.name == "RUNNING" + assert status.value == received_byte + + # Test: Comparisons + print(f"Comparison check: {status} == 1 is {status == 1}") + assert status == 1 + assert status != 0 + + # Immutability Check + try: + Status.RUNNING.value = 999 + 0 / 0 + except AttributeError as e: + print(f"\nImmutability check: Passed (Cannot modify EnumValue): {e}\n") + + # Test: Iteration + print("Iteration check: ", end="") + for m in Status(): + print(f"{m.name}, ", end="") + print("-> Passed") + + # Test: Error handling for invalid lookup + try: + Status(999) + 0 / 0 + except AttributeError as e: + print(f"\nAttributeError: Invalid lookup check: Caught expected error -> {e}\n") + + # --- Example 3: Functional API and serialization --- + print("--- Functional API and Eval Check ---") + + # Verify that eval(repr(obj)) restores the object + c_repr = repr(c) + print(f"Original: {c_repr}") + c_restored = eval(c_repr) + print(f"Restored: {repr(c_restored)}") + print(f"Objects are equal: {c == c_restored}") + assert c == c_restored + + # Direct creation using the Enum base class + state = eval("Enum(name='State', names={'ON':1, 'OFF':2})") + print(f"Functional Enum instance (state): {state}") + print(type(state)) + assert state.ON == 1 + assert state.ON.name == "ON" + assert state.ON > 0 + assert state.ON.value | state.OFF.value == 3 + + # --- 1. Unique Data Types & Class Methods --- + # Enums can hold more than just integers; here we use strings and add a method. + class HttpMethod(Enum): + GET = "GET" + POST = "POST" + DELETE = "DELETE" + + def is_safe(self): + # Demonstrates that custom logic can coexist with Enum members + return self.list()[0] == self.GET # Simplistic example check + + api_call = HttpMethod() + print(f"Member with string value: {api_call.GET}") + assert api_call.GET == "GET" + + # --- 2. Advanced Reverse Lookup Scenarios --- + # Demonstrates lookup by both name string and raw value string. + print(f"Lookup by value 'POST': {api_call('POST')}") + print(f"Lookup by name 'DELETE': {api_call('DELETE')}") + assert api_call("GET").name == "GET" + + # --- 3. Empty Enum Handling --- + # Verifies behavior when no members are defined. + class Empty(Enum): + pass + + empty_inst = Empty() + print(f"Empty Enum list: {empty_inst.list()}") + assert len(empty_inst) == 0 + + # --- 4. Deep Functional API & Serialization --- + # Testing complex name strings and verifying the 'eval' round-trip for functional enums. + complex_enum = Enum(name='Config', names={'MAX_RETRY': 5, 'TIMEOUT_SEC': 30}) + + # Verify serialization maintains the dynamic class name + repr_str = repr(complex_enum) + restored = eval(repr_str) + + print(f"Restored Functional Enum: {restored}") + assert restored.MAX_RETRY == 5 + assert type(restored).__name__ == 'Config' + + # --- 5. Immutability & Integrity Guard --- + # Ensuring the Enum structure cannot be tampered with after creation. + try: + api_call.NEW_METHOD = "PATCH" + 0 / 0 + except AttributeError as e: + print(f"Caught expected mutation error: {e}") + + try: + del api_call.GET + 0 / 0 + except AttributeError as e: + print(f"Caught expected deletion error: {e}") + + print("\nAll tests passed successfully!") diff --git a/python-stdlib/enum/manifest.py b/python-stdlib/enum/manifest.py new file mode 100644 index 000000000..137ec22aa --- /dev/null +++ b/python-stdlib/enum/manifest.py @@ -0,0 +1,3 @@ +metadata(version="1.3.0") + +module("enum.py") diff --git a/python-stdlib/enum/test_enum.py b/python-stdlib/enum/test_enum.py new file mode 100644 index 000000000..caa8bee64 --- /dev/null +++ b/python-stdlib/enum/test_enum.py @@ -0,0 +1,141 @@ +# test_enum.py +# version="1.3.0" + +import unittest +from enum import Enum + + +class TestEnum(unittest.TestCase): + def setUp(self): + # Standard Class Definitions for testing + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + class Status(Enum): + IDLE = 0 + RUNNING = 1 + ERROR = 2 + + self.ColorClass = Color + self.color = Color() + self.StatusClass = Status + self.status = Status() + + def test_class_attributes(self): + """Test basic access to Enum members, names, and values.""" + self.assertEqual(self.color.RED.value, 1) + self.assertEqual(self.color.RED.name, 'RED') + self.assertEqual(str(type(self.color.RED)), "") + self.assertEqual(type(self.color.RED).__name__, 'EnumValue') + EnumValue = type(self.color.RED) + self.assertIsInstance(self.color.RED, EnumValue) + self.assertEqual(self.status.IDLE.value, 0) + + def test_comparison(self): + """Test equality between EnumValues and raw values.""" + self.assertTrue(self.color.RED == 1) + self.assertFalse(self.color.RED == 2) + self.assertEqual(self.color.RED, self.color.RED) + # Verify object does not equal value of different type + self.assertFalse(self.color.RED == "1") + + def test_call_reverse_lookup(self): + """Test reverse lookup via instance call Color(1) -> RED.""" + result = self.color(1) + self.assertEqual(result.name, 'RED') + self.assertEqual(result.value, 1) + + # Test lookup by name string + result = self.color('RED') + self.assertEqual(result.name, 'RED') + + result = self.color(self.ColorClass.RED) + self.assertEqual(result, self.ColorClass.RED) + + with self.assertRaises(AttributeError): + self.color(999) + + def test_constructor_reverse_lookup(self): + """Test reverse lookup via class constructor Status(1) -> RUNNING.""" + member = self.StatusClass(1) + self.assertEqual(member.name, "RUNNING") + self.assertEqual(member, self.StatusClass.RUNNING) + + with self.assertRaises(AttributeError): + self.StatusClass(999) + + def test_is_value(self): + """Test utility method is_value().""" + self.assertTrue(self.color.is_value(1)) + self.assertTrue(self.color.is_value(3)) + self.assertFalse(self.color.is_value(5)) + + def test_iteration(self): + """Test iteration over Enum instance.""" + members = list(self.color) + names = [m.name for m in members] + self.assertEqual(len(members), 3) + self.assertIn('RED', names) + self.assertIn('GREEN', names) + self.assertIn('BLUE', names) + + def test_immutability(self): + """Verify EnumValue and Enum instance are immutable after init.""" + # EnumValue attribute protection + with self.assertRaises(AttributeError): + self.color.RED.value = 10 + + # Enum instance attribute protection (static) + with self.assertRaises(AttributeError): + self.color.NEW_MEMBER = 4 + + def test_deletion_prevention(self): + """Verify that members cannot be deleted.""" + with self.assertRaises(AttributeError): + del self.color.RED + + def test_len_and_list(self): + """Test __len__ and list() utility methods.""" + self.assertEqual(len(self.color), 3) + members_list = self.color.list() + self.assertEqual(members_list, [self.color.RED, self.color.GREEN, self.color.BLUE]) + + def test_call_method(self): + """Test calling EnumValue as a function to get its value.""" + self.assertEqual(self.color.RED(), 1) + self.assertEqual(self.color.GREEN(), 2) + + def test_functional_api(self): + """Test dynamic Enum creation using the Functional API.""" + # Logic restored from commented out sections + State = Enum(name='State', names={'ON': 1, 'OFF': 0}) + self.assertTrue(hasattr(State, 'ON')) + self.assertEqual(State.ON.value, 1) + self.assertEqual(State.OFF.name, 'OFF') + + def test_serialization_repr_eval(self): + """Verify eval(repr(obj)) restores the Enum correctly.""" + # Test standard instance + c_repr = repr(self.color) + c_restored = eval(c_repr) + # Check equality of members + self.assertEqual(self.color.list(), c_restored.list()) + self.assertEqual(type(self.color).__name__, type(c_restored).__name__) + + # Test functional instance + s_dynamic = Enum(name='StatusFunc', names={'START': 1, 'STOP': 0}) + s_repr = repr(s_dynamic) + s_restored = eval(s_repr) + self.assertEqual(s_dynamic.list(), s_restored.list()) + + # Test functional instance + s_dynamic = Enum(name='State', names={'ON': 1, 'OFF': 0}) + s_repr = repr(s_dynamic) + s_restored = eval(s_repr) + self.assertEqual(s_dynamic.list(), s_restored.list()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/ci.sh b/tools/ci.sh old mode 100755 new mode 100644 index 4533fe204..8e6935eb6 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -66,6 +66,7 @@ function ci_package_tests_run { python-stdlib/binascii/test_binascii.py \ python-stdlib/collections-defaultdict/test_defaultdict.py \ python-stdlib/curses.ascii/test_curses_ascii.py \ + python-stdlib/enum/test_enum.py \ python-stdlib/functools/test_partial.py \ python-stdlib/functools/test_reduce.py \ python-stdlib/functools/test_cache.py \ From 29b8085eae6a35c4bc74c4807ae99b7c604d53bf Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Mon, 5 Oct 2026 21:00:17 +0300 Subject: [PATCH 2/5] python-stdlib\enum: Bump to version="1.4.0". Signed-off-by: Ihor Nehrutsa --- python-stdlib/enum/README.md | 275 ++++++--------------- python-stdlib/enum/enum.py | 395 ++++++++++++------------------- python-stdlib/enum/enum_check.py | 172 ++++++++++++++ python-stdlib/enum/enum_usage.py | 155 ++++++++++++ python-stdlib/enum/manifest.py | 2 +- python-stdlib/enum/test_enum.py | 156 ++++++------ 6 files changed, 646 insertions(+), 509 deletions(-) create mode 100644 python-stdlib/enum/enum_check.py create mode 100644 python-stdlib/enum/enum_usage.py diff --git a/python-stdlib/enum/README.md b/python-stdlib/enum/README.md index 6d1184bc4..662c8bace 100644 --- a/python-stdlib/enum/README.md +++ b/python-stdlib/enum/README.md @@ -1,201 +1,74 @@ -# Enum Library - -This library provides a lightweight, memory-efficient `Enum` implementation designed for MicroPython environments. It focuses on immutability, reverse lookup capabilities, and serialization support without the complexity of metaclasses. - ---- - -## Core Features -* **Immutability**: Enum members (`EnumValue`) are protected against modification. Any attempt to change their name or value raises an `AttributeError`. -* **Static Design**: Once an Enum instance is initialized, it is "frozen." You cannot add new attributes or delete existing members. -* **Dual Reverse Lookup**: - * **Class Constructor**: Retrieve a member by value using the class name (e.g., `Status(1)`). - * **Instance Call**: Retrieve a member by value by calling the instance (e.g., `s(1)`). -* **Serialization Support**: Implements `__repr__` such that `obj == eval(repr(obj))`, allowing easy restoration of Enum states. -* **Functional API**: Supports dynamic creation of Enums at runtime. - ---- - -## Usage Examples - -### 1. Standard Class Definition -Define your enumeration by inheriting from the `Enum` class. Class-level constants are automatically converted into `EnumValue` objects upon initialization. - -```python -from enum import Enum - -class Color(Enum): - RED = 'red' - GREEN = 'green' - -# Initialize the enum to process attributes -c = Color() - -print(c.RED) # Output: RED: red -print(c.RED.name) # Output: RED -print(c.RED.value) # Output: red -print(c.RED()) # Output: red -print(c.is_value("RED")) # Output: true -print(c.is_value(Color.RED)) # Output: true -print(c.is_value('red')) # Output: true -print(c.list()) # Output: [Color.RED: red, Color.GREEN: green] -print([m for m in c]) # Output: [Color.RED: red, Color.GREEN: green] -print([m.name for m in c]) # Output: ['RED', 'GREEN'] -print([m.value for m in c]) # Output: ['red', 'green'] -``` - - -### 2. Reverse Lookup -The library provides two ways to find a member based on its raw value. - -```python -class Status(Enum): - IDLE = 0 - RUNNING = 1 - -# Method A: Via Class (Simulates interpreting hardware/network bytes) -# Uses __new__ logic to return the correct EnumValue -current_status = Status(1) -print(current_status.name) # Output: RUNNING -print(current_status.value) # Output: 1 -print(current_status) # Output: Status.RUNNING: 1 -print(current_status()) # Output: 1 - -# Method B: Via Instance Call -s = Status() -print(s(0).name) # Output: IDLE -print(s(0).value) # Output: 0 -print(s(0)) # Output: Status.IDLE: 0 -print(s(0)()) # Output: 0 -``` - - -### 3. Functional API (Dynamic Creation) -If you need to create an Enum from external data (like a JSON config), use the functional constructor. - -```python -# Create a dynamic Enum instance -State = Enum(name='State', names={'ON': 1, 'OFF': 2}) - -print(State) # Output: Enum(name='State', names={'ON': 1, 'OFF': 2}) -print(State.ON) # Output: State.ON: 1 -print(State.ON.name) # Output: ON -print(State.ON.value) # Output: 1 -print(State.ON()) # Output: 1 -assert State.ON == 1 # Comparison -assert State.ON() == 1 # -assert State.ON.value == 1 # -assert State.ON.name == "ON" # -``` - - -### 4. Serialization (Repr / Eval) -The library ensures that the string representation can be used to perfectly reconstruct the object. - -```python -from enum import Enum - -class Color(Enum): - RED = 'red' - GREEN = 'green' - BLUE = 3 - -colors = Color() -# Get serialized string -serialized = repr(colors) -# Reconstruct object -restored_colors = eval(serialized) - -print(f"Original: {colors}") # Output: Original: Enum(name='Color', names={'BLUE': 3, 'RED': 'red', 'GREEN': 'green'}) -print(f"Restored: {restored_colors}") # Output: Restored: Enum(name='Color', names={'BLUE': 3, 'RED': 'red', 'GREEN': 'green'}) -print(colors == restored_colors) # Output: True -``` - - ---- - -## API Reference - -### `EnumValue` -The object representing a specific member of an Enum. -* `.name`: The string name of the member. -* `.value`: The raw value associated with the member. -* `()`: Calling the member object returns its raw value (e.g., `c.RED() -> 'red'`). - -### `Enum` -The base class for all enumerations. -* `list()`: Returns a list of all defined members. -* `is_value(value)`: Returns `True` if the provided raw value exists within the Enum. -* `__len__`: Returns the total number of members. -* `__iter__`: Allows looping through members (e.g., `[m.name for m in color_inst]`). - ---- - -## Error Handling -* **`AttributeError`**: - * Raised when attempting to modify an `EnumValue`. - * Raised when attempting to add new members to an initialized Enum. - * Raised when a class-level lookup (`Status(999)`) fails. - * Raised when an instance-level lookup (`s(999)`) fails. - -## Compare with CPython - -```python -# Run on MicroPython v1.28.0 on 2026-04-06; Generic ESP32 module with ESP32 -# Run on Python 3.12.10 -from enum import Enum - -# class syntax -class Color(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - -# OR -# functional syntax -# Color = Enum('Color', {'RED': 1, 'GREEN': 2, 'BLUE': 3}) - -# List enum members -try: - print(list(Color)) -# [, , ] -except: - print(Color.list()) -# [RED: 1, GREEN: 2, BLUE: 3] - -# Accessing enum member by name -print(Color.GREEN, type(Color.GREEN)) -# Color.GREEN -# GREEN: 2 - -# Accessing enum member by name -try: - print(Color['GREEN']) -# Color.GREEN -except: - print(Color('GREEN')) -# GREEN: 2 - -# Accessing enum member by value -print(Color(2)) -# Color.GREEN - -# Accessing enum member name -print(Color.GREEN.name, type(Color.GREEN.name)) -# GREEN - -# Accessing enum member value -print(Color.GREEN.value, type(Color.GREEN.value)) -# 2 -``` - -### Output is: - -| MicroPython v1.28.0 | Python 3.12.10 | -| :--- | :--- | -| [Color.RED: 1, Color.GREEN: 2, Color.BLUE: 3] | [, , ] | -| Color.GREEN: 2 | Color.GREEN | -| Color.GREEN: 2 | Color.GREEN | -| Color.GREEN: 2 | Color.GREEN | -| GREEN | GREEN | -| 2 | 2 | - +# Lightweight Enum + +This package provides two lightweight enumeration implementations for +MicroPython: + +* `enum.py` provides `Enum`, `IntEnum`, and `StrEnum`, class declarations, + several forms of the functional API, name/value lookup, iteration, and + `.dump()`. +* `enum_mini.py` provides a smaller `Enum` implementation for applications + that only need basic members, dictionary-based functional creation, lookup, + and iteration. + +Both implementations avoid metaclasses. Members declared in a class are +initialized lazily when the enum is first instantiated or looked up; members +created through the functional API are initialized immediately. Members inherit +from the type of their value, so integer-valued members behave like integers +and string-valued members behave like strings. Consequently, equality with a +raw value (and between members from different enum classes with the same value) +is possible. This differs from Python's standard-library `Enum`. + +### Standard Behavior (Compatible) +- ✅ Member access: `Color.RED`, `.name`, `.value` +- ✅ Member iteration: `for item in Color()` +- ✅ `IntEnum` and `StrEnum` types with strict value checking +- ✅ Immutability: members cannot be modified after creation +- ✅ Functional API: `Enum('Name', {'KEY': value, ...})` + +### Non-Standard Behavior +Key differences from CPython: +- **Explicit Lazy Initialization**: MicroPython implementations rely on class/instance evaluation (e.g., `Color()`) to trigger lazy member initialization when standard class bodies aren't fully traversed upfront. +- **Name-based lookup via call**: `Color("RED")` works for both name and value lookup (CPython standard call only supports value lookup). +- **Value equality**: `Color.RED == 1` is `True` (only true for `IntEnum` in CPython). +- **Custom methods**: `.dump()` does not exist in stdlib `Enum`. +- **Callable members**: `Color.RED()` returns the value (not supported in CPython). +- **Instance `__len__`**: `len(Color())` works; CPython enums are not containers. +- **Serialization**: `dump()` for eval-based reconstruction is MicroPython-specific. + +### Migration Notes +If you're porting code from CPython `enum`: +- Use `Color()` MicroPython requires explicit initialization. +- Replace `Color['RED']` with `Color('RED')`. + +## Comparing the implementations + +| Feature | CPython | `enum.py` | `enum_mini.py` | +| --- | --- | --- | --- | +| Enum types | `Enum`, `IntEnum`, `StrEnum` | `Enum`, `IntEnum`, `StrEnum` | `Enum` | +| Lookup by name | `Color["RED"]` | `Color("RED")` or `Color()["RED"]` | `Color("RED")` | +| Lookup by value | `Color(1)` | `Color(1)` | `Color(1)` | +| Iteration | `for item in Color` | `for item in Color()` | `for item in Color()` | +| Iteration in `__members__` | Yes | Yes | Yes | +| `__members__` keys and values | Names and members | Members and values | Members and values | +| Functional API | Dict, names string, list/tuple, or iterable | Dict, names string, list/tuple, or iterable | Dictionary only | +| `start` for generated integer values | Yes | Yes | No | +| `.dump()` | No | Yes | No | +| Plain `Enum` member equals its raw value | No | Yes | Yes | +| Member is an instance of its enum class | Yes | No | No | +| Calling the enum without arguments returns a container | No | Yes | Yes | + +## Code + +The runnable example in [`CPy/CPy.py`](CPy/CPy.py) compares basic member +access, lookup, iteration, equality, and immutability on CPython and +MicroPython. +Run it with `python CPy.py`. + +## Limitations + +These implementations intentionally provide a smaller API than Python's +standard-library `enum`. In particular, members are value-type instances, not +instances of their enum class; equal values can compare equal across different +enum classes; and enum classes do not provide the full standard-library Enum +semantics. Choose the full implementation when you need `IntEnum`, `StrEnum`, +the expanded functional API, or `.dump()`. diff --git a/python-stdlib/enum/enum.py b/python-stdlib/enum/enum.py index 28472b4e7..edfd42bde 100644 --- a/python-stdlib/enum/enum.py +++ b/python-stdlib/enum/enum.py @@ -1,269 +1,182 @@ # enum.py -# version="1.3.0" - - -def _make_enum(v, n, e): - T = type(v) - - def _setattr(self, k, v): - raise AttributeError(f"{self.__class__.__name__} is immutable") - - # Create class: type(name, bases, dict), which inherits a base type (int, str, etc.) +# Enum implementation without metaclasses +# version="1.4.0" + +# ============================================================================== +# Variable & Abbreviation Definitions: +# ============================================================================== +# Functions & Helper Methods: +# _c(e, n, v, v_t) -> create_enum_item: Helper function to construct a typed enum instance. +# _i() -> items: Classmethod to lazily initialize and return the __members__ dict. +# a(s, k, v) -> __setattr__: Inner setter guard raising AttributeError on enum items. +# +# Helper Arguments & Local Variables: +# e - enum_name / enum_item: Class name string or instance of an enum item. +# n - name: String representing the enum member key (e.g., "RED"). +# v - value / member value: Value assigned to or searched within the enum member. +# c - class / new_class: Dynamically created Enum subclass via `type()`. +# v_t - value_type: Target constraint type (`int`, `str`, or `None` for generic Enum). +# l - names_list: List of parsed string keys from comma/space-separated inputs. +# d - dict / mapping: Temporary dictionary for parsed enum members. +# k, v - key, value: Key-value pair during iteration over attributes or dictionaries. +# i - item: Individual enum member instance during iteration. +# __members__ - Dictionary mapping enum member instances to their raw values. +# s, k, o - self, key/attribute_name, other_enum (standard short parameters). +# ============================================================================== + + +def _c(e, n, v, v_t=None): + # Inner guard to prevent modification of enum item attributes + def a(s, k, v): + raise AttributeError("cannot set attribute") + + # Type checks for IntEnum and StrEnum + if v_t is int and not isinstance(v, int): + raise TypeError(f"IntEnum member {n!r} value must be int, got {type(v).__name__}") + elif v_t is str and not isinstance(v, str): + raise TypeError(f"StrEnum member {n!r} value must be str, got {type(v).__name__}") + + # Safely extract class name string if a class or object was passed + e = getattr(e, "__name__", str(e)) + + # Create dynamic subclass inheriting the value's type (int, str, float etc.) return type( - "EnumValue", - (T,), + f"{e}.{n}", + (type(v),), { "name": n, - "value": property(lambda s: v), - "__repr__": lambda s: f"{e}.{n}: {v}", - "__str__": lambda s: f"{e}.{n}: {v}", + "value": v, + "__str__": lambda s: str(v) if v_t in (int, str) else f"{e}.{n}", + "__repr__": lambda s: f"<{e}.{n}: {v!r}>", "__call__": lambda s: v, - "__setattr__": _setattr, + "__setattr__": a, }, )(v) class Enum: - def __new__(cls, name=None, names=None): - # If a name and names are provided, create a NEW subclass of Enum - if name and names: - # Support Functional API: Enum("Name", {"KEY1": VALUE1, "KEY2": VALUE2, ..}) - # Dynamically create: class - new_cls = type(name, (cls,), {"_i": True}) # _inited - for k, v in names.items(): - setattr(new_cls, k, _make_enum(v, k, name)) - return super().__new__(new_cls) - - # Reverse lookup by value or name (e.g., Color(1) or Color("RED")) - if name and cls is not Enum: - return cls._lookup(name) + _v_t = None # Expected value type constraint (int, str, or None) + + def __new__(cls, value=None, names=None, *, start=1): + # Functional API: dynamic creation of a new Enum class + if value is not None and names is not None: + is_str_enum = cls._v_t is str + + # Parse 'names' parameter into a key-value dictionary + if isinstance(names, dict): + d = names + elif isinstance(names, str): + # Space or comma-separated string of member names + l = names.replace(",", " ").split() + d = ( + {k: k for k in l} + if is_str_enum + else {k: v for v, k in enumerate(l, start=start)} + ) + elif isinstance(names, (list, tuple)): + # List/tuple of strings or (name, value) pairs + if names and isinstance(names[0], (list, tuple)): + d = dict(names) + else: + d = ( + {k: k for k in names} + if is_str_enum + else {k: v for v, k in enumerate(names, start=start)} + ) + else: + # Other iterables (e.g., sets) + d = ( + {k: k for k in names} + if is_str_enum + else {k: v for v, k in enumerate(names, start=start)} + ) + + # Construct and return a new Enum subclass + c = type(str(value), (cls,), {"__members__": {}}) + for k, v in d.items(): + e = _c(value, k, v, v_t=cls._v_t) + setattr(c, k, e) + c.__members__[e] = v + return c + + if cls not in (Enum, IntEnum, StrEnum): + # Lazy initialization of subclass members + cls._i() + + # Lookup existing member by value or name: e.g., Color(1) or Color("RED") + if value is not None: + return cls()(value) return super().__new__(cls) - def __init__(self, name=None, names=None): - if "_i" not in self.__class__.__dict__: - self.list() - @classmethod - def _lookup(cls, v): - for m in cls.list(): - if m.value == v or m.name == v: - return m - raise AttributeError(f"{v} is not in {cls.__name__}") + def __contains__(cls, v): + # Lookup member by value or name + for i in cls._i(): + if i.value == v or i.name == v: + return True + return False @classmethod - def __iter__(cls): - return iter(cls.list()) + def __call__(cls, v): + # Lookup member by value or name + for i in cls._i(): + if i.value == v or i.name == v: + return i + raise ValueError(f"{v!r} is not a valid {cls.__name__}") @classmethod - def list(cls): - if "_i" not in cls.__dict__: - # Copy dict.items() to avoid RuntimeError when changing the dictionary - for k, v in list(cls.__dict__.items()): - if not k.startswith("_") and not callable(v): - setattr(cls, k, _make_enum(v, k, cls.__name__)) - cls._i = True - return [ - m for k in dir(cls) if not k.startswith("_") and hasattr(m := getattr(cls, k), "name") - ] + def __getitem__(cls, k): + # Instance-level container lookup: Color()["RED"] + for i in cls._i(): + if i.name == k: + return i + raise KeyError(k) + + # Equality checks if both classes are Enums and have identical __members__ dicts + # __eq__ = classmethod(lambda cls, o: isinstance(o, type) and issubclass(o, Enum) and cls._i() == o._i()) + __eq__ = classmethod(lambda cls, o: getattr(o, "_i", None) and cls._i() == o._i()) + # Iteration yields enum member instances (keys of __members__) + __iter__ = classmethod(lambda cls: iter(cls._i())) + __len__ = classmethod(lambda cls: len(cls._i())) + __str__ = __repr__ = classmethod(lambda cls: f"") @classmethod - def is_value(cls, v): - return any(m.value == v or m.name == v for m in cls.list()) - - def __repr__(self): - # Supports the condition: obj == eval(repr(obj)) - d = {m.name: m.value for m in self.__class__.list()} - # Return a string like: Enum(name='Name', names={'KEY1': VALUE1, 'KEY2': VALUE2, ..}) - return f"Enum(name='{self.__class__.__name__}', names={d})" - - def __call__(self, v): - return self._lookup(v) - - def __setattr__(self, k, v): - if "_i" in self.__class__.__dict__: - raise AttributeError(f"{self.__class__.__name__} is immutable") - super().__setattr__(k, v) - - def __delattr__(self, k): - raise AttributeError(f"{self.__class__.__name__} is immutable") + def __setattr__(cls, k, v): + raise AttributeError("cannot set attribute") @classmethod - def __len__(cls): - return len(cls.list()) - - def __eq__(self, o): - if not isinstance(o, Enum): - return False - return self.list() == o.list() - - -if __name__ == "__main__": - # --- Usage Example 1 --- - # Standard Class Definition - class Color(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - - # Basic access - print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, {Color(1).name} ") - print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()} ") - assert Color(1).value == 1 - assert Color.BLUE.value >= Color.GREEN.value - - print("Color.list():", Color.list()) - - # Iteration - print("Members list:", [member for member in Color()]) - print("Names list:", [member.name for member in Color()]) - print("Values list:", [member.value for member in Color()]) - print() - - # Create instance - c = Color() - print(f"Enum c: {c}") - - # Basic access - print(f"RED: name={c.RED.name}, value={c.RED.value}, str={str(c.RED)}, call={c.RED()} ") - - # Assertions - assert c.RED.name == "RED" - assert c.RED.value == 1 - assert c.RED == 1 - assert c.RED() == 1 + def __delattr__(cls, k): + raise AttributeError("cannot delete attribute") - # Reverse Lookup via instance call - o = c(1) - print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}") - assert c(1).name == "RED" - assert c(1).value == 1 - assert c(1) == 1 - - try: - c(999) - 0 / 0 - except AttributeError as e: - print(f"\nAttributeError: {e}: {c}\n") - - # --- Usage Example 2 --- - # Define an Enum class - class Status(Enum): - IDLE = 0 - RUNNING = 1 - ERROR = 2 - - # 2. Test: Reverse Lookup - # This simulates receiving a byte from the hardware - received_byte = 1 - status = Status(received_byte) - print(f"Lookup check: Received {received_byte} -> {status}") - print(Status.__len__()) - print(len(Status())) - assert status == received_byte - assert status == Status.RUNNING - assert status.name == "RUNNING" - assert status.value == received_byte - - # Test: Comparisons - print(f"Comparison check: {status} == 1 is {status == 1}") - assert status == 1 - assert status != 0 - - # Immutability Check - try: - Status.RUNNING.value = 999 - 0 / 0 - except AttributeError as e: - print(f"\nImmutability check: Passed (Cannot modify EnumValue): {e}\n") - - # Test: Iteration - print("Iteration check: ", end="") - for m in Status(): - print(f"{m.name}, ", end="") - print("-> Passed") - - # Test: Error handling for invalid lookup - try: - Status(999) - 0 / 0 - except AttributeError as e: - print(f"\nAttributeError: Invalid lookup check: Caught expected error -> {e}\n") - - # --- Example 3: Functional API and serialization --- - print("--- Functional API and Eval Check ---") - - # Verify that eval(repr(obj)) restores the object - c_repr = repr(c) - print(f"Original: {c_repr}") - c_restored = eval(c_repr) - print(f"Restored: {repr(c_restored)}") - print(f"Objects are equal: {c == c_restored}") - assert c == c_restored - - # Direct creation using the Enum base class - state = eval("Enum(name='State', names={'ON':1, 'OFF':2})") - print(f"Functional Enum instance (state): {state}") - print(type(state)) - assert state.ON == 1 - assert state.ON.name == "ON" - assert state.ON > 0 - assert state.ON.value | state.OFF.value == 3 - - # --- 1. Unique Data Types & Class Methods --- - # Enums can hold more than just integers; here we use strings and add a method. - class HttpMethod(Enum): - GET = "GET" - POST = "POST" - DELETE = "DELETE" - - def is_safe(self): - # Demonstrates that custom logic can coexist with Enum members - return self.list()[0] == self.GET # Simplistic example check - - api_call = HttpMethod() - print(f"Member with string value: {api_call.GET}") - assert api_call.GET == "GET" - - # --- 2. Advanced Reverse Lookup Scenarios --- - # Demonstrates lookup by both name string and raw value string. - print(f"Lookup by value 'POST': {api_call('POST')}") - print(f"Lookup by name 'DELETE': {api_call('DELETE')}") - assert api_call("GET").name == "GET" - - # --- 3. Empty Enum Handling --- - # Verifies behavior when no members are defined. - class Empty(Enum): - pass - - empty_inst = Empty() - print(f"Empty Enum list: {empty_inst.list()}") - assert len(empty_inst) == 0 - - # --- 4. Deep Functional API & Serialization --- - # Testing complex name strings and verifying the 'eval' round-trip for functional enums. - complex_enum = Enum(name='Config', names={'MAX_RETRY': 5, 'TIMEOUT_SEC': 30}) + @classmethod + def dump(cls): + # Serialize enum members to string representation for eval compatibility + # cls == eval(cls.dump()) + if cls._v_t is None: + e = "Enum" + else: + e = cls._v_t.__name__[0].upper() + cls._v_t.__name__[1:] + "Enum" + d = {i.name: i.value for i in cls._i()} + return f"{e}('{cls.__name__}', {d})" - # Verify serialization maintains the dynamic class name - repr_str = repr(complex_enum) - restored = eval(repr_str) + @classmethod + def _i(cls): + # Initialize and return dictionary mapping enum member instances to their values + if "__members__" not in cls.__dict__: + # Convert raw class attributes into typed enum instances + cls.__members__ = {} + for k, v in list(cls.__dict__.items()): + if not k.startswith("_") and not callable(v): + e = _c(cls.__name__, k, v, v_t=cls._v_t) + setattr(cls, k, e) + cls.__members__[e] = v + return cls.__members__ - print(f"Restored Functional Enum: {restored}") - assert restored.MAX_RETRY == 5 - assert type(restored).__name__ == 'Config' - # --- 5. Immutability & Integrity Guard --- - # Ensuring the Enum structure cannot be tampered with after creation. - try: - api_call.NEW_METHOD = "PATCH" - 0 / 0 - except AttributeError as e: - print(f"Caught expected mutation error: {e}") +class IntEnum(Enum): + _v_t = int - try: - del api_call.GET - 0 / 0 - except AttributeError as e: - print(f"Caught expected deletion error: {e}") - print("\nAll tests passed successfully!") +class StrEnum(Enum): + _v_t = str diff --git a/python-stdlib/enum/enum_check.py b/python-stdlib/enum/enum_check.py new file mode 100644 index 000000000..fd62858f5 --- /dev/null +++ b/python-stdlib/enum/enum_check.py @@ -0,0 +1,172 @@ +# enum_check.py +# version="1.4.0" + +from enum import Enum, IntEnum, StrEnum + +# --- Usage Example 1: Standard Class Definition & Access --- +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +# Basic class-level property and method calls +print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, name={Color(1).name}") +print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()}") +assert Color(1).value == 1 +assert Color.BLUE.value >= Color.GREEN.value + +# Access members via instance indexing and call syntax +assert Color()["RED"] == Color.RED +assert Color("RED") == Color.RED + +# Iteration over Enum members via class instance call +print("Members list:", [member for member in Color()]) +print("Names list:", [member.name for member in Color()]) +print("Values list:", [member.value for member in Color()]) +print() + +# Instance-based interaction and comparison checks +c = Color() +print(f"Enum c instance: {c}") + +assert c.RED.name == "RED" +assert c.RED.value == 1 +assert c.RED == 1 +assert c.RED() == 1 + +# Reverse Lookup via instance call +o = c(1) +print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}") +assert c(1).name == "RED" +assert c(1).value == 1 +assert c(1) == 1 + +# Verify ValueError is raised for invalid key/value lookup +try: + Color(999) + assert False, "Should have raised ValueError" +except ValueError as e: + print(f"ValueError (Caught expected error): {e}\n") + + +# --- Usage Example 2: Enum Operations & Comparisons --- +class Status(Enum): + IDLE = 0 + RUNNING = 1 + ERROR = 2 + +# This simulates receiving a status value from hardware +received_byte = 1 +status = Status(received_byte) +print(f"Lookup check: Received {received_byte} -> {status}") +print(f"Enum length via class: {len(Status())}") + +assert status == received_byte +assert status == Status.RUNNING +assert status.name == "RUNNING" +assert status.value == received_byte + +# Test equality and arithmetic operations +print(f"Comparison check: {status} == 1 is {status == 1}") +assert status == 1 +assert status != 0 +assert status + 10 == 11 + +# Verify immutability of Enum member attributes +try: + Status.RUNNING.value = 999 + assert False, "Should have raised AttributeError" +except AttributeError as e: + print(f"Immutability check: Passed (Cannot modify EnumValue): {e}\n") + +# Iteration over Enum members +print("Iteration check: ", end="") +for m in Status(): + print(f"{m.name}, ", end="") +print("-> Passed") + +try: + Status(999) + assert False, "Should have raised ValueError" +except ValueError as e: + print(f"ValueError (Invalid lookup check): Caught expected error -> {e}\n") + + +# --- Example 3: Functional API, Serialization and Eval --- +print("--- Functional API and Eval Check ---") + +c_dump = Color.dump() +print(f"Original Dump: {c_dump}") +c2 = eval(c_dump) +print(f"Restored Class Dump: {c2.dump()}") + +# Verify structural equality between restored and original class items +print(Color.__members__) +print(c2.__members__) +assert Color.__members__ == c2.__members__ +print("Objects are equal: True") + +# Dynamic class creation via functional API with eval() +state = eval("Enum('State', {'ON':1, 'OFF':2})") +print(f"Functional Enum class (state): {state}") +print(f"Type: {type(state)}") +assert state.ON == 1 +assert state.ON.name == "ON" +assert state.ON > 0 + + +# --- Example 4: Enum with String Values --- +# Standard Enum holding string values +class HttpMethod(Enum): + GET = "GET" + POST = "POST" + DELETE = "DELETE" + +api_call = HttpMethod() +print(f"Member with string value: {api_call.GET}") +assert api_call.GET == "GET" + +# Lookup by raw value string and member name string via instance invocation +print(f"Lookup by value 'POST': {api_call('POST')}") +print(f"Lookup by name 'DELETE': {api_call('DELETE')}") +assert api_call("GET").name == "GET" + + +# --- Example 5: Empty Enum Handling --- +# Verifies container behavior when no members are defined +class Empty(Enum): + pass + +empty = Empty() +print(f"Empty Enum items: {empty.__members__}") +assert len(empty) == 0 + + +# --- Example 6: Deep Functional API & Serialization --- +# Functional creation with explicit class name parameter and dictionary mapping +complex_enum = Enum('Config', {'MAX_RETRY': 5, 'TIMEOUT_SEC': 30}) + +# Verify dump output and reconstruction via eval() +dump_str = complex_enum.dump() +restored = eval(dump_str) + +print(f"Restored Functional Enum: {restored.dump()}") +assert restored.MAX_RETRY == 5 +assert restored.__name__ == 'Config' + + +# --- Example 7: Immutability & Integrity Guard --- +# Ensuring that members cannot be added or deleted dynamically post-instantiation +try: + api_call.NEW_METHOD = "PATCH" + assert False, "Should have raised AttributeError" +except AttributeError as e: + print(f"Caught expected mutation error: {e}") + +try: + del api_call.GET + assert False, "Should have raised AttributeError" +except AttributeError as e: + print(f"Caught expected deletion error: {e}") + +print("\nAll tests passed successfully!") diff --git a/python-stdlib/enum/enum_usage.py b/python-stdlib/enum/enum_usage.py new file mode 100644 index 000000000..52453de28 --- /dev/null +++ b/python-stdlib/enum/enum_usage.py @@ -0,0 +1,155 @@ +# enum_usage.py +# version="1.4.0" + +from enum import Enum, IntEnum, StrEnum + +# ============================================================================== +# Usage Examples +# ============================================================================== + +# ============================================================================== +# Class Definition Syntax +# ============================================================================== + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +color = Color() # Trigger initialization + +# 1. Canonical output (str and repr) +print(Color.RED) # Color.RED +print(repr(Color.RED)) # + +# 2. Attribute access (name and value) +print(Color.RED.name) # RED +print(Color.RED.value) # 1 + +# 3. Equality and identity checks +print(Color.RED == Color.RED) # True +print(Color.RED is Color.RED) # True +print(Color.RED == 1) # True (like CPython's IntEnum; standard CPython Enum returns False) + +# 4. Lookup by value or name (via call) +print(Color(2)) # Color.GREEN +print(Color("RED")) # Color.RED + +# 5. Container access (via instance) +print(Color()["RED"]) # Color.RED (via instance) +# print(Color["RED"]) # does not work + +# 6. Length and iteration (order may vary) +print(len(Color())) # 3 +print([x for x in Color()]) # [, , ] + +# 7. Serialization and dictionary mapping +print(Color.__members__) # {: 1, : 2, : 3} +print(Color.dump()) # Enum('Color', {'BLUE': 3, 'RED': 1, 'GREEN': 2}) + +# 8. Immutability check +try: + Color.RED.value = 100 + raise AssertionError("Expected AttributeError on setting attribute") +except AttributeError as e: + print("Immutability test passed:", e) + +try: + color.RED = 100 + raise AssertionError("Expected AttributeError on setting attribute") +except AttributeError as e: + print("Immutability test passed:", e) + +try: + del color.RED + raise AssertionError("Expected AttributeError on deleting attribute") +except AttributeError as e: + print("Immutability test passed:", e) + +# # test NOT passed +# try: +# del Color.RED +# 0 / 0 +# except AttributeError as e: +# print("Immutability test passed:", e) +# print(Color.RED) + +assert color.RED == 1 + +# 9. Functional API: Comma / space separated string with custom start +State = Enum("State", "OFF, ON", start=10) +print(State.OFF) # State.OFF +print(State.ON) # State.ON +print(State.ON.value) # 11 +print(repr(State.ON)) # + +# 10. Functional API: Dictionary +Load = Enum("Load", {"LOW": "low", "HIGH": "high"}) +print(Load.HIGH) # Load.HIGH +print(Load.HIGH.value) # high +print(Load.LOW == "low") # True (like CPython's StrEnum; standard CPython Enum returns False) + +# 11. Functional API: Tuple of pairs +Do = Enum("Do", (("START", 200), ("STOP", 201))) +print(Do.START) # Do.START +print(Do.STOP) # Do.STOP +print(Do.STOP.value) # 201 + + +# ============================================================================== +# IntEnum Example +# ============================================================================== +class Priority(IntEnum): + LOW = 1 + MEDIUM = 2 + HIGH = 3 + +print("\n--- IntEnum ---") +print(Priority.LOW) # 1 +print(repr(Priority.LOW)) # + +# Arithmetic and numeric comparison (inherits int) +print(Priority.LOW == 1) # True +print(Priority.HIGH > 2) # True +print(Priority.LOW + 10) # 11 + +# Preserving int type inheritance +print(isinstance(Priority.LOW, int)) # True +print(Priority.dump()) # IntEnum('Priority', {'MEDIUM': 2, 'LOW': 1, 'HIGH': 3}) + + +# ============================================================================== +# StrEnum Example +# ============================================================================== +class Status(StrEnum): + PENDING = "pending" + RUNNING = "running" + DONE = "done" + +status = Status() + +print("\n--- StrEnum ---") +print(Status.PENDING) # pending +print(repr(Status.PENDING)) # + +# String operations (inherits str) +print(Status.PENDING == "pending") # True +print(Status.RUNNING.upper()) # RUNNING +print(Status.RUNNING.name) # RUNNING +print(Status.RUNNING.value.upper()) # RUNNING + +# Preserving str type inheritance +print(isinstance(Status.PENDING, str)) # True +print(Status.dump()) # StrEnum('Status', {'DONE': 'done', 'RUNNING': 'running', 'PENDING': 'pending'}) + + +# ============================================================================== +# StrEnum Functional API +# ============================================================================== +HttpCode = StrEnum("HttpCode", "OK NOT_FOUND INTERNAL_ERROR") + +print("\n--- StrEnum Functional API ---") +print(HttpCode.OK.value) # OK +print(HttpCode.NOT_FOUND == "NOT_FOUND") # True +print(HttpCode.dump()) # StrEnum('HttpCode', {'NOT_FOUND': 'NOT_FOUND', 'OK': 'OK', 'INTERNAL_ERROR': 'INTERNAL_ERROR'}) diff --git a/python-stdlib/enum/manifest.py b/python-stdlib/enum/manifest.py index 137ec22aa..76d44eb3c 100644 --- a/python-stdlib/enum/manifest.py +++ b/python-stdlib/enum/manifest.py @@ -1,3 +1,3 @@ -metadata(version="1.3.0") +metadata(version="1.4.0") module("enum.py") diff --git a/python-stdlib/enum/test_enum.py b/python-stdlib/enum/test_enum.py index caa8bee64..29a469add 100644 --- a/python-stdlib/enum/test_enum.py +++ b/python-stdlib/enum/test_enum.py @@ -1,13 +1,12 @@ # test_enum.py -# version="1.3.0" +# version="1.4.0" import unittest -from enum import Enum +from enum import Enum, IntEnum, StrEnum class TestEnum(unittest.TestCase): def setUp(self): - # Standard Class Definitions for testing class Color(Enum): RED = 1 GREEN = 2 @@ -27,80 +26,75 @@ def test_class_attributes(self): """Test basic access to Enum members, names, and values.""" self.assertEqual(self.color.RED.value, 1) self.assertEqual(self.color.RED.name, 'RED') - self.assertEqual(str(type(self.color.RED)), "") - self.assertEqual(type(self.color.RED).__name__, 'EnumValue') - EnumValue = type(self.color.RED) - self.assertIsInstance(self.color.RED, EnumValue) + self.assertEqual(str(type(self.color.RED)), "") + self.assertEqual(type(self.color.RED).__name__, 'Color.RED') + + self.assertIsInstance(self.color.RED, type(self.color.RED)) self.assertEqual(self.status.IDLE.value, 0) + def test_getitem_access(self): + """Test item access via square brackets.""" + self.assertEqual(self.ColorClass()["RED"], self.color.RED) + self.assertEqual(self.ColorClass()["BLUE"], self.color.BLUE) + + with self.assertRaises(KeyError): + _ = self.ColorClass()["YELLOW"] + def test_comparison(self): - """Test equality between EnumValues and raw values.""" + """Test equality between Enum members and raw values.""" self.assertTrue(self.color.RED == 1) self.assertFalse(self.color.RED == 2) self.assertEqual(self.color.RED, self.color.RED) - # Verify object does not equal value of different type self.assertFalse(self.color.RED == "1") - def test_call_reverse_lookup(self): - """Test reverse lookup via instance call Color(1) -> RED.""" - result = self.color(1) - self.assertEqual(result.name, 'RED') - self.assertEqual(result.value, 1) + def test_reverse_lookup(self): + """Test reverse lookup by value, name, and member instance.""" + # Lookup by value + self.assertEqual(self.color(1), self.color.RED) + self.assertEqual(self.StatusClass(1), self.StatusClass.RUNNING) - # Test lookup by name string - result = self.color('RED') - self.assertEqual(result.name, 'RED') + # Lookup by name + self.assertEqual(self.color('RED'), self.color.RED) - result = self.color(self.ColorClass.RED) - self.assertEqual(result, self.ColorClass.RED) + # Lookup by instance + self.assertEqual(self.color(self.ColorClass.RED), self.ColorClass.RED) - with self.assertRaises(AttributeError): + # Invalid lookup + with self.assertRaises(ValueError): self.color(999) - - def test_constructor_reverse_lookup(self): - """Test reverse lookup via class constructor Status(1) -> RUNNING.""" - member = self.StatusClass(1) - self.assertEqual(member.name, "RUNNING") - self.assertEqual(member, self.StatusClass.RUNNING) - - with self.assertRaises(AttributeError): + with self.assertRaises(ValueError): self.StatusClass(999) - def test_is_value(self): - """Test utility method is_value().""" - self.assertTrue(self.color.is_value(1)) - self.assertTrue(self.color.is_value(3)) - self.assertFalse(self.color.is_value(5)) - def test_iteration(self): """Test iteration over Enum instance.""" members = list(self.color) names = [m.name for m in members] self.assertEqual(len(members), 3) - self.assertIn('RED', names) - self.assertIn('GREEN', names) - self.assertIn('BLUE', names) + self.assertEqual(names, ['RED', 'GREEN', 'BLUE']) def test_immutability(self): - """Verify EnumValue and Enum instance are immutable after init.""" - # EnumValue attribute protection + """Verify Enum member and Enum instance are immutable after init.""" with self.assertRaises(AttributeError): self.color.RED.value = 10 - # Enum instance attribute protection (static) with self.assertRaises(AttributeError): self.color.NEW_MEMBER = 4 def test_deletion_prevention(self): - """Verify that members cannot be deleted.""" + """Verify that members cannot be deleted from instance or class.""" with self.assertRaises(AttributeError): del self.color.RED - def test_len_and_list(self): - """Test __len__ and list() utility methods.""" +# with self.assertRaises(AttributeError): +# delattr(self.ColorClass, "RED") + + def test_len_and_items(self): + """Test __len__ and __members__ dictionary.""" self.assertEqual(len(self.color), 3) - members_list = self.color.list() - self.assertEqual(members_list, [self.color.RED, self.color.GREEN, self.color.BLUE]) + self.assertEqual( + self.color.__members__, + {self.color.RED: 1, self.color.GREEN: 2, self.color.BLUE: 3} + ) def test_call_method(self): """Test calling EnumValue as a function to get its value.""" @@ -109,32 +103,62 @@ def test_call_method(self): def test_functional_api(self): """Test dynamic Enum creation using the Functional API.""" - # Logic restored from commented out sections - State = Enum(name='State', names={'ON': 1, 'OFF': 0}) + State = Enum(value='State', names={'ON': 1, 'OFF': 0}) self.assertTrue(hasattr(State, 'ON')) self.assertEqual(State.ON.value, 1) self.assertEqual(State.OFF.name, 'OFF') + StrParsed = Enum('StrParsed', 'A B C', start=10) + self.assertEqual(StrParsed.A.value, 10) + self.assertEqual(StrParsed.C.value, 12) + + ListParsed = Enum('ListParsed', ['X', 'Y']) + self.assertEqual(ListParsed.X.value, 1) + self.assertEqual(ListParsed.Y.value, 2) + + def test_int_enum_and_str_enum(self): + """Verify IntEnum and StrEnum type restrictions.""" + class Number(IntEnum): + ONE = 1 + TWO = 2 + + Number() + self.assertEqual(Number.ONE.value, 1) + + with self.assertRaises(TypeError): + class InvalidInt(IntEnum): + ONE = 1 + BAD = "not_an_int" + InvalidInt() + + class Greeting(StrEnum): + HI = "hello" + + Greeting() + self.assertEqual(Greeting.HI.value, "hello") + + with self.assertRaises(TypeError): + class InvalidStr(StrEnum): + HI = "hello" + BAD = 123 + InvalidStr() + + def test_str_and_repr(self): + """Test string representations for Enum members.""" + self.assertEqual(str(self.color.RED), "Color.RED") + self.assertEqual(repr(self.color.RED), "") + def test_serialization_repr_eval(self): - """Verify eval(repr(obj)) restores the Enum correctly.""" - # Test standard instance - c_repr = repr(self.color) - c_restored = eval(c_repr) - # Check equality of members - self.assertEqual(self.color.list(), c_restored.list()) - self.assertEqual(type(self.color).__name__, type(c_restored).__name__) - - # Test functional instance - s_dynamic = Enum(name='StatusFunc', names={'START': 1, 'STOP': 0}) - s_repr = repr(s_dynamic) - s_restored = eval(s_repr) - self.assertEqual(s_dynamic.list(), s_restored.list()) - - # Test functional instance - s_dynamic = Enum(name='State', names={'ON': 1, 'OFF': 0}) - s_repr = repr(s_dynamic) - s_restored = eval(s_repr) - self.assertEqual(s_dynamic.list(), s_restored.list()) + """Verify eval(cls.dump()) restores the Enum correctly.""" + c_dump = self.color.dump() + c_restored = eval(c_dump) + self.assertEqual(self.color.__members__, c_restored.__members__) + self.assertEqual(type(self.color).__name__, c_restored.__name__) + + s_dynamic = Enum(value='StatusFunc', names={'START': 1, 'STOP': 0}) + s_dump = s_dynamic.dump() + s_restored = eval(s_dump) + self.assertEqual(s_dynamic.__members__, s_restored.__members__) if __name__ == '__main__': From ff707975ab7454c59a4be944784f618c90b5f2f8 Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Wed, 7 Oct 2026 00:08:43 +0300 Subject: [PATCH 3/5] python-stdlib\enum: Add mini Enum implementation. Signed-off-by: Ihor Nehrutsa --- python-stdlib/enum/enum_mini.py | 116 ++++++++++++++++++++++++++ python-stdlib/enum/enum_mini_usage.py | 107 ++++++++++++++++++++++++ python-stdlib/enum/test_enum_mini.py | 108 ++++++++++++++++++++++++ tools/ci.sh | 1 + 4 files changed, 332 insertions(+) create mode 100644 python-stdlib/enum/enum_mini.py create mode 100644 python-stdlib/enum/enum_mini_usage.py create mode 100644 python-stdlib/enum/test_enum_mini.py diff --git a/python-stdlib/enum/enum_mini.py b/python-stdlib/enum/enum_mini.py new file mode 100644 index 000000000..2693ad079 --- /dev/null +++ b/python-stdlib/enum/enum_mini.py @@ -0,0 +1,116 @@ +# enum_mini.py +# Minimalist Enum implementation without metaclasses +# version="1.4.0" + +# ============================================================================== +# Variable & Abbreviation Definitions: +# ============================================================================== +# Functions & Helper Methods: +# _c(e, n, v) -> create_enum_item: Helper function to construct a typed enum instance. +# _i() -> items: Classmethod to lazily initialize and return the __members__ dict. +# a(s, k, v) -> __setattr__: Inner setter guard raising AttributeError on enum items. +# +# Helper Arguments & Local Variables: +# e - enum_name / enum_item: Class name string or instance of an enum item. +# n - name: String representing the enum member key (e.g., "RED"). +# v - value / member value: Value assigned to or searched within the enum member. +# c - class / new_class: Dynamically created Enum subclass via `type()`. +# k, v - key, value: Key-value pair during iteration over attributes or dictionaries. +# i - item: Individual enum member instance during iteration. +# __members__ - Dictionary mapping enum instances to values. +# s, k, o - self, key/attribute_name, other_enum (standard short parameters). +# ============================================================================== + + +def _c(e, n, v): + # Inner guard to prevent modification of enum item attributes + def a(s, k, v): + raise AttributeError("readonly") + + # Safely extract class name string if a class object or instance was passed + e = getattr(e, "__name__", str(e)) + + # Dynamically create a subclass inheriting from type(v) (e.g., int, str, float) + return type( + f"{e}.{n}", + (type(v),), + { + "name": n, + "value": v, + "__str__": lambda s: f"{e}.{n}", + "__repr__": lambda s: f"<{e}.{n}: {v!r}>", + "__call__": lambda s: v, + "__setattr__": a, + }, + )(v) + + +class Enum: + def __new__(cls, value=None, names=None): + # Functional API: Enum('Name', {'KEY': 'val'}) + if value is not None and names is not None: + if not isinstance(names, dict): + raise TypeError("names must be dict") + + # Construct and return a new Enum subclass + c = type(str(value), (cls,), {"__members__": {}}) + for k, v in names.items(): + e = _c(value, k, v) + setattr(c, k, e) + c.__members__[e] = v + return c + + if cls is not Enum: + # Lazy initialization of subclass members + cls._i() + + # Lookup existing member by value or name using call: e.g., Color(1) or Color("RED") + if value is not None: + return cls()(value) + + return super().__new__(cls) + + @classmethod + def __contains__(cls, v): + # Lookup member by value or name + for i in cls._i(): + if i.value == v or i.name == v: + return True + return False + + @classmethod + def __call__(cls, v): + # Lookup member by value or name + for i in cls._i(): + if i.value == v or i.name == v: + return i + raise ValueError(f"invalid: {v!r}") + + # Equality checks if both classes are Enums and have identical __members__ dicts + # __eq__ = classmethod(lambda cls, o: isinstance(o, type) and issubclass(o, Enum) and cls._i() == o._i()) + __eq__ = classmethod(lambda cls, o: getattr(o, "_i", None) and cls._i() == o._i()) + # Iteration yields enum member instances (keys of __members__) + __iter__ = classmethod(lambda cls: iter(cls._i())) + __len__ = classmethod(lambda cls: len(cls._i())) + __str__ = __repr__ = classmethod(lambda cls: f"") + + @classmethod + def __setattr__(cls, k, v): + raise AttributeError("readonly") + + @classmethod + def __delattr__(cls, k): + raise AttributeError("readonly") + + @classmethod + def _i(cls): + # Initialize and return dictionary mapping enum instances to their values + if "__members__" not in cls.__dict__: + # Convert raw class attributes into typed enum instances + cls.__members__ = {} + for k, v in list(cls.__dict__.items()): + if not k.startswith("_") and not callable(v): + e = _c(cls.__name__, k, v) + setattr(cls, k, e) + cls.__members__[e] = v + return cls.__members__ diff --git a/python-stdlib/enum/enum_mini_usage.py b/python-stdlib/enum/enum_mini_usage.py new file mode 100644 index 000000000..2c3fabca3 --- /dev/null +++ b/python-stdlib/enum/enum_mini_usage.py @@ -0,0 +1,107 @@ +# enum_mini_usage.py +# version="1.4.0" + +from enum_mini import Enum + +# ============================================================================== +# Usage Examples +# ============================================================================== + +print("--- Color(Enum) ---") + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +color = Color() # Trigger initialization + +# 1. Canonical output (str and repr) +print(Color.RED) # Color.RED +print(repr(Color.RED)) # + +# 2. Attribute access (name and value) +print(Color.RED.name) # RED +print(Color.RED.value) # 1 + +# 3. Equality and identity checks +print(Color.RED == Color.RED) # True +print(Color.RED is Color.RED) # True +print(Color.RED == 1) # True (like CPython IntEnum; CPython Enum returns False) + +# 4. Lookup by value or name (via call) +print(Color(2)) # Color.GREEN +print(Color("RED")) # Color.RED + +# 5. Length and iteration (order may vary) +print(len(Color())) # 3 +print([x for x in Color()]) # [, , ] + +# 6. Enum members mapping (order may vary) +print(Color.__members__) # {: 1, : 2, : 3} + +# 7. Immutability check +try: + Color.RED.value = 100 + 0 / 0 +except AttributeError as e: + print("Immutability test passed:", e) + +try: + color.RED = 100 + 0 / 0 +except AttributeError as e: + print("Immutability test passed:", e) + +try: + del color.RED + 0 / 0 +except AttributeError as e: + print("Immutability test passed:", e) + +# # test NOT passed +# try: +# del Color.RED +# 0 / 0 +# except AttributeError as e: +# print("Immutability test passed:", e) + +assert Color.RED == 1 + +# 8. Functional API: Dictionary +Load = Enum("Load", {"LOW": "low", "HIGH": "high"}) +print(Load.HIGH) # Load.HIGH +print(Load.HIGH.value) # high +print(Load.LOW == "low") # True (like CPython StrEnum; CPython Enum returns False) + + +print("\n--- Priority(Enum) - Math & Comparison ---") + +class Priority(Enum): + LOW = 1 + MEDIUM = 2 + HIGH = 3 + +Priority() + +# Arithmetic and numeric comparison (inherits int) +print(Priority.HIGH > 2) # True +print(Priority.LOW + 10) # 11 +print(isinstance(Priority.LOW, int)) # True + + +print("\n--- Status(Enum) - String Operations) ---") + +class Status(Enum): + PENDING = "pending" + RUNNING = "running" + DONE = "done" + +status = Status() + +# String operations (inherits str) +print(Status.PENDING == "pending") # True +print(Status.RUNNING.lower()) # running +print(Status.RUNNING.name.lower()) # running +print(Status.RUNNING.value.upper()) # RUNNING diff --git a/python-stdlib/enum/test_enum_mini.py b/python-stdlib/enum/test_enum_mini.py new file mode 100644 index 000000000..419b92521 --- /dev/null +++ b/python-stdlib/enum/test_enum_mini.py @@ -0,0 +1,108 @@ +import unittest +from enum_mini import Enum + +# ------------------------------------------------------------------------------ +# Test Sample Enum Classes +# ------------------------------------------------------------------------------ +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +class Status(Enum): + PENDING = "pending" + APPROVED = "approved" + REJECTED = "rejected" + + +Color() +Status() + + +class TestEnumMini(unittest.TestCase): + + # ========================================================================== + # Declarative Class API & String Representation + # ========================================================================== + def test_class_attributes_and_repr(self): + """Verify member values, names, str, and repr for int and str enums.""" + self.assertEqual(Color.RED.value, 1) + self.assertEqual(Color.RED.name, "RED") + self.assertEqual(str(Color.RED), "Color.RED") + self.assertEqual(repr(Color.RED), "") + + self.assertEqual(Status.PENDING.value, "pending") + self.assertEqual(repr(Status.PENDING), "") + + # ========================================================================== + # Member Lookup & Calling + # ========================================================================== + def test_lookup_by_value_and_name(self): + """Verify member lookup by value or name via Enum call.""" + self.assertIs(Color(1), Color.RED) + self.assertIs(Color("RED"), Color.RED) + self.assertIs(Status("pending"), Status.PENDING) + + # Test calling member instance returns raw value + self.assertEqual(Color.RED(), 1) + + def test_lookup_invalid_value(self): + """Verify ValueError is raised on non-existent value/name.""" + with self.assertRaises(ValueError): + Color(999) + with self.assertRaises(ValueError): + Color("YELLOW") + + # ========================================================================== + # Functional API + # ========================================================================== + def test_functional_api_dict(self): + """Create Enum via dict mapping.""" + Size = Enum("Size", {"S": 36, "M": 38, "L": 40}) + self.assertEqual(Size.S.value, 36) + self.assertEqual(Size.L.value, 40) + + # ========================================================================== + # Iteration, Length, and Members Mapping + # ========================================================================== + def test_iteration_and_len(self): + """Verify iteration, len(), and __members__ mapping.""" + self.assertEqual(len(Color()), 3) + self.assertEqual(list(Color()), [Color.RED, Color.GREEN, Color.BLUE]) + + items_dict = Color.__members__ + self.assertIn(Color.RED, items_dict) + self.assertEqual(items_dict[Color.RED], 1) + + # ========================================================================== + # Equality & Cross-Enum Comparison + # ========================================================================== + def test_equality(self): + """Verify equality operators for members and cross-enum items.""" + self.assertEqual(Color.RED, Color.RED) + self.assertNotEqual(Color.RED, Color.GREEN) + + OtherColor = Enum("OtherColor", {"RED": 1}) + self.assertEqual(Color.RED, OtherColor.RED) + + # ========================================================================== + # Protection & Mutability Limits + # ========================================================================== + def test_attribute_setting_protection(self): + """Ensure attribute modification/deletion is blocked on items and class instances.""" + with self.assertRaises(AttributeError): + Color.RED.value = 100 + + with self.assertRaises(AttributeError): + Color.RED.new_attr = "test" + + with self.assertRaises(AttributeError): + Color().NEW_MEMBER = 4 + + with self.assertRaises(AttributeError): + del Color().RED + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tools/ci.sh b/tools/ci.sh index 8e6935eb6..a31bd3e83 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -67,6 +67,7 @@ function ci_package_tests_run { python-stdlib/collections-defaultdict/test_defaultdict.py \ python-stdlib/curses.ascii/test_curses_ascii.py \ python-stdlib/enum/test_enum.py \ + python-stdlib/enum/test_enum_mini.py \ python-stdlib/functools/test_partial.py \ python-stdlib/functools/test_reduce.py \ python-stdlib/functools/test_cache.py \ From d3fead8d3efcf40dfb179a5f31878889919d4b0d Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Sat, 10 Oct 2026 09:20:57 +0300 Subject: [PATCH 4/5] enum: Add CPython and MicroPython demo. Signed-off-by: Ihor Nehrutsa --- python-stdlib/enum/CPy/CPy.py | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 python-stdlib/enum/CPy/CPy.py diff --git a/python-stdlib/enum/CPy/CPy.py b/python-stdlib/enum/CPy/CPy.py new file mode 100644 index 000000000..81559ac6a --- /dev/null +++ b/python-stdlib/enum/CPy/CPy.py @@ -0,0 +1,76 @@ +from enum import Enum + +is_micropython = False +try: + from machine import reset + is_micropython = True +except Exception: + pass + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +if is_micropython: + Color() # Trigger initialization + +print("\n--- Basic Lookups ---") +print(Color.RED) # Color.RED +print(Color.RED.name) # RED +print(Color.RED.value) # 1 +print(repr(Color.RED)) # +print(Color.__contains__(1)) # True +print(Color.__contains__(11)) # False +print(Color.__contains__(Color.RED)) # True +print(Color.RED.value == 1) # True +print(Color.RED.value + 10) # 11 +print(Color(1) is Color.RED) # True +print() + +if is_micropython: + print(Color()) # + print(repr(Color())) # + print(len(Color())) # 3 + print(Color("RED")) # Color.RED + print(Color("RED") is Color.RED) # True + print(Color.RED == 1) # True (like IntEnum) + print(list(Color())) # ['RED', 'GREEN', 'BLUE'] + print(list(Color.__members__)) # ['RED', 'GREEN', 'BLUE'] +else: + print(Color) # + print(repr(Color)) # + print(len(Color)) # 3 + print(Color["RED"]) # Color.RED + print(Color["RED"] is Color.RED) # True + print(Color.RED == 1) # False + print(list(Color)) # [, , ] + print(list(Color.__members__)) # ['RED', 'GREEN', 'BLUE'] + +print("\n--- Immutability Tests ---") +try: + Color.RED.value = 99 + 0 / 0 +except AttributeError: + print("Protected: Cannot reassign class attribute") + +try: + if is_micropython: + Color().RED = 99 + else: + Color.RED = 99 + 0 / 0 +except AttributeError: + print("Protected: Cannot reassign class attribute") + +try: + if is_micropython: + del Color().RED + else: + del Color.RED + 0 / 0 +except AttributeError: + print("Protected: Cannot delete class attribute") + +assert Color.RED.value == 1 From c3b5aaeb61c33ee13c70a6d3dfcd95a8b185e297 Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Sun, 18 Oct 2026 01:26:59 +0300 Subject: [PATCH 5/5] python-stdlib\enum: Bump to version="1.5.0". The builder hook allows using an Enum without lazy initialization. Signed-off-by: Ihor Nehrutsa --- python-stdlib/enum/CPy/CPy.py | 101 ++++++++++++++++---------- python-stdlib/enum/README.md | 100 +++++++++++++++++-------- python-stdlib/enum/enum.py | 73 +++++++++++++------ python-stdlib/enum/enum_check.py | 62 ++++++++-------- python-stdlib/enum/enum_mini.py | 64 +++++++++++----- python-stdlib/enum/enum_mini_usage.py | 21 ++---- python-stdlib/enum/enum_usage.py | 39 +++++----- python-stdlib/enum/test_enum.py | 2 +- python-stdlib/enum/test_enum_mini.py | 15 ++-- 9 files changed, 297 insertions(+), 180 deletions(-) diff --git a/python-stdlib/enum/CPy/CPy.py b/python-stdlib/enum/CPy/CPy.py index 81559ac6a..dedc44053 100644 --- a/python-stdlib/enum/CPy/CPy.py +++ b/python-stdlib/enum/CPy/CPy.py @@ -1,11 +1,4 @@ -from enum import Enum - -is_micropython = False -try: - from machine import reset - is_micropython = True -except Exception: - pass +from enum import Enum, StrEnum, IntEnum class Color(Enum): @@ -13,11 +6,15 @@ class Color(Enum): GREEN = 2 BLUE = 3 -if is_micropython: - Color() # Trigger initialization -print("\n--- Basic Lookups ---") +print("\n--- Basic Lookups --- # CPython # MicroPython output") +print(Color) # +print(type(Color)) # # +print(Color.__members__) # {'RED': , 'GREEN': , 'BLUE': } # {: 1, : 2, : 3} +print(list(Color)) # [, , ] + print(Color.RED) # Color.RED +print(type(Color.RED)) # # print(Color.RED.name) # RED print(Color.RED.value) # 1 print(repr(Color.RED)) # @@ -29,24 +26,12 @@ class Color(Enum): print(Color(1) is Color.RED) # True print() -if is_micropython: - print(Color()) # - print(repr(Color())) # - print(len(Color())) # 3 - print(Color("RED")) # Color.RED - print(Color("RED") is Color.RED) # True - print(Color.RED == 1) # True (like IntEnum) - print(list(Color())) # ['RED', 'GREEN', 'BLUE'] - print(list(Color.__members__)) # ['RED', 'GREEN', 'BLUE'] -else: - print(Color) # - print(repr(Color)) # - print(len(Color)) # 3 - print(Color["RED"]) # Color.RED - print(Color["RED"] is Color.RED) # True - print(Color.RED == 1) # False - print(list(Color)) # [, , ] - print(list(Color.__members__)) # ['RED', 'GREEN', 'BLUE'] +print(Color) # +print(repr(Color)) # +print(len(Color)) # 3 +print(Color["RED"]) # Color.RED +print(Color["RED"] is Color.RED) # True +print(Color.RED == 1) # False # True print("\n--- Immutability Tests ---") try: @@ -56,21 +41,63 @@ class Color(Enum): print("Protected: Cannot reassign class attribute") try: - if is_micropython: - Color().RED = 99 - else: - Color.RED = 99 + Color.RED = 99 0 / 0 except AttributeError: print("Protected: Cannot reassign class attribute") try: - if is_micropython: - del Color().RED - else: - del Color.RED + del Color.RED 0 / 0 except AttributeError: print("Protected: Cannot delete class attribute") +try: + Color.RED.custom = 5 + print("NOT Protected: Set attribute on enum item") # NOT Protected: Set attribute on enum item +except AttributeError: + print("Protected: Cannot set attribute on enum item") # Protected: Cannot set attribute on enum item + assert Color.RED.value == 1 + +print("\n--- Functional API & Extensions ---") +try: + DynCoord = Enum("DynCoord", {"X": 10, "Y": 20}) + print(DynCoord) # + print(type(DynCoord)) # # + print(DynCoord.__members__) # {'X': , 'Y': } # {: 20, : 10} + print(list(DynCoord)) # [, ] # [, ] + print(DynCoord.X.value) # 10 + +except Exception as e: + print_exception(e) + print(f"Functional dict API error: {e}") + +print() +try: + DynStr = StrEnum("DynStr", "A B C") + print(DynStr) # + print(type(DynStr)) # # + print(DynStr.__members__) # {'A': , 'B': , 'C': } # {: 'a', : 'c', : 'b'} + print(list(DynStr)) # [, , ] # [, , ] + print(DynStr.A.value) # a +except Exception as e: + print(f"Functional string API not supported or error: {e}") + +try: + class Colors(Color): + YELLOW = 'yellow' +except TypeError as e: + print(f"TypeError: {e}") + +if hasattr(Color, "dump"): + print("\n--- Dump & Restore (if available) ---") + d_str = Color.dump() + print("Dump:", d_str) + Restored = eval(d_str) # Dump: Enum('Color', {'BLUE': 3, 'RED': 1, 'GREEN': 2}) + print(Restored) # + print(type(Restored)) # + print(Restored.__members__) # {: 1, : 2, : 3} + print(list(Restored)) # [, , ] + print("Restored equality:", Restored == Color) # True + print("Restored equality:", Restored.RED.value == Color.RED.value) # True diff --git a/python-stdlib/enum/README.md b/python-stdlib/enum/README.md index 662c8bace..94669e61c 100644 --- a/python-stdlib/enum/README.md +++ b/python-stdlib/enum/README.md @@ -1,61 +1,103 @@ # Lightweight Enum -This package provides two lightweight enumeration implementations for -MicroPython: - -* `enum.py` provides `Enum`, `IntEnum`, and `StrEnum`, class declarations, - several forms of the functional API, name/value lookup, iteration, and - `.dump()`. -* `enum_mini.py` provides a smaller `Enum` implementation for applications - that only need basic members, dictionary-based functional creation, lookup, - and iteration. - -Both implementations avoid metaclasses. Members declared in a class are -initialized lazily when the enum is first instantiated or looked up; members -created through the functional API are initialized immediately. Members inherit -from the type of their value, so integer-valued members behave like integers -and string-valued members behave like strings. Consequently, equality with a -raw value (and between members from different enum classes with the same value) -is possible. This differs from Python's standard-library `Enum`. +This package provides two lightweight enumeration implementations for MicroPython: + +* `enum.py` provides `Enum`, `IntEnum`, and `StrEnum`, multiple forms of the + functional API, lookup by name or value, iteration, and `.dump()`. +* `enum_mini.py` provides a smaller `Enum` implementation with class-based + members, dictionary-based functional creation, lookup by name or value, and + iteration. + +Both implementations avoid metaclasses. They use a `builtins.__build_class__` +hook to turn classes derived from their `Enum` base into initialized enum +containers. Members are created lazily by `_i()` when needed; functional-API +enums are initialized during creation. Each member is a dynamically created +subclass of its raw value's type (for example, `int` or `str`), rather than an +instance of the enum container class. As a result, members can compare equal to +their raw values, and members from different enum containers can compare equal +when their values match. + +**Important:** Both modules install a process-wide class-building hook when +imported. This affects class creation for classes derived from the relevant +`Enum` base, so use these modules with care in applications that define many +classes or mix implementations. + +## Quick start: `enum.py` + +```python +from enum import Enum, IntEnum, StrEnum + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +print(Color.RED) # Color.RED +print(Color.RED.name) # RED +print(Color.RED.value) # 1 + +print(Color(2)) # Color.GREEN: lookup by value +print(Color("RED")) # Color.RED: lookup by name +print(Color["RED"]) # Color.RED: lookup by name +print(list(Color)) # Enum members +print(len(Color)) # 3 +print(Color.dump()) # Reconstructable expression +``` + +`enum.py` also provides constrained value types: + +```python +class Priority(IntEnum): + LOW = 1 + HIGH = 2 + +class Status(StrEnum): + PENDING = "pending" + DONE = "done" + +print(Priority.HIGH > 1) # True +print(isinstance(Priority.LOW, int)) # True +print(Status.PENDING.name.upper()) # PENDING +print(isinstance(Status.DONE, str)) # True +``` + +`IntEnum` values must be integers and `StrEnum` values must be strings. +Invalid member values raise `TypeError`. ### Standard Behavior (Compatible) - ✅ Member access: `Color.RED`, `.name`, `.value` -- ✅ Member iteration: `for item in Color()` +- ✅ Member iteration: `for item in Color` - ✅ `IntEnum` and `StrEnum` types with strict value checking - ✅ Immutability: members cannot be modified after creation - ✅ Functional API: `Enum('Name', {'KEY': value, ...})` ### Non-Standard Behavior Key differences from CPython: -- **Explicit Lazy Initialization**: MicroPython implementations rely on class/instance evaluation (e.g., `Color()`) to trigger lazy member initialization when standard class bodies aren't fully traversed upfront. - **Name-based lookup via call**: `Color("RED")` works for both name and value lookup (CPython standard call only supports value lookup). - **Value equality**: `Color.RED == 1` is `True` (only true for `IntEnum` in CPython). - **Custom methods**: `.dump()` does not exist in stdlib `Enum`. - **Callable members**: `Color.RED()` returns the value (not supported in CPython). -- **Instance `__len__`**: `len(Color())` works; CPython enums are not containers. +- **Instance `__len__`**: `len(Color)` works; CPython enums are not containers. - **Serialization**: `dump()` for eval-based reconstruction is MicroPython-specific. -### Migration Notes -If you're porting code from CPython `enum`: -- Use `Color()` MicroPython requires explicit initialization. -- Replace `Color['RED']` with `Color('RED')`. - ## Comparing the implementations | Feature | CPython | `enum.py` | `enum_mini.py` | | --- | --- | --- | --- | | Enum types | `Enum`, `IntEnum`, `StrEnum` | `Enum`, `IntEnum`, `StrEnum` | `Enum` | -| Lookup by name | `Color["RED"]` | `Color("RED")` or `Color()["RED"]` | `Color("RED")` | +| Lookup by name | `Color["RED"]` | `Color("RED")` or `Color["RED"]` | `Color("RED")` | | Lookup by value | `Color(1)` | `Color(1)` | `Color(1)` | -| Iteration | `for item in Color` | `for item in Color()` | `for item in Color()` | +| Iteration | `for item in Color` | `for item in Color` | `for item in Color` | | Iteration in `__members__` | Yes | Yes | Yes | -| `__members__` keys and values | Names and members | Members and values | Members and values | +| `__members__` mapping | Name to member | Member to raw value | Member to raw value | | Functional API | Dict, names string, list/tuple, or iterable | Dict, names string, list/tuple, or iterable | Dictionary only | | `start` for generated integer values | Yes | Yes | No | +| Strict integer/string member types | `IntEnum` / `StrEnum` | `IntEnum` / `StrEnum` | Not provided | +| Length | Not provided | `len(Color)` | `len(Color)` | | `.dump()` | No | Yes | No | | Plain `Enum` member equals its raw value | No | Yes | Yes | | Member is an instance of its enum class | Yes | No | No | -| Calling the enum without arguments returns a container | No | Yes | Yes | +| Calling a member, e.g. `Color.RED()` | Not supported | Returns its raw value | Returns its raw value | ## Code diff --git a/python-stdlib/enum/enum.py b/python-stdlib/enum/enum.py index edfd42bde..e74e0f664 100644 --- a/python-stdlib/enum/enum.py +++ b/python-stdlib/enum/enum.py @@ -1,39 +1,43 @@ # enum.py # Enum implementation without metaclasses -# version="1.4.0" +# version="1.5.0" # ============================================================================== # Variable & Abbreviation Definitions: # ============================================================================== # Functions & Helper Methods: -# _c(e, n, v, v_t) -> create_enum_item: Helper function to construct a typed enum instance. +# _c(e, n, v, t) -> create_enum_item: Helper function to construct a typed enum instance. # _i() -> items: Classmethod to lazily initialize and return the __members__ dict. # a(s, k, v) -> __setattr__: Inner setter guard raising AttributeError on enum items. # # Helper Arguments & Local Variables: # e - enum_name / enum_item: Class name string or instance of an enum item. # n - name: String representing the enum member key (e.g., "RED"). -# v - value / member value: Value assigned to or searched within the enum member. +# v - value / member value: Value assigned to, searched within, or passed to lookup. # c - class / new_class: Dynamically created Enum subclass via `type()`. -# v_t - value_type: Target constraint type (`int`, `str`, or `None` for generic Enum). +# t - value_type: Target constraint type (`int`, `str`, or `None` for generic Enum). # l - names_list: List of parsed string keys from comma/space-separated inputs. # d - dict / mapping: Temporary dictionary for parsed enum members. # k, v - key, value: Key-value pair during iteration over attributes or dictionaries. # i - item: Individual enum member instance during iteration. # __members__ - Dictionary mapping enum member instances to their raw values. # s, k, o - self, key/attribute_name, other_enum (standard short parameters). +# f - function: The body function of the class being defined. +# b - bases: Tuple of base classes passed during class creation. +# kw - kwargs: Additional keyword arguments passed during class creation. +# cls - class object: Target class being created or initialized. # ============================================================================== -def _c(e, n, v, v_t=None): +def _c(e, n, v, t=None): # Inner guard to prevent modification of enum item attributes def a(s, k, v): raise AttributeError("cannot set attribute") # Type checks for IntEnum and StrEnum - if v_t is int and not isinstance(v, int): + if t is int and not isinstance(v, int): raise TypeError(f"IntEnum member {n!r} value must be int, got {type(v).__name__}") - elif v_t is str and not isinstance(v, str): + elif t is str and not isinstance(v, str): raise TypeError(f"StrEnum member {n!r} value must be str, got {type(v).__name__}") # Safely extract class name string if a class or object was passed @@ -46,7 +50,7 @@ def a(s, k, v): { "name": n, "value": v, - "__str__": lambda s: str(v) if v_t in (int, str) else f"{e}.{n}", + "__str__": lambda s: str(v) if t in (int, str) else f"{e}.{n}", "__repr__": lambda s: f"<{e}.{n}: {v!r}>", "__call__": lambda s: v, "__setattr__": a, @@ -60,7 +64,7 @@ class Enum: def __new__(cls, value=None, names=None, *, start=1): # Functional API: dynamic creation of a new Enum class if value is not None and names is not None: - is_str_enum = cls._v_t is str + is_str = cls._v_t is str # Parse 'names' parameter into a key-value dictionary if isinstance(names, dict): @@ -69,8 +73,8 @@ def __new__(cls, value=None, names=None, *, start=1): # Space or comma-separated string of member names l = names.replace(",", " ").split() d = ( - {k: k for k in l} - if is_str_enum + {k: k.lower() for k in l} + if is_str else {k: v for v, k in enumerate(l, start=start)} ) elif isinstance(names, (list, tuple)): @@ -80,23 +84,22 @@ def __new__(cls, value=None, names=None, *, start=1): else: d = ( {k: k for k in names} - if is_str_enum + if is_str else {k: v for v, k in enumerate(names, start=start)} ) else: # Other iterables (e.g., sets) d = ( {k: k for k in names} - if is_str_enum + if is_str else {k: v for v, k in enumerate(names, start=start)} ) # Construct and return a new Enum subclass - c = type(str(value), (cls,), {"__members__": {}}) + c = type(str(value), (cls,), {}) for k, v in d.items(): - e = _c(value, k, v, v_t=cls._v_t) - setattr(c, k, e) - c.__members__[e] = v + setattr(c, k, v) + c._i() return c if cls not in (Enum, IntEnum, StrEnum): @@ -118,7 +121,10 @@ def __contains__(cls, v): return False @classmethod - def __call__(cls, v): + def __call__(cls, v=None): + if v is None: + return cls() + # Lookup member by value or name for i in cls._i(): if i.value == v or i.name == v: @@ -127,15 +133,14 @@ def __call__(cls, v): @classmethod def __getitem__(cls, k): - # Instance-level container lookup: Color()["RED"] + # Instance-level container lookup: Color["RED"] for i in cls._i(): if i.name == k: return i raise KeyError(k) # Equality checks if both classes are Enums and have identical __members__ dicts - # __eq__ = classmethod(lambda cls, o: isinstance(o, type) and issubclass(o, Enum) and cls._i() == o._i()) - __eq__ = classmethod(lambda cls, o: getattr(o, "_i", None) and cls._i() == o._i()) + __eq__ = classmethod(lambda cls, o: getattr(o, "_i", False) and cls._i() == o._i()) # Iteration yields enum member instances (keys of __members__) __iter__ = classmethod(lambda cls: iter(cls._i())) __len__ = classmethod(lambda cls: len(cls._i())) @@ -168,7 +173,11 @@ def _i(cls): cls.__members__ = {} for k, v in list(cls.__dict__.items()): if not k.startswith("_") and not callable(v): - e = _c(cls.__name__, k, v, v_t=cls._v_t) + if isinstance(v, cls): + continue + if v in cls.__members__: + raise ValueError(f"duplicate value {v!r} in {cls.__name__}") + e = _c(cls.__name__, k, v, cls._v_t) setattr(cls, k, e) cls.__members__[e] = v return cls.__members__ @@ -180,3 +189,23 @@ class IntEnum(Enum): class StrEnum(Enum): _v_t = str + + +# Install builder hook +import builtins + +_SN = "__ENUM_STATE__" # _STATE_NAME +if getattr(builtins, _SN, None): + builtins.__ENUM_STATE__["base"] = Enum +else: + _orig = builtins.__build_class__ + + # def _build_class(f, n, *b, **kw): + def _bc(f, n, *b, **kw): + cls = _orig(f, n, *b, **kw) + return ( + cls() if any(isinstance(base, type) and issubclass(base, Enum) for base in b) else cls + ) + + builtins.__build_class__ = _bc + setattr(builtins, _SN, {"original": _orig, "base": Enum, "wrapper": _bc}) diff --git a/python-stdlib/enum/enum_check.py b/python-stdlib/enum/enum_check.py index fd62858f5..1db3ebe57 100644 --- a/python-stdlib/enum/enum_check.py +++ b/python-stdlib/enum/enum_check.py @@ -1,5 +1,5 @@ # enum_check.py -# version="1.4.0" +# version="1.5.0" from enum import Enum, IntEnum, StrEnum @@ -10,24 +10,24 @@ class Color(Enum): BLUE = 3 # Basic class-level property and method calls -print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, name={Color(1).name}") -print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()}") +print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, name={Color(1).name}") # RED: repr=, type=, name=RED +print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()}") # RED: name=RED, value=1, str=Color.RED, call=1 +print(Color.__members__) # {: 1, : 2, : 3} assert Color(1).value == 1 assert Color.BLUE.value >= Color.GREEN.value # Access members via instance indexing and call syntax -assert Color()["RED"] == Color.RED assert Color("RED") == Color.RED # Iteration over Enum members via class instance call -print("Members list:", [member for member in Color()]) -print("Names list:", [member.name for member in Color()]) -print("Values list:", [member.value for member in Color()]) +print("Members list:", [member for member in Color]) # Members list: [, , ] +print("Names list:", [member.name for member in Color]) # Names list: ['RED', 'GREEN', 'BLUE'] +print("Values list:", [member.value for member in Color]) # Values list: [1, 2, 3] print() # Instance-based interaction and comparison checks c = Color() -print(f"Enum c instance: {c}") +print(f"Enum c instance: {c}") # Enum c instance: assert c.RED.name == "RED" assert c.RED.value == 1 @@ -36,7 +36,7 @@ class Color(Enum): # Reverse Lookup via instance call o = c(1) -print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}") +print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}") # c(1) lookup object: Color.RED, name=RED, value=1 assert c(1).name == "RED" assert c(1).value == 1 assert c(1) == 1 @@ -46,7 +46,7 @@ class Color(Enum): Color(999) assert False, "Should have raised ValueError" except ValueError as e: - print(f"ValueError (Caught expected error): {e}\n") + print(f"ValueError (Caught expected error): {e}\n") # ValueError (Caught expected error): 999 is not a valid Color # --- Usage Example 2: Enum Operations & Comparisons --- @@ -58,8 +58,8 @@ class Status(Enum): # This simulates receiving a status value from hardware received_byte = 1 status = Status(received_byte) -print(f"Lookup check: Received {received_byte} -> {status}") -print(f"Enum length via class: {len(Status())}") +print(f"Lookup check: Received {received_byte} -> {status}") # Lookup check: Received 1 -> Status.RUNNING +print(f"Enum length via class: {len(Status())}") # Enum length via class: 3 assert status == received_byte assert status == Status.RUNNING @@ -67,7 +67,7 @@ class Status(Enum): assert status.value == received_byte # Test equality and arithmetic operations -print(f"Comparison check: {status} == 1 is {status == 1}") +print(f"Comparison check: {status} == 1 is {status == 1}") # Comparison check: Status.RUNNING == 1 is True assert status == 1 assert status != 0 assert status + 10 == 11 @@ -77,39 +77,39 @@ class Status(Enum): Status.RUNNING.value = 999 assert False, "Should have raised AttributeError" except AttributeError as e: - print(f"Immutability check: Passed (Cannot modify EnumValue): {e}\n") + print(f"Immutability check: Passed (Cannot modify EnumValue): {e}\n") # Immutability check: Passed (Cannot modify EnumValue): cannot set attribute # Iteration over Enum members print("Iteration check: ", end="") for m in Status(): print(f"{m.name}, ", end="") -print("-> Passed") +print("-> Passed") # Iteration check: IDLE, RUNNING, ERROR, -> Passed try: Status(999) assert False, "Should have raised ValueError" except ValueError as e: - print(f"ValueError (Invalid lookup check): Caught expected error -> {e}\n") + print(f"ValueError (Invalid lookup check): Caught expected error -> {e}\n") # ValueError (Invalid lookup check): Caught expected error -> 999 is not a valid Status # --- Example 3: Functional API, Serialization and Eval --- -print("--- Functional API and Eval Check ---") +print("--- Functional API and Eval Check ---") # --- Functional API and Eval Check --- c_dump = Color.dump() -print(f"Original Dump: {c_dump}") +print(f"Original Dump: {c_dump}") # Original Dump: Enum('Color', {'BLUE': 3, 'RED': 1, 'GREEN': 2}) c2 = eval(c_dump) -print(f"Restored Class Dump: {c2.dump()}") +print(f"Restored Dump: {c2.dump()}") # Restored Dump: Enum('Color', {'BLUE': 3, 'RED': 1, 'GREEN': 2}) # Verify structural equality between restored and original class items -print(Color.__members__) -print(c2.__members__) +print(Color.__members__) # {: 1, : 2, : 3} +print(c2.__members__) # {: 1, : 2, : 3} assert Color.__members__ == c2.__members__ -print("Objects are equal: True") +print("Objects are equal: True") # Objects are equal: True # Dynamic class creation via functional API with eval() state = eval("Enum('State', {'ON':1, 'OFF':2})") -print(f"Functional Enum class (state): {state}") -print(f"Type: {type(state)}") +print(f"Functional Enum class (state): {state}") # Functional Enum class (state): +print(f"Type: {type(state)}") # Type: assert state.ON == 1 assert state.ON.name == "ON" assert state.ON > 0 @@ -123,12 +123,12 @@ class HttpMethod(Enum): DELETE = "DELETE" api_call = HttpMethod() -print(f"Member with string value: {api_call.GET}") +print(f"Member with string value: {api_call.GET}") # Member with string value: HttpMethod.GET assert api_call.GET == "GET" # Lookup by raw value string and member name string via instance invocation -print(f"Lookup by value 'POST': {api_call('POST')}") -print(f"Lookup by name 'DELETE': {api_call('DELETE')}") +print(f"Lookup by value 'POST': {api_call('POST')}") # Lookup by value 'POST': HttpMethod.POST +print(f"Lookup by name 'DELETE': {api_call('DELETE')}") # Lookup by name 'DELETE': HttpMethod.DELETE assert api_call("GET").name == "GET" @@ -138,7 +138,7 @@ class Empty(Enum): pass empty = Empty() -print(f"Empty Enum items: {empty.__members__}") +print(f"Empty Enum items: {empty.__members__}") # Empty Enum items: {} assert len(empty) == 0 @@ -150,7 +150,7 @@ class Empty(Enum): dump_str = complex_enum.dump() restored = eval(dump_str) -print(f"Restored Functional Enum: {restored.dump()}") +print(f"Restored Functional Enum: {restored.dump()}") # Restored Functional Enum: Enum('Config', {'TIMEOUT_SEC': 30, 'MAX_RETRY': 5}) assert restored.MAX_RETRY == 5 assert restored.__name__ == 'Config' @@ -161,12 +161,12 @@ class Empty(Enum): api_call.NEW_METHOD = "PATCH" assert False, "Should have raised AttributeError" except AttributeError as e: - print(f"Caught expected mutation error: {e}") + print(f"Caught expected mutation error: {e}") # Caught expected mutation error: cannot set attribute try: del api_call.GET assert False, "Should have raised AttributeError" except AttributeError as e: - print(f"Caught expected deletion error: {e}") + print(f"Caught expected deletion error: {e}") # Caught expected deletion error: cannot delete attribute print("\nAll tests passed successfully!") diff --git a/python-stdlib/enum/enum_mini.py b/python-stdlib/enum/enum_mini.py index 2693ad079..5c35840d1 100644 --- a/python-stdlib/enum/enum_mini.py +++ b/python-stdlib/enum/enum_mini.py @@ -1,6 +1,6 @@ # enum_mini.py # Minimalist Enum implementation without metaclasses -# version="1.4.0" +# version="1.5.0" # ============================================================================== # Variable & Abbreviation Definitions: @@ -13,12 +13,19 @@ # Helper Arguments & Local Variables: # e - enum_name / enum_item: Class name string or instance of an enum item. # n - name: String representing the enum member key (e.g., "RED"). -# v - value / member value: Value assigned to or searched within the enum member. +# v - value / member value: Value assigned to, searched within, or passed to lookup. # c - class / new_class: Dynamically created Enum subclass via `type()`. +# t - value_type: Target constraint type (`int`, `str`, or `None` for generic Enum). +# l - names_list: List of parsed string keys from comma/space-separated inputs. +# d - dict / mapping: Temporary dictionary for parsed enum members. # k, v - key, value: Key-value pair during iteration over attributes or dictionaries. # i - item: Individual enum member instance during iteration. -# __members__ - Dictionary mapping enum instances to values. +# __members__ - Dictionary mapping enum member instances to their raw values. # s, k, o - self, key/attribute_name, other_enum (standard short parameters). +# f - function: The body function of the class being defined. +# b - bases: Tuple of base classes passed during class creation. +# kw - kwargs: Additional keyword arguments passed during class creation. +# cls - class object: Target class being created or initialized. # ============================================================================== @@ -27,10 +34,10 @@ def _c(e, n, v): def a(s, k, v): raise AttributeError("readonly") - # Safely extract class name string if a class object or instance was passed + # Safely extract class name string if a class or object was passed e = getattr(e, "__name__", str(e)) - # Dynamically create a subclass inheriting from type(v) (e.g., int, str, float) + # Create dynamic subclass inheriting the value's type (int, str, float etc.) return type( f"{e}.{n}", (type(v),), @@ -50,21 +57,20 @@ def __new__(cls, value=None, names=None): # Functional API: Enum('Name', {'KEY': 'val'}) if value is not None and names is not None: if not isinstance(names, dict): - raise TypeError("names must be dict") + raise TypeError("must be dict") # Construct and return a new Enum subclass - c = type(str(value), (cls,), {"__members__": {}}) + c = type(str(value), (cls,), {}) for k, v in names.items(): - e = _c(value, k, v) - setattr(c, k, e) - c.__members__[e] = v + setattr(c, k, v) + c._i() return c if cls is not Enum: # Lazy initialization of subclass members cls._i() - # Lookup existing member by value or name using call: e.g., Color(1) or Color("RED") + # Lookup existing member by value or name: e.g., Color(1) or Color("RED") if value is not None: return cls()(value) @@ -79,7 +85,10 @@ def __contains__(cls, v): return False @classmethod - def __call__(cls, v): + def __call__(cls, v=None): + if v is None: + return cls() + # Lookup member by value or name for i in cls._i(): if i.value == v or i.name == v: @@ -87,12 +96,9 @@ def __call__(cls, v): raise ValueError(f"invalid: {v!r}") # Equality checks if both classes are Enums and have identical __members__ dicts - # __eq__ = classmethod(lambda cls, o: isinstance(o, type) and issubclass(o, Enum) and cls._i() == o._i()) - __eq__ = classmethod(lambda cls, o: getattr(o, "_i", None) and cls._i() == o._i()) + __eq__ = classmethod(lambda cls, o: getattr(o, "_i", False) and cls._i() == o._i()) # Iteration yields enum member instances (keys of __members__) __iter__ = classmethod(lambda cls: iter(cls._i())) - __len__ = classmethod(lambda cls: len(cls._i())) - __str__ = __repr__ = classmethod(lambda cls: f"") @classmethod def __setattr__(cls, k, v): @@ -104,13 +110,37 @@ def __delattr__(cls, k): @classmethod def _i(cls): - # Initialize and return dictionary mapping enum instances to their values + # Initialize and return dictionary mapping enum member instances to their values if "__members__" not in cls.__dict__: # Convert raw class attributes into typed enum instances cls.__members__ = {} for k, v in list(cls.__dict__.items()): if not k.startswith("_") and not callable(v): + if isinstance(v, cls): + continue + if v in cls.__members__: + raise ValueError(f"duplicate value {v!r} in {cls.__name__}") e = _c(cls.__name__, k, v) setattr(cls, k, e) cls.__members__[e] = v return cls.__members__ + + +# Install builder hook +import builtins + +_SN = "__ENUM_STATE__" # _STATE_NAME +if getattr(builtins, _SN, None): + builtins.__ENUM_STATE__["base"] = Enum +else: + _orig = builtins.__build_class__ + + # def _build_class(f, n, *b, **kw): + def _bc(f, n, *b, **kw): + cls = _orig(f, n, *b, **kw) + return ( + cls() if any(isinstance(base, type) and issubclass(base, Enum) for base in b) else cls + ) + + builtins.__build_class__ = _bc + setattr(builtins, _SN, {"original": _orig, "base": Enum, "wrapper": _bc}) diff --git a/python-stdlib/enum/enum_mini_usage.py b/python-stdlib/enum/enum_mini_usage.py index 2c3fabca3..3d16e9829 100644 --- a/python-stdlib/enum/enum_mini_usage.py +++ b/python-stdlib/enum/enum_mini_usage.py @@ -1,5 +1,5 @@ # enum_mini_usage.py -# version="1.4.0" +# version="1.5.0" from enum_mini import Enum @@ -35,8 +35,8 @@ class Color(Enum): print(Color("RED")) # Color.RED # 5. Length and iteration (order may vary) -print(len(Color())) # 3 -print([x for x in Color()]) # [, , ] +print(len(Color)) # 3 +print([x for x in Color]) # [, , ] # 6. Enum members mapping (order may vary) print(Color.__members__) # {: 1, : 2, : 3} @@ -60,12 +60,11 @@ class Color(Enum): except AttributeError as e: print("Immutability test passed:", e) -# # test NOT passed -# try: -# del Color.RED -# 0 / 0 -# except AttributeError as e: -# print("Immutability test passed:", e) +try: + del Color.RED + 0 / 0 +except AttributeError as e: + print("Immutability test passed:", e) assert Color.RED == 1 @@ -83,8 +82,6 @@ class Priority(Enum): MEDIUM = 2 HIGH = 3 -Priority() - # Arithmetic and numeric comparison (inherits int) print(Priority.HIGH > 2) # True print(Priority.LOW + 10) # 11 @@ -98,8 +95,6 @@ class Status(Enum): RUNNING = "running" DONE = "done" -status = Status() - # String operations (inherits str) print(Status.PENDING == "pending") # True print(Status.RUNNING.lower()) # running diff --git a/python-stdlib/enum/enum_usage.py b/python-stdlib/enum/enum_usage.py index 52453de28..d9d4fcbe1 100644 --- a/python-stdlib/enum/enum_usage.py +++ b/python-stdlib/enum/enum_usage.py @@ -1,5 +1,5 @@ # enum_usage.py -# version="1.4.0" +# version="1.5.0" from enum import Enum, IntEnum, StrEnum @@ -17,8 +17,6 @@ class Color(Enum): BLUE = 3 -color = Color() # Trigger initialization - # 1. Canonical output (str and repr) print(Color.RED) # Color.RED print(repr(Color.RED)) # @@ -37,12 +35,11 @@ class Color(Enum): print(Color("RED")) # Color.RED # 5. Container access (via instance) -print(Color()["RED"]) # Color.RED (via instance) -# print(Color["RED"]) # does not work +print(Color["RED"]) # Color.RED # 6. Length and iteration (order may vary) -print(len(Color())) # 3 -print([x for x in Color()]) # [, , ] +print(len(Color)) # 3 +print([x for x in Color]) # [, , ] # 7. Serialization and dictionary mapping print(Color.__members__) # {: 1, : 2, : 3} @@ -56,26 +53,25 @@ class Color(Enum): print("Immutability test passed:", e) try: - color.RED = 100 + Color.RED = 100 raise AssertionError("Expected AttributeError on setting attribute") except AttributeError as e: print("Immutability test passed:", e) try: - del color.RED + del Color.RED raise AssertionError("Expected AttributeError on deleting attribute") except AttributeError as e: print("Immutability test passed:", e) -# # test NOT passed -# try: -# del Color.RED -# 0 / 0 -# except AttributeError as e: -# print("Immutability test passed:", e) -# print(Color.RED) +try: + del Color.RED + 0 / 0 +except AttributeError as e: + print("Immutability test passed:", e) +print(Color.RED) -assert color.RED == 1 +assert Color.RED == 1 # 9. Functional API: Comma / space separated string with custom start State = Enum("State", "OFF, ON", start=10) @@ -127,9 +123,8 @@ class Status(StrEnum): RUNNING = "running" DONE = "done" -status = Status() - print("\n--- StrEnum ---") +print(Status.__members__) print(Status.PENDING) # pending print(repr(Status.PENDING)) # @@ -150,6 +145,6 @@ class Status(StrEnum): HttpCode = StrEnum("HttpCode", "OK NOT_FOUND INTERNAL_ERROR") print("\n--- StrEnum Functional API ---") -print(HttpCode.OK.value) # OK -print(HttpCode.NOT_FOUND == "NOT_FOUND") # True -print(HttpCode.dump()) # StrEnum('HttpCode', {'NOT_FOUND': 'NOT_FOUND', 'OK': 'OK', 'INTERNAL_ERROR': 'INTERNAL_ERROR'}) +print(HttpCode.OK.value) # ok +print(HttpCode.NOT_FOUND == "not_found") # True +print(HttpCode.dump()) # StrEnum('HttpCode', {'NOT_FOUND': 'not_found', 'OK': 'ok', 'INTERNAL_ERROR': 'internal_error'}) diff --git a/python-stdlib/enum/test_enum.py b/python-stdlib/enum/test_enum.py index 29a469add..978cbc2a9 100644 --- a/python-stdlib/enum/test_enum.py +++ b/python-stdlib/enum/test_enum.py @@ -1,5 +1,5 @@ # test_enum.py -# version="1.4.0" +# version="1.5.0" import unittest from enum import Enum, IntEnum, StrEnum diff --git a/python-stdlib/enum/test_enum_mini.py b/python-stdlib/enum/test_enum_mini.py index 419b92521..9dd078c5a 100644 --- a/python-stdlib/enum/test_enum_mini.py +++ b/python-stdlib/enum/test_enum_mini.py @@ -1,3 +1,6 @@ +# test_enum_mini.py +# version="1.5.0" + import unittest from enum_mini import Enum @@ -16,10 +19,6 @@ class Status(Enum): REJECTED = "rejected" -Color() -Status() - - class TestEnumMini(unittest.TestCase): # ========================================================================== @@ -68,8 +67,8 @@ def test_functional_api_dict(self): # ========================================================================== def test_iteration_and_len(self): """Verify iteration, len(), and __members__ mapping.""" - self.assertEqual(len(Color()), 3) - self.assertEqual(list(Color()), [Color.RED, Color.GREEN, Color.BLUE]) + self.assertEqual(len(Color), 3) + self.assertEqual(list(Color), [Color.RED, Color.GREEN, Color.BLUE]) items_dict = Color.__members__ self.assertIn(Color.RED, items_dict) @@ -98,10 +97,10 @@ def test_attribute_setting_protection(self): Color.RED.new_attr = "test" with self.assertRaises(AttributeError): - Color().NEW_MEMBER = 4 + Color.NEW_MEMBER = 4 with self.assertRaises(AttributeError): - del Color().RED + del Color.RED if __name__ == "__main__":