forked from MicroPythonOS/MicroPythonOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdataclasses.py
More file actions
89 lines (77 loc) · 3.54 KB
/
dataclasses.py
File metadata and controls
89 lines (77 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# dataclasses.py: MicroPython compatibility layer for Python's dataclasses
# Implements @dataclass, field, and Field with support for default_factory
MISSING = object() # Sentinel for missing values
class Field:
"""Represents a dataclass field, supporting default_factory."""
def __init__(self, default=MISSING, default_factory=MISSING, init=True, repr=True):
self.name = None # Set by dataclass decorator
self.type = None # Set by dataclass decorator
self.default = default
self.default_factory = default_factory
self.init = init
self.repr = repr
def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True):
"""Specify a dataclass field with optional default_factory."""
if default is not MISSING and default_factory is not MISSING:
raise ValueError("Cannot specify both default and default_factory")
return Field(default=default, default_factory=default_factory, init=init, repr=repr)
def dataclass(cls):
"""Decorator to emulate @dataclass, generating __init__ and __repr__."""
# Get class annotations and defaults
annotations = getattr(cls, '__annotations__', {})
defaults = {}
fields = {}
# Process class attributes for defaults and field() calls
for name in dir(cls):
if not name.startswith('__'):
attr = getattr(cls, name, None)
if not callable(attr):
if isinstance(attr, Field):
fields[name] = attr
fields[name].name = name
fields[name].type = annotations.get(name)
if attr.default is not MISSING:
defaults[name] = attr.default
elif name in annotations:
defaults[name] = attr
# Ensure all annotated fields have a Field object
for name in annotations:
if name not in fields:
fields[name] = Field(default=defaults.get(name, MISSING), init=True, repr=True)
fields[name].name = name
fields[name].type = annotations.get(name)
# Generate __init__ method
def __init__(self, *args, **kwargs):
# Positional arguments
init_fields = [name for name, f in fields.items() if f.init]
for i, value in enumerate(args):
print(f"dataclasses.py: {i} {value}")
if i >= len(init_fields):
raise TypeError(f"dataclasses.py: too many positional arguments")
setattr(self, init_fields[i], value)
# Keyword arguments, defaults, and default_factory
for name, field in fields.items():
if field.init and name in kwargs:
setattr(self, name, kwargs[name])
elif not hasattr(self, name):
if field.default_factory is not MISSING:
setattr(self, name, field.default_factory())
elif field.default is not MISSING:
setattr(self, name, field.default)
elif field.init:
raise TypeError(f"Missing required argument: {name}")
# Call __post_init__ if defined
if hasattr(self, '__post_init__'):
self.__post_init__()
# Generate __repr__ method
def __repr__(self):
fields_repr = [
f"{name}={getattr(self, name)!r}"
for name, field in fields.items()
if field.repr
]
return f"{cls.__name__}({', '.join(fields_repr)})"
# Attach generated methods to class
setattr(cls, '__init__', __init__)
setattr(cls, '__repr__', __repr__)
return cls