Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python-stdlib/enum/enum.py: Add Enum class. #980

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
211 changes: 211 additions & 0 deletions python-stdlib/enum/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# enum.py

_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
3 changes: 3 additions & 0 deletions python-stdlib/enum/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
metadata(version="1.0.0")

module("enum.py")
91 changes: 91 additions & 0 deletions python-stdlib/enum/test_enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# enum_test.py

from enum import Enum, enum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to run this test under CPython 3.12.2 but it doesn't work, for many reasons. And it should run under CPython so we can test that the implementation of MicroPython's enum matches the CPython enum.

For example, enum does not exist in the enum CPython module. Which version of CPython were you testing against?



class Direction(Enum):
CW = "CW"
CCW = "CCW"


class State(Direction):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython doesn't allow inheriting enums from each other.

Stop = 1
Run = 2
Ready = 3
Disabled = False
Enabled = True


state = Enum()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython cannot create enums in this way.

print(state)
state = Direction()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython requires a value in the constructor here.

print(state)
state = State()
print(state)
state = State({"X": 1.0, "Y": 2.0})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython doesn't allow such an argument to the constructor.

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython doesn't allow calling an enum.


print("state('CW'):", state("CW"))
print("type(state('CW')):", type(state("CW")))

print("state.key_from_value(20):", state.key_from_value(20))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython doesn't have key_from_value().

print("len(state):", len(state))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython doesn't have __len__ on an enum.


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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPython enums don't have keys/values/items methods.


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")
1 change: 1 addition & 0 deletions tools/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
Loading