Last active
November 20, 2025 16:08
-
-
Save markusand/3652e0a9341531b2ccebf096489ba65d to your computer and use it in GitHub Desktop.
A simple implementation for a dataclass decorator in micropython
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """A simple implementation for a dataclass decorator in micropython""" | |
| # pylint: disable=line-too-long | |
| from typing import Callable, Type, TypeVar, Any, Tuple | |
| from builtins import repr as brepr | |
| T = TypeVar("T") | |
| def field(default_factory: Callable[[], Any]) -> Tuple[str, Callable[[], Any], Type[Any]]: | |
| """Create a factory for a mutable default""" | |
| return ("__factory__", default_factory, type(default_factory())) | |
| def dataclass( | |
| cls: Type[T] | None = None, | |
| *, | |
| init: bool = True, | |
| repr: bool = True, # pylint: disable=redefined-builtin | |
| eq: bool = True, | |
| order: bool = False, | |
| frozen: bool = False, | |
| ) -> Type[T]: | |
| """Decorates a class with a dataclass-like interface""" | |
| def wrap(cls: Type[T]) -> Type[T]: | |
| fields = { | |
| k: v | |
| for k, v in cls.__dict__.items() | |
| if not k.startswith("_") | |
| and not callable(v) | |
| and not isinstance(v, (property, staticmethod, classmethod)) | |
| } | |
| if init: | |
| def __init__(self: T, **kwargs: Any) -> None: | |
| if extra := set(kwargs.keys()) - set(fields.keys()): | |
| raise AttributeError(f"Unexpected fields: {', '.join(extra)}") | |
| for key, default in fields.items(): | |
| if isinstance(default, (list, dict)): | |
| raise ValueError(f"mutable default {type(default).__name__} for field {key} not allowed") | |
| is_factory = isinstance(default, tuple) and len(default) == 3 and default[0] == "__factory__" | |
| value = kwargs[key] if key in kwargs else default[1]() if is_factory else default | |
| if value is not None and not (isinstance(value, default[2]) if is_factory else isinstance(value, type(default)): | |
| raise AttributeError(f"invalid type for {key}, expected {type(default).__name__}") | |
| if frozen: | |
| object.__setattr__(self, key, value) | |
| else: | |
| setattr(self, key, value) | |
| cls.__init__ = __init__ | |
| if repr: | |
| def __repr__(self: T) -> str: | |
| field_strs = [f"{k}={brepr(getattr(self, k))}" for k in fields] | |
| return f"{cls.__name__}({', '.join(field_strs)})" | |
| cls.__repr__ = __repr__ | |
| if eq: | |
| def __eq__(self: T, other: Any) -> bool: | |
| return isinstance(other, cls) and self.__dict__ == other.__dict__ | |
| cls.__eq__ = __eq__ | |
| if order: | |
| def __lt__(self: T, other: Any) -> bool: | |
| if not isinstance(other, cls): | |
| raise TypeError( | |
| f"'<' not supported for '{type(self).__name__}' and '{type(other).__name__}'" | |
| ) | |
| return tuple(getattr(self, k) for k in fields) < tuple( | |
| getattr(other, k) for k in fields | |
| ) | |
| def __le__(self: T, other: Any) -> bool: | |
| if not isinstance(other, cls): | |
| raise TypeError( | |
| f"'<=' not supported for '{type(self).__name__}' and '{type(other).__name__}'" | |
| ) | |
| return tuple(getattr(self, k) for k in fields) <= tuple( | |
| getattr(other, k) for k in fields | |
| ) | |
| def __gt__(self: T, other: Any) -> bool: | |
| if not isinstance(other, cls): | |
| raise TypeError( | |
| f"'>' not supported for '{type(self).__name__}' and '{type(other).__name__}'" | |
| ) | |
| return tuple(getattr(self, k) for k in fields) > tuple( | |
| getattr(other, k) for k in fields | |
| ) | |
| def __ge__(self: T, other: Any) -> bool: | |
| if not isinstance(other, cls): | |
| raise TypeError( | |
| f"'>=' not supported for '{type(self).__name__}' and '{type(other).__name__}'" | |
| ) | |
| return tuple(getattr(self, k) for k in fields) >= tuple( | |
| getattr(other, k) for k in fields | |
| ) | |
| cls.__lt__ = __lt__ # type: ignore | |
| cls.__le__ = __le__ # type: ignore | |
| cls.__gt__ = __gt__ # type: ignore | |
| cls.__ge__ = __ge__ # type: ignore | |
| if frozen: | |
| def __setattr__(self: T, name: str, value: Any) -> None: | |
| raise AttributeError(f"cannot reassign field '{name}'") | |
| def __delattr__(self: T, name: str) -> None: | |
| raise AttributeError(f"cannot delete field '{name}'") | |
| cls.__setattr__ = __setattr__ | |
| cls.__delattr__ = __delattr__ | |
| return cls | |
| return wrap if cls is None else wrap(cls) # type: ignore |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment