diff --git a/python-stdlib/enum/enum.md b/python-stdlib/enum/enum.md new file mode 100644 index 000000000..1133abf9f --- /dev/null +++ b/python-stdlib/enum/enum.md @@ -0,0 +1,120 @@ +Below is the documentation for your `enum.py` library. This file explains the core concepts of your custom `Enum` implementation and provides practical examples for embedded development and general logic. + +--- + +# Custom Enum Library for Python & MicroPython + +This library provides a flexible, memory-efficient `Enum` class designed for dynamic usage and seamless mathematical integration. Unlike the standard CPython `Enum`, this version allows for runtime expansion and direct arithmetic operations without needing to access a `.value` property. + +## Core Features +* **Transparent Math**: Supports arithmetic (`+`, `-`, `*`, `/`) and bitwise (`&`, `|`, `^`, `<<`, `>>`) operations directly on enum members. +* **Dynamic Expansion**: Add new members at runtime via `.append()` or direct attribute assignment. +* **Memory Efficient**: Uses `__slots__` in the `ValueWrapper` to minimize RAM usage on platforms like the ESP32. +* **Flexible Initialization**: Can be initialized via class inheritance, dictionaries, or keyword arguments. + +--- + +## Usage Examples + +### 1. Hardware Pin Configuration (ESP32) +Define your hardware pins using class inheritance. You can skip internal or reserved pins using the `__skipped__` attribute. + +```python +from enum import Enum + +class Pins(Enum): + # Members defined at class level + LED = 2 + BUTTON = 4 + # Members to exclude from the enum mapping + __skipped__ = ('RESERVED_PIN',) + RESERVED_PIN = 0 + +# You can also add pins during instantiation +pins = Pins(SDA=21, SCL=22) + +print(f"I2C SDA Pin: {pins.SDA}") # Output: 21 +print(f"Is pin 21 valid? {pins.is_value(21)}") # Output: True +``` + +### 2. Math and Register Logic +The `ValueWrapper` allows you to perform calculations directly. This is particularly useful for bitmasks and step-based logic. + +```python +# Initialize with key-value pairs +brightness = Enum(MIN=0, STEP=25, MAX=255) + +# Direct arithmetic (Forward and Reflected) +next_level = brightness.MIN + brightness.STEP // 2 +complex_math = 100 + brightness.STEP + +print(f"Next Level: {next_level}") # Output: 12 +print(f"Complex Math: {complex_math}") # Output: 125 + +# Bitwise operations for register control +flags = Enum(BIT_0=0x01, BIT_1=0x02) +combined = flags.BIT_0 | flags.BIT_1 +print(f"Combined Flags: {hex(combined)}") # Output: 0x03 +``` + +### 3. Dynamic State Machines +You can expand an `Enum` as your program logic progresses, such as adding states to a connection manager. + +```python +status = Enum(IDLE=0, CONNECTING=1) + +# Add multiple members via append() +status.append(CONNECTED=2, ERROR=3) + +# Add a single member via direct assignment +status.DISCONNECTING = 4 + +for name, val in status.items(): + print(f"Status {name} has code {val}") +``` + +### 4. Working with Different Data Types +Enums are not restricted to integers; they can wrap strings, floats, and booleans. + +```python +commands = Enum( + START="CMD_START", + STOP="CMD_STOP", + TIMEOUT=5.5, + IS_ACTIVE=True +) + +if commands.IS_ACTIVE: + # Use str() to get the wrapped string value + print(f"Executing: {commands.START}") +``` + +### 5. Introspection and Utilities +The library provides helper methods to validate values or find keys based on their values. + +```python +class ErrorCodes(Enum): + NOT_FOUND = 404 + SERVER_ERROR = 500 + +# Check if a value exists in the Enum +exists = ErrorCodes.is_value(404) # True + +# Get the formatted string name from a value +name = ErrorCodes.key_from_value(500) +print(name) # Output: ErrorCodes.SERVER_ERROR +``` + +--- + +## API Reference + +### `ValueWrapper` +The internal class that wraps values to enable mathematical transparency. +* `.value`: Access the raw value. +* `()`: Calling the object returns the raw value. + +### `Enum` (Inherits from `dict`) +* `append(arg=None, **kwargs)`: Adds new members to the Enum. +* `is_value(value)`: Returns `True` if the value exists in the Enum. +* `key_from_value(value)`: Returns the string representation (e.g., `ClassName.KEY`) for a given value. diff --git a/python-stdlib/enum/enum.py b/python-stdlib/enum/enum.py new file mode 100644 index 000000000..2fb698a2d --- /dev/null +++ b/python-stdlib/enum/enum.py @@ -0,0 +1,157 @@ +# enum.py +# version="1.2.1" + + +class EnumValue: + def __init__(self, value, name): + object.__setattr__(self, 'value', value) + object.__setattr__(self, 'name', name) + + def __repr__(self): + return str(self.value) + + def __call__(self): + return self.value + + def __eq__(self, other): + return self.value == (other.value if isinstance(other, EnumValue) else other) + + def __setattr__(self, key, value): + raise AttributeError("EnumValue is immutable") + + +class Enum: + def __new__(cls, *args, **kwargs): + if len(args) > 0: + raise TypeError(f"{cls.__name__}() kwargs allowed only, not {args} args") + return super(Enum, cls).__new__(cls) + + def __init__(self, **kwargs): + # 1. Collect class-level attributes (constants) + self._scan_class_attrs() + # 2. Add arguments from the constructor + if kwargs: + self.append(**kwargs) + + def _update(self, key, value): + setattr(self.__class__, key, EnumValue(value, key)) + + def _scan_class_attrs(self): + # Converts static class attributes into EnumValue objects + # List of methods and internal names that should not be converted + ignored = ('is_value', 'append') + + for key in dir(self.__class__): + # Skip internal names and methods + if key.startswith('_') or key in ignored: + continue + + value = getattr(self.__class__, key) + # Convert only constants, not methods + if not callable(value) and not isinstance(value, EnumValue): + self._update(key, value) + + def is_value(self, value): + # Оптимізація: ітеруємося по self (де вже є __iter__), а не через dir() + return any(member.value == value for member in self) + + def append(self, **kwargs): + forbidden = ('is_value', 'append', '_update', '_scan_class_attrs') + for key, value in kwargs.items(): + if key in forbidden or key.startswith('_'): + raise NameError(f"Cannot add enum member with reserved name: {key}") + if hasattr(self.__class__, key): + existing = getattr(self.__class__, key) + if isinstance(existing, EnumValue): + raise AttributeError(f"Enum member '{key}' already exists and is immutable") + self._update(key, value) + return self + + def __repr__(self): + # Implementation of the principle: obj == eval(repr(obj)) + # Use !r to correctly represent values ​​(e.g., quotes for strings) + members = [f"{k}={getattr(self.__class__, k).value!r}" for k in dir(self.__class__) if not k.startswith('_') and isinstance(getattr(self.__class__, k), EnumValue)] + # Return a string like: Color(RED=1, GREEN=2, BLUE=3) + return f"{type(self).__name__}({', '.join(members)})" + + def __call__(self, value): + for member in self: + if member.value == value: + return member + raise ValueError(f"no such value: {value}") + + def __setattr__(self, key, value): + if hasattr(self, key) and isinstance(getattr(self, key), EnumValue): + raise AttributeError(f"Enum member '{key}' is immutable") + super().__setattr__(key, value) + + def __delattr__(self, key): + if hasattr(self, key) and isinstance(getattr(self, key), EnumValue): + raise AttributeError("Enum members cannot be deleted") + super().__delattr__(key) + + def __len__(self): + return sum(1 for _ in self) + + def __iter__(self): + for key in dir(self.__class__): + attr = getattr(self.__class__, key) + if isinstance(attr, EnumValue): + yield attr + + +def enum(**kwargs): # `**kwargs` kept backwards compatible as in the Internet examples + return Enum(**kwargs) + + +if __name__ == '__main__': + # --- Usage Example --- + + # 1. Creation via class + class Color(Enum): + RED = 1 + GREEN = 2 + + # Create instance + c = Color() + print(f"Enum repr: {c}") + + # 2. Strict __init__ control check + try: + c_bad = Color('BLACK') + except TypeError as e: + print(f"\nTypeError: Strict Init Check: {e}\n") + + # 3. Dynamic addition + c.append(BLUE=3) + print(f"c after append: {c}") + + print('dir(c):', dir(c)) + + # 4. Immutability and name protection check + try: + c.append(append=True) + except NameError as e: + print(f"\nNameError: Reserved name protection: {e}\n") + + # 5. Basic access + print(f"RED: Name={c.RED.name}, Value={c.RED.value}, EnumValue={c.RED}, Call={c.RED()} ") + + # 6. Assertions + assert c.RED == 1 + assert c.RED.value == 1 + assert c.RED.name == 'RED' + + # 7. Reverse lookup + print(f"c(1) lookup object: {c(1)}, Name={c(1).name}") # RED + assert c(1).name == 'RED' + assert c(1) == 1 + + # 8. Iteration + print("Values list:", [member.value for member in c]) + print("Names list:", [member.name for member in c]) + + try: + c(7) + except ValueError as e: + print(f"\nValueError: {c} {e}\n") diff --git a/python-stdlib/enum/enum_0.py b/python-stdlib/enum/enum_0.py new file mode 100644 index 000000000..234498ec5 --- /dev/null +++ b/python-stdlib/enum/enum_0.py @@ -0,0 +1,212 @@ +# enum.py +# version="1.0.0" + +_Err = "no such attribute: " + + +class int_value(int): + @property + def value(self) -> int: + return self + + def __call__(self) -> int: + return self + + +class str_value(str): + @property + def value(self) -> str: + return self + + def __call__(self) -> str: + return self + + +class bool_value(bool): + @property + def value(self) -> bool: + return self + + def __call__(self) -> bool: + return self + + +class float_value(float): + @property + def value(self) -> float: + return self + + def __call__(self) -> float: + return self + + +def get_class_value(value): + if type(value) is int: + return int_value(value) + elif type(value) is bool: + return bool_value(value) + elif type(value) is float: + return float_value(value) + elif type(value) is str: + return str_value(value) + else: + return value + + +def enum(**kw_args): # `**kw_args` kept backwards compatible as in the Internet examples + return Enum(kw_args) + + +class Enum(dict): + def __init__(self, arg=None): # `arg` is dict() compatible + super().__init__() + self._arg = None + if not arg is None: + self.append(arg) + self._is_enums_from_class = False + self._get_enums_from_class() + + def _update(self, key, value): + self.update({key: get_class_value(value)}) + + def append(self, arg=None, **kw_args): + if len(kw_args): + for key, value in kw_args.items(): + self._update(key, value) + if type(arg) == type(dict()): + for key, value in arg.items(): + self._update(key, value) + else: + self._arg = arg # for __str__() + return self + + def __repr__(self): + d = self.copy() + try: + d.pop("_arg") + except: + pass + return str(d) + + def __str__(self): + value = None + try: + value = self._arg + except: + pass + if not value is None: + if self.is_value(value): + self._arg = None + return value + raise ValueError(_Err + f"{value}") + return self.__qualname__ + "(" + self.__repr__() + ")" + + def is_value(self, value): + if value in self.values(): + return True + return False + + def key_from_value(self, value): + for key in self: + if self.get(key) == value: + return self.__qualname__ + "." + key + raise ValueError(_Err + f"{value}") + + def __call__(self, value): + if self.is_value(value): + return value + raise ValueError(_Err + f"{value}") + + def __getattr__(self, key): + try: + if key in self: + return self[key] + else: + raise KeyError(_Err + f"{key}") + except: + raise KeyError(_Err + f"{key}") + + def __setattr__(self, key, value): + if key == "_arg": + self[key] = value + return + try: + self[key] = get_class_value(value) + except: + raise KeyError(_Err + f"{key}") + + def __delattr__(self, key): + try: + if key in self: + del self[key] + else: + raise KeyError(_Err + f"{key}") + except: + raise KeyError(_Err + f"{key}") + + def __len__(self): + return len(tuple(self.keys())) + + def __dir__(self): + return dir(Enum) + + def _get_enums_from_class(self): + ## Class XX(Enum): + ## X1 = 1 + ## X2 = 2 + + if not self._is_enums_from_class: + keys = dir(eval(self.__qualname__)) + + def try_remove(item): + try: + keys.remove(item) + except: + pass + + for item in dir(dict): + try_remove(item) + + _list = [ + "__init__", + "__class__init__", + "__call__", + "__Errases__", + "__module__", + "__qualname__", + "__len__", + "__lt__", + "__le__", + "__eq__", + "__ne__", + "__gt__", + "__ge__", + "__dir__", + "__delattr__", + "__getattr__", + "__setattr__", + "__str__", + "__repr__", + "_get_enums_from_class", + "_arg", + "_update", + "is_value", + "key_from_value", + "append", + ] + for item in _list: + try_remove(item) + module = "" + if self.__module__ != "__main__": + module = self.__module__ + "." + for key in keys: + try: + value = eval(f"{module}{self.__qualname__}.{key}") + except: + value = eval(f"{self.__qualname__}.{key}") + self._update(key, value) + keys.clear() + del keys + self._is_enums_from_class = True # 1 !!! + self.pop("_is_enums_from_class") # 2 !!! + return self \ No newline at end of file diff --git a/python-stdlib/enum/enum_1.py b/python-stdlib/enum/enum_1.py new file mode 100644 index 000000000..c7baf2629 --- /dev/null +++ b/python-stdlib/enum/enum_1.py @@ -0,0 +1,272 @@ +# enum.py +# version="1.1.0" + +_Err = "no such attribute: " + + +class ValueWrapper: + """Universal wrapper for accessing values via .value or calling ()""" + __slots__ = ('_v', ) + + def __init__(self, v): + self._v = v + + @property + def value(self): + return self._v + + def __call__(self): + return self._v + + def __repr__(self): + return repr(self._v) + + def __str__(self): + return str(self._v) + + # Type conversion + def __int__(self): + return int(self._v) + + def __float__(self): + return float(self._v) + + def __index__(self): + return int(self._v) + + def __bool__(self): + return bool(self._v) + + # Helper function to extract the raw value + def _get_v(self, other): + return other._v if isinstance(other, ValueWrapper) else other + + # Arithmetic and Bitwise operations (Forward) + def __add__(self, other): + return self._v + self._get_v(other) + + def __sub__(self, other): + return self._v - self._get_v(other) + + def __mul__(self, other): + return self._v * self._get_v(other) + + def __truediv__(self, other): + return self._v / self._get_v(other) + + def __floordiv__(self, other): + return self._v // self._get_v(other) + + def __mod__(self, other): + return self._v % self._get_v(other) + + def __pow__(self, other): + return self._v**self._get_v(other) + + def __and__(self, other): + return self._v & self._get_v(other) + + def __or__(self, other): + return self._v | self._get_v(other) + + def __xor__(self, other): + return self._v ^ self._get_v(other) + + def __lshift__(self, other): + return self._v << self._get_v(other) + + def __rshift__(self, other): + return self._v >> self._get_v(other) + + # Arithmetic and Bitwise operations (Reflected) + def __radd__(self, other): + return self._get_v(other) + self._v + + def __rsub__(self, other): + return self._get_v(other) - self._v + + def __rmul__(self, other): + return self._get_v(other) * self._v + + def __rtruediv__(self, other): + return self._get_v(other) / self._v + + def __rfloordiv__(self, other): + return self._get_v(other) // self._v + + def __rand__(self, other): + return self._get_v(other) & self._v + + def __ror__(self, other): + return self._get_v(other) | self._v + + def __rxor__(self, other): + return self._get_v(other) ^ self._v + + def __rlshift__(self, other): + return self._get_v(other) << self._v + + def __rrshift__(self, other): + return self._get_v(other) >> self._v + + # Unary operators + def __neg__(self): + return -self._v + + def __pos__(self): + return +self._v + + def __abs__(self): + return abs(self._v) + + def __invert__(self): + return ~self._v + + # Comparison + def __eq__(self, other): + return self._v == self._get_v(other) + + def __lt__(self, other): + return self._v < self._get_v(other) + + def __le__(self, other): + return self._v <= self._get_v(other) + + def __gt__(self, other): + return self._v > self._get_v(other) + + def __ge__(self, other): + return self._v >= self._get_v(other) + + def __ne__(self, other): + return self._v != self._get_v(other) + + +def enum(**kw_args): # `**kw_args` kept backwards compatible as in the Internet examples + return Enum(kw_args) + + +class Enum(dict): + def __init__(self, arg=None, **kwargs): + super().__init__() + # Use __dict__ directly for internal flags + # to avoid cluttering the dictionary keyspace + super().__setattr__('_is_loading', True) + + # 1. Collect class-level attributes (constants) + self._scan_class_attrs() + # 2. Add arguments from the constructor + if arg: self.append(arg) + if kwargs: self.append(kwargs) + + super().__setattr__('_is_loading', False) + + def _scan_class_attrs(self): + cls = self.__class__ + # Define attributes to skip (internal or explicitly requested) + skipped = getattr(cls, '__skipped__', ()) + + for key in dir(cls): + # Skip internal names, methods, and excluded attributes + if key.startswith('_') or key in ('append', 'is_value', 'key_from_value'): + continue + if key in skipped: + continue + + val = getattr(cls, key) + # Only wrap non-callable attributes (constants) + if not callable(val): + self[key] = ValueWrapper(val) + + def append(self, arg=None, **kwargs): + if isinstance(arg, dict): + for k, v in arg.items(): + self[k] = ValueWrapper(v) + else: + self._arg = arg # for __str__() + if kwargs: + for k, v in kwargs.items(): + self[k] = ValueWrapper(v) + return self + + def __getattr__(self, key): + if key in self: + return self[key] + raise AttributeError(_Err + key) + + def __setattr__(self, key, value): + if self._is_loading or key.startswith('_'): + # Record directly into memory as a regular variable + super().__setattr__(key, value) + else: + # Handle as an Enum element (wrap in ValueWrapper) + self[key] = ValueWrapper(value) + + def is_value(self, value): + return any(v._v == value for v in self.values()) + + def key_from_value(self, value): + for k, v in self.items(): + if v._v == value: return f"{self.__class__.__name__}.{k}" + raise ValueError(_Err + str(value)) + + def __dir__(self): + # 1. Dictionary keys (your data: X1, X2, etc.) + data_keys = list(self.keys()) + # 2. Class attributes (your methods: append, is_value, etc.) + class_stuff = list(dir(self.__class__)) + # 3. Parent class attributes (for completeness) + parent_attrs = list(dir(super())) + # Combine and remove duplicates using set for clarity + #return list(set(data_keys + class_stuff + parent_attrs)) + return list(set(data_keys + class_stuff)) + + def __call__(self, value): + if self.is_value(value): + return value + raise ValueError(_Err + f"{value}") + + +if __name__ == "__main__": + # --- Usage Examples --- + + # 1. GPIO and Hardware Configuration + class Pins(Enum): + LED = 2 + BUTTON = 4 + __skipped__ = ('RESERVED_PIN', ) + RESERVED_PIN = 0 + + pins = Pins(SDA=21, SCL=22) + print(f"I2C SDA Pin: {pins.SDA}") + print(f"Is 21 a valid pin? {pins.is_value(21)}") + + # 2. Math and Logic + brightness = Enum(MIN=0, STEP=25, MAX=255) + print(f"Next level: {brightness.MIN + brightness.STEP // 2}") + print(f"Calculation: {brightness.MIN + 2 * brightness.STEP}") + + # Direct arithmetic without .value + print(f"Complex math: {100 + brightness.STEP}") + + # 3. State Machine (Dynamic Expansion) + status = Enum(IDLE=0, CONNECTING=1) + status.append(CONNECTED=2, ERROR=3) + status.DISCONNECTING = 4 + + for name, val in status.items(): + print(f"Status {name} has code {val}") + + # 4. Working with different types + commands = Enum(START="CMD_START", STOP="CMD_STOP", REBOOT_CODE=0xDEADBEEF, IS_ACTIVE=True) + + if commands.IS_ACTIVE: + print(f"Running command: {commands.START}") + + # 5. Class Config and dir() + class WebConfig(Enum): + PORT = 80 + TIMEOUT = 5.0 + + config = WebConfig({'IP': '192.168.1.1'}) + print(f"Available keys in config: {list(config.keys())}") diff --git a/python-stdlib/enum/manifest.py b/python-stdlib/enum/manifest.py new file mode 100644 index 000000000..3046f9b87 --- /dev/null +++ b/python-stdlib/enum/manifest.py @@ -0,0 +1,3 @@ +metadata(version="1.2.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..4d3d7edc2 --- /dev/null +++ b/python-stdlib/enum/test_enum.py @@ -0,0 +1,99 @@ +# version="1.2.1" + +import unittest +from enum import Enum, EnumValue, enum + + +class TestEnum(unittest.TestCase): + def setUp(self): + # Створюємо базовий клас для тестів + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + self.ColorClass = Color + self.color = Color() + + def test_class_attributes(self): + """Тест статичних атрибутів, визначених у класі""" + self.assertEqual(self.color.RED.value, 1) + self.assertEqual(self.color.RED.name, 'RED') + self.assertIsInstance(self.color.RED, EnumValue) + + def test_init_kwargs(self): + """Тест додавання значень через конструктор __init__(**kwargs)""" + c = self.ColorClass(YELLOW=4, BLACK=0) + self.assertEqual(c.YELLOW.value, 4) + self.assertEqual(c.BLACK.name, 'BLACK') + + def test_append(self): + """Тест методу append(**kwargs) та ланцюжкового виклику""" + self.color.append(MAGENTA=5).append(CYAN=6) + self.assertEqual(self.color.MAGENTA.value, 5) + self.assertEqual(self.color.CYAN.name, 'CYAN') + + def test_comparison(self): + """Тест порівняння EnumValue з числами та іншими об'єктами""" + 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_call_reverse_lookup(self): + """Тест зворотного пошуку Color(1) -> RED""" + # У вашій реалізації __call__ повертає об'єкт EnumValue + result = self.color(1) + self.assertEqual(result.name, 'RED') + self.assertEqual(result.value, 1) + + # Перевірка виключення для неіснуючого значення + with self.assertRaises(ValueError): + self.color(999) + + def test_is_value(self): + """Тест методу перевірки наявності значення 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): + """Тест ітерабельності Enum (магічний метод __iter__)""" + # Створюємо список імен через ітерацію + members = list(self.color) + names = [m.name for m in members] + + self.assertIn('RED', names) + self.assertIn('GREEN', names) + self.assertIn('BLUE', names) + self.assertEqual(len(members), 3) + + def test_enum_value_immutability(self): + """Тест захисту від зміни значень EnumValue""" + with self.assertRaises(AttributeError): + self.color.RED.value = 10 + + with self.assertRaises(AttributeError): + self.color.RED.name = "NEW_NAME" + + def test_len(self): + """Тест магічного методу __len__""" + self.assertEqual(len(self.color), 3) + self.color.append(WHITE=7) + self.assertEqual(len(self.color), 4) + + def test_backwards_compatible_function(self): + """Тест глобальної функції enum(**kwargs)""" + e = enum(A=10, B=20) + self.assertEqual(e.A.value, 10) + self.assertEqual(e.B.name, 'B') + + def test_call_method(self): + """Тест виклику об'єкта як функції c.RED() -> 1""" + self.assertEqual(self.color.RED(), 1) + self.assertEqual(self.color.GREEN(), 2) + + +if __name__ == '__main__': + unittest.main() diff --git a/python-stdlib/enum/test_enum_0.py b/python-stdlib/enum/test_enum_0.py new file mode 100644 index 000000000..4a7363d6f --- /dev/null +++ b/python-stdlib/enum/test_enum_0.py @@ -0,0 +1,92 @@ +# test_enum.py +# version="1.0.0" + +from enum import Enum, enum + + +class Direction(Enum): + CW = "CW" + CCW = "CCW" + + +class State(Direction): + Stop = 1 + Run = 2 + Ready = 3 + Disabled = False + Enabled = True + + +state = Enum() +print(state) +state = Direction() +print(state) +state = State() +print(state) +state = State({"X": 1.0, "Y": 2.0}) +print(state) +state.Idle = 10 +state.Triggered = 20 +state.Lockout = 30 +print(state) + +print("Direction(Direction.CCW):", Direction(Direction.CCW)) +print("Direction('CW'):", Direction("CW")) +print("state(10):", state(10)) + +print("state('CW'):", state("CW")) +print("type(state('CW')):", type(state("CW"))) + +print("state.key_from_value(20):", state.key_from_value(20)) +print("len(state):", len(state)) + +print("state.Idle:", state.Idle) +print("type(state.Idle):", type(state.Idle)) + +current_state = state.Idle +print("current_state:", current_state) +if current_state == state.Idle: + print(" Idle state") +if current_state != state.Triggered: + print(" Not a triggered state") + current_state = state.Idle +print("current_state:", current_state) +print("state.key_from_value(current_state):", state.key_from_value(current_state)) + +state2 = eval(str(state)) +print(state2) +print("state == state2:", state == state2) + +del state.Triggered +print(state) +print("state == state2:", state == state2) + +print("state.keys():", state.keys()) +print("state.values():", state.values()) +print("state.items():", state.items()) + +try: + del state.stop +except Exception as e: + print("Exception:", e) + +assert current_state == state.Idle +assert current_state != state.Disabled +assert state.Idle != state.Disabled +print( + "State(State.Ready):", + State(State.Ready), + "type(State.Ready):", + type(State(State.Ready)), + "type(State.Ready):", + type(State.Ready), +) +assert int(str(State(State.Ready))) == State.Ready +assert int(str(State(State.Ready))) != State.Disabled +print("will raise exception") +try: + del state.Triggered +except Exception as e: + print("Exception:", e) + +print("OK") diff --git a/python-stdlib/enum/test_enum_1.py b/python-stdlib/enum/test_enum_1.py new file mode 100644 index 000000000..e181f1241 --- /dev/null +++ b/python-stdlib/enum/test_enum_1.py @@ -0,0 +1,172 @@ +# test_enum.py +# version="1.1.0" + +import unittest +from enum import Enum, ValueWrapper + + +class TestEnum(unittest.TestCase): + def test_class_initialization(self): + """Check Enum creation via class inheritance""" + class Pins(Enum): + TX = 1 + RX = 3 + + pins = Pins() + self.assertEqual(int(pins.TX), 1) + self.assertEqual(int(pins.RX), 3) + self.assertIn('TX', pins) + self.assertIn('RX', pins) + + def test_dict_initialization(self): + """Check Enum creation by passing a dictionary to the constructor""" + e = Enum({'A': 10, 'B': 'test'}, C='C') + self.assertEqual(e.A.value, 10) + self.assertEqual(e.B(), 'test') + self.assertEqual(e.C, 'C') + + def test_value_wrapper_behaviors(self): + """Check ValueWrapper properties (calling, types, comparison)""" + v = ValueWrapper(100) + self.assertEqual(v.value, 100) # .value + self.assertEqual(v(), 100) # __call__ + self.assertEqual(int(v), 100) # __int__ + self.assertTrue(v == 100) # __eq__ + self.assertEqual(str(v), "100") # __str__ + + def test_append_and_dynamic_attrs(self): + """Check dynamic addition of values""" + e = Enum() + e.append(C=30) + e.append({'D': 40}, E=50) + e.F = 60 + + self.assertEqual(int(e.C), 30) + self.assertEqual(int(e.D), 40) + self.assertEqual(int(e.E), 50) + self.assertEqual(int(e.F), 60) + self.assertIsInstance(e.E, ValueWrapper) + self.assertIsInstance(e.F, ValueWrapper) + + def test_getattr_error(self): + """Check that an error is raised when an attribute is missing""" + e = Enum(A=1) + with self.assertRaises(AttributeError): + _ = e.NON_EXISTENT + + def test_is_value_and_key_lookup(self): + """Check key lookup by value and value validation""" + class Status(Enum): + IDLE = 0 + BUSY = 1 + + s = Status() + self.assertTrue(s.is_value(0)) + self.assertTrue(s.is_value(1)) + self.assertFalse(s.is_value(99)) + self.assertEqual(s.key_from_value(1), "Status.X2" if "X2" in dir(s) else "Status.BUSY") + self.assertEqual(s.key_from_value(1), "Status.BUSY") + + def test_is_loading_protection(self): + """Check that _is_loading does not end up in dictionary keys""" + e = Enum(A=1) + self.assertNotIn('_is_loading', e.keys()) + # Check that the flag is False after initialization + self.assertFalse(e._is_loading) + + def test_dir_visibility(self): + """Check for the presence of keys and methods in dir()""" + e = Enum(DATA=123) + directory = dir(e) + self.assertIn('DATA', directory) # Dynamic data + self.assertIn('append', directory) # Enum class method + self.assertIn('keys', directory) # Base dict method + + def test_math_and_indexing(self): + """Check usage in mathematics and as an index""" + e = Enum(VAL=10) + # Mathematics + self.assertEqual(e.VAL + 5, 15) + # Usage as an index (e.g., in a list) + ls = [0] * 20 + ls[e.VAL] = 1 + self.assertEqual(ls[10], 1) + + def test_various_types(self): + """Check operation with various data types""" + e = Enum(STR="test", FLT=1.5, BL=True) + self.assertEqual(str(e.STR), "test") + self.assertEqual(float(e.FLT), 1.5) + self.assertTrue(e.BL) + + def test_skipped_attributes(self): + """Check ignoring attributes via __skipped__""" + class MyEnum(Enum): + __skipped__ = ('SECRET', ) + PUBLIC = 1 + SECRET = 2 + + e = MyEnum() + self.assertIn('PUBLIC', e) + self.assertNotIn('SECRET', e) + + def test_post_loading_setattr(self): + """Check setting attributes after initialization""" + e = Enum(A=1) + # Regular attribute (starts with _) + e._internal = 100 + self.assertEqual(e._internal, 100) + self.assertNotIn('_internal', e.keys()) # Should not be in data + + # New Enum element + e.B = 2 + self.assertIsInstance(e.B, ValueWrapper) + self.assertIn('B', e.keys()) + + def test_key_from_value_not_found(self): + """Check for an error when searching for a non-existent value""" + e = Enum(A=1) + with self.assertRaises(ValueError): + e.key_from_value(999) + + def test_math_division(self): + """Check floor division and true division""" + e = Enum(STEP=25) + # Floor division + self.assertEqual(e.STEP // 2, 12) + # True division + self.assertEqual(e.STEP / 2, 12.5) + + def test_full_arithmetic(self): + """Check all new arithmetic operations""" + v = ValueWrapper(10) + self.assertEqual(v + 5, 15) + self.assertEqual(20 - v, 10) # Check __rsub__ + self.assertEqual(v * 2, 20) + self.assertEqual(30 // v, 3) # Check __rfloordiv__ + self.assertEqual(v % 3, 1) + self.assertTrue(v > 5) # Check comparison + + def test_bitmask_operations(self): + """Test bitwise operations (important for registers)""" + flags = Enum(BIT_0=0x01, BIT_1=0x02) + + # Check OR and AND + combined = flags.BIT_0 | flags.BIT_1 + self.assertEqual(combined, 0x03) + self.assertEqual(combined & flags.BIT_1, 0x02) + + # Check shifts + self.assertEqual(flags.BIT_0 << 2, 4) + self.assertEqual(8 >> flags.BIT_0, 4) # Check __rrshift__ + + def test_unary_operations(self): + """Test unary operators""" + e = Enum(VAL=10, NEG_VAL=-5) + self.assertEqual(-e.VAL, -10) + self.assertEqual(abs(e.NEG_VAL), 5) + self.assertEqual(~e.VAL, -11) # Bitwise NOT + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/ci.sh b/tools/ci.sh index abe83b563..6ff914ed7 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -54,6 +54,7 @@ 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/functools/test_partial.py \ python-stdlib/functools/test_reduce.py \ python-stdlib/heapq/test_heapq.py \