Skip to content

Commit 90df88a

Browse files
continue porting python-nostr
1 parent 1e3e990 commit 90df88a

File tree

4 files changed

+666
-17
lines changed

4 files changed

+666
-17
lines changed

internal_filesystem/lib/dataclasses.py

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,85 @@
1-
# dataclasses.py: Minimal MicroPython compatibility layer for Python's dataclasses
2-
# Implements @dataclass with __init__ and __repr__ generation
1+
# dataclasses.py: MicroPython compatibility layer for Python's dataclasses
2+
# Implements @dataclass, field, and Field with support for default_factory
3+
4+
MISSING = object() # Sentinel for missing values
5+
6+
class Field:
7+
"""Represents a dataclass field, supporting default_factory."""
8+
def __init__(self, default=MISSING, default_factory=MISSING, init=True, repr=True):
9+
self.name = None # Set by dataclass decorator
10+
self.type = None # Set by dataclass decorator
11+
self.default = default
12+
self.default_factory = default_factory
13+
self.init = init
14+
self.repr = repr
15+
16+
def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True):
17+
"""Specify a dataclass field with optional default_factory."""
18+
if default is not MISSING and default_factory is not MISSING:
19+
raise ValueError("Cannot specify both default and default_factory")
20+
return Field(default=default, default_factory=default_factory, init=init, repr=repr)
321

422
def dataclass(cls):
5-
"""Decorator to emulate Python's @dataclass, generating __init__ and __repr__."""
23+
"""Decorator to emulate @dataclass, generating __init__ and __repr__."""
624
# Get class annotations and defaults
725
annotations = getattr(cls, '__annotations__', {})
826
defaults = {}
27+
fields = {}
28+
29+
# Process class attributes for defaults and field() calls
930
for name in dir(cls):
1031
if not name.startswith('__'):
1132
attr = getattr(cls, name, None)
12-
if not callable(attr) and name in annotations:
13-
defaults[name] = attr
33+
if not callable(attr):
34+
if isinstance(attr, Field):
35+
fields[name] = attr
36+
fields[name].name = name
37+
fields[name].type = annotations.get(name)
38+
if attr.default is not MISSING:
39+
defaults[name] = attr.default
40+
elif name in annotations:
41+
defaults[name] = attr
42+
43+
# Ensure all annotated fields have a Field object
44+
for name in annotations:
45+
if name not in fields:
46+
fields[name] = Field(default=defaults.get(name, MISSING), init=True, repr=True)
47+
fields[name].name = name
48+
fields[name].type = annotations.get(name)
1449

1550
# Generate __init__ method
1651
def __init__(self, *args, **kwargs):
1752
# Positional arguments
18-
fields = list(annotations.keys())
53+
init_fields = [name for name, f in fields.items() if f.init]
1954
for i, value in enumerate(args):
20-
if i >= len(fields):
55+
if i >= len(init_fields):
2156
raise TypeError(f"Too many positional arguments")
22-
setattr(self, fields[i], value)
57+
setattr(self, init_fields[i], value)
2358

24-
# Keyword arguments and defaults
25-
for name in fields:
26-
if name in kwargs:
59+
# Keyword arguments, defaults, and default_factory
60+
for name, field in fields.items():
61+
if field.init and name in kwargs:
2762
setattr(self, name, kwargs[name])
2863
elif not hasattr(self, name):
29-
if name in defaults:
30-
setattr(self, name, defaults[name])
31-
else:
64+
if field.default_factory is not MISSING:
65+
setattr(self, name, field.default_factory())
66+
elif field.default is not MISSING:
67+
setattr(self, name, field.default)
68+
elif field.init:
3269
raise TypeError(f"Missing required argument: {name}")
3370

71+
# Call __post_init__ if defined
72+
if hasattr(self, '__post_init__'):
73+
self.__post_init__()
74+
3475
# Generate __repr__ method
3576
def __repr__(self):
36-
fields = [
77+
fields_repr = [
3778
f"{name}={getattr(self, name)!r}"
38-
for name in annotations
79+
for name, field in fields.items()
80+
if field.repr
3981
]
40-
return f"{cls.__name__}({', '.join(fields)})"
82+
return f"{cls.__name__}({', '.join(fields_repr)})"
4183

4284
# Attach generated methods to class
4385
setattr(cls, '__init__', __init__)

internal_filesystem/lib/enum.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# for python-nostr
2+
3+
# enum.py: MicroPython compatibility layer for Python's enum module
4+
# Implements Enum and IntEnum for integer-based enumerations, avoiding metaclass keyword
5+
6+
class Enum:
7+
"""Base class for enumerations."""
8+
def __init__(self, value, name):
9+
self.value = value
10+
self.name = name
11+
12+
def __str__(self):
13+
return self.name
14+
15+
def __repr__(self):
16+
return f"<{self.__class__.__name__}.{self.name}: {self.value}>"
17+
18+
def __eq__(self, other):
19+
if isinstance(other, Enum):
20+
return self.value == other.value
21+
return self.value == other
22+
23+
def __hash__(self):
24+
return hash(self.value)
25+
26+
def create_enum_class(base_class, name, attrs):
27+
"""Factory function to create an enum class with metaclass-like behavior."""
28+
members = {}
29+
values = {}
30+
for key, value in attrs.items():
31+
if not key.startswith('__') and not callable(value):
32+
enum_item = base_class(value, key)
33+
members[key] = enum_item
34+
values[value] = enum_item
35+
attrs[key] = enum_item
36+
attrs['_members'] = members
37+
attrs['_values'] = values
38+
39+
# Define iteration and lookup
40+
def __iter__(cls):
41+
return iter(cls._members.values())
42+
43+
def __getitem__(cls, value):
44+
return cls._values.get(value)
45+
46+
attrs['__iter__'] = __iter__
47+
attrs['__getitem__'] = __getitem__
48+
49+
# Create class using type
50+
return type(name, (base_class,), attrs)
51+
52+
# Define IntEnum using factory function
53+
IntEnum = create_enum_class(Enum, 'IntEnum', {
54+
'__int__': lambda self: self.value
55+
})

0 commit comments

Comments
 (0)