Skip to content

Instantly share code, notes, and snippets.

@markusand
Last active November 20, 2025 16:08
Show Gist options
  • Select an option

  • Save markusand/3652e0a9341531b2ccebf096489ba65d to your computer and use it in GitHub Desktop.

Select an option

Save markusand/3652e0a9341531b2ccebf096489ba65d to your computer and use it in GitHub Desktop.
A simple implementation for a dataclass decorator in micropython
"""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