Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions python-stdlib/enum/CPy/CPy.py
Original file line number Diff line number Diff line change
@@ -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)) # <Color.RED: 1>
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()) # <enum 'Color'>
print(repr(Color())) # <enum '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) # <enum 'Color'>
print(repr(Color)) # <enum '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)) # [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
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
74 changes: 74 additions & 0 deletions python-stdlib/enum/README.md
Comment thread
IhorNehrutsa marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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()`.
182 changes: 182 additions & 0 deletions python-stdlib/enum/enum.py
Original file line number Diff line number Diff line change
@@ -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"<enum '{type(cls).__name__}'>")

@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
Loading
Loading