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 diff --git a/python-stdlib/enum/README.md b/python-stdlib/enum/README.md new file mode 100644 index 000000000..662c8bace --- /dev/null +++ b/python-stdlib/enum/README.md @@ -0,0 +1,74 @@ +# 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 new file mode 100644 index 000000000..edfd42bde --- /dev/null +++ b/python-stdlib/enum/enum.py @@ -0,0 +1,182 @@ +# enum.py +# 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( + f"{e}.{n}", + (type(v),), + { + "name": n, + "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__": 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_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) + + @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"{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: 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("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): + e = _c(cls.__name__, k, v, v_t=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 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_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/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 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..29a469add --- /dev/null +++ b/python-stdlib/enum/test_enum.py @@ -0,0 +1,165 @@ +# test_enum.py +# version="1.4.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..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 7ee7eb4d1..5d261c576 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -65,6 +65,8 @@ function ci_package_tests_run { python-stdlib/base64/test_base64.py \ python-stdlib/binascii/test_binascii.py \ python-stdlib/collections-defaultdict/test_defaultdict.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/heapq/test_heapq.py \