diff --git a/python-stdlib/enum/CPy/CPy.py b/python-stdlib/enum/CPy/CPy.py new file mode 100644 index 000000000..dedc44053 --- /dev/null +++ b/python-stdlib/enum/CPy/CPy.py @@ -0,0 +1,103 @@ +from enum import Enum, StrEnum, IntEnum + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +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)) # +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() + +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: + Color.RED.value = 99 + 0 / 0 +except AttributeError: + print("Protected: Cannot reassign class attribute") + +try: + Color.RED = 99 + 0 / 0 +except AttributeError: + print("Protected: Cannot reassign class attribute") + +try: + 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 new file mode 100644 index 000000000..94669e61c --- /dev/null +++ b/python-stdlib/enum/README.md @@ -0,0 +1,116 @@ +# Lightweight 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` +- ✅ `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: +- **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. + +## 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__` 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 a member, e.g. `Color.RED()` | Not supported | Returns its raw value | Returns its raw value | + +## 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 new file mode 100644 index 000000000..e74e0f664 --- /dev/null +++ b/python-stdlib/enum/enum.py @@ -0,0 +1,211 @@ +# enum.py +# Enum implementation without metaclasses +# version="1.5.0" + +# ============================================================================== +# Variable & Abbreviation Definitions: +# ============================================================================== +# Functions & Helper Methods: +# _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, 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 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, 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 t is int and not isinstance(v, int): + raise TypeError(f"IntEnum member {n!r} value must be int, got {type(v).__name__}") + 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 + e = getattr(e, "__name__", str(e)) + + # Create dynamic subclass inheriting the value's type (int, str, float etc.) + return type( + f"{e}.{n}", + (type(v),), + { + "name": n, + "value": v, + "__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, + }, + )(v) + + +class Enum: + _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 = 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.lower() for k in l} + if is_str + 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 + 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 + else {k: v for v, k in enumerate(names, start=start)} + ) + + # Construct and return a new Enum subclass + c = type(str(value), (cls,), {}) + for k, v in d.items(): + setattr(c, k, v) + c._i() + 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) + + @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=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: + return i + raise ValueError(f"{v!r} is not a valid {cls.__name__}") + + @classmethod + 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: 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): + raise AttributeError("cannot set attribute") + + @classmethod + def __delattr__(cls, k): + raise AttributeError("cannot delete attribute") + + @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})" + + @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): + 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__ + + +class IntEnum(Enum): + _v_t = int + + +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 new file mode 100644 index 000000000..1db3ebe57 --- /dev/null +++ b/python-stdlib/enum/enum_check.py @@ -0,0 +1,172 @@ +# enum_check.py +# version="1.5.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}") # 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 + +# Iteration over Enum members via class instance call +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}") # Enum c instance: + +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}") # c(1) lookup object: Color.RED, name=RED, value=1 +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") # ValueError (Caught expected error): 999 is not a valid Color + + +# --- 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}") # 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 +assert status.name == "RUNNING" +assert status.value == received_byte + +# Test equality and arithmetic operations +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 + +# 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") # 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") # 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") # 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 ---") # --- Functional API and Eval Check --- + +c_dump = Color.dump() +print(f"Original Dump: {c_dump}") # Original Dump: Enum('Color', {'BLUE': 3, 'RED': 1, 'GREEN': 2}) +c2 = eval(c_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__) # {: 1, : 2, : 3} +print(c2.__members__) # {: 1, : 2, : 3} +assert Color.__members__ == c2.__members__ +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}") # Functional Enum class (state): +print(f"Type: {type(state)}") # Type: +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}") # 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')}") # 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" + + +# --- 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__}") # Empty Enum items: {} +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()}") # Restored Functional Enum: Enum('Config', {'TIMEOUT_SEC': 30, 'MAX_RETRY': 5}) +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}") # 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}") # 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 new file mode 100644 index 000000000..5c35840d1 --- /dev/null +++ b/python-stdlib/enum/enum_mini.py @@ -0,0 +1,146 @@ +# enum_mini.py +# Minimalist Enum implementation without metaclasses +# version="1.5.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, 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 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): + # 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 or object was passed + e = getattr(e, "__name__", str(e)) + + # Create dynamic subclass inheriting the value's type (int, str, float etc.) + 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("must be dict") + + # Construct and return a new Enum subclass + c = type(str(value), (cls,), {}) + for k, v in names.items(): + 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: 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=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: + 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: getattr(o, "_i", False) and cls._i() == o._i()) + # Iteration yields enum member instances (keys of __members__) + __iter__ = classmethod(lambda cls: iter(cls._i())) + + @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 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 new file mode 100644 index 000000000..3d16e9829 --- /dev/null +++ b/python-stdlib/enum/enum_mini_usage.py @@ -0,0 +1,102 @@ +# enum_mini_usage.py +# version="1.5.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) + +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 + +# 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" + +# 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/enum_usage.py b/python-stdlib/enum/enum_usage.py new file mode 100644 index 000000000..d9d4fcbe1 --- /dev/null +++ b/python-stdlib/enum/enum_usage.py @@ -0,0 +1,150 @@ +# enum_usage.py +# version="1.5.0" + +from enum import Enum, IntEnum, StrEnum + +# ============================================================================== +# Usage Examples +# ============================================================================== + +# ============================================================================== +# Class Definition Syntax +# ============================================================================== + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +# 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 + +# 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) + +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" + +print("\n--- StrEnum ---") +print(Status.__members__) +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 new file mode 100644 index 000000000..76d44eb3c --- /dev/null +++ b/python-stdlib/enum/manifest.py @@ -0,0 +1,3 @@ +metadata(version="1.4.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..978cbc2a9 --- /dev/null +++ b/python-stdlib/enum/test_enum.py @@ -0,0 +1,165 @@ +# test_enum.py +# version="1.5.0" + +import unittest +from enum import Enum, IntEnum, StrEnum + + +class TestEnum(unittest.TestCase): + def setUp(self): + 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__, '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 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) + self.assertFalse(self.color.RED == "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) + + # Lookup by name + self.assertEqual(self.color('RED'), self.color.RED) + + # Lookup by instance + self.assertEqual(self.color(self.ColorClass.RED), self.ColorClass.RED) + + # Invalid lookup + with self.assertRaises(ValueError): + self.color(999) + with self.assertRaises(ValueError): + self.StatusClass(999) + + 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.assertEqual(names, ['RED', 'GREEN', 'BLUE']) + + def test_immutability(self): + """Verify Enum member and Enum instance are immutable after init.""" + with self.assertRaises(AttributeError): + self.color.RED.value = 10 + + with self.assertRaises(AttributeError): + self.color.NEW_MEMBER = 4 + + def test_deletion_prevention(self): + """Verify that members cannot be deleted from instance or class.""" + with self.assertRaises(AttributeError): + del self.color.RED + +# 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) + 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.""" + 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.""" + 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(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__': + unittest.main() diff --git a/python-stdlib/enum/test_enum_mini.py b/python-stdlib/enum/test_enum_mini.py new file mode 100644 index 000000000..9dd078c5a --- /dev/null +++ b/python-stdlib/enum/test_enum_mini.py @@ -0,0 +1,107 @@ +# test_enum_mini.py +# version="1.5.0" + +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" + + +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 old mode 100755 new mode 100644 index 4533fe204..a31bd3e83 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -66,6 +66,8 @@ 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/enum/test_enum_mini.py \ python-stdlib/functools/test_partial.py \ python-stdlib/functools/test_reduce.py \ python-stdlib/functools/test_cache.py \