firstclass 0.0.1__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,104 +1,146 @@
1
- Metadata-Version: 2.4
2
- Name: firstclass
3
- Version: 0.0.1
4
- Summary: dataclass with bytes, ctypes, C struct, JSON and XML serialization
5
- Requires-Python: >=3.10
6
- Description-Content-Type: text/markdown
7
-
8
- # firstclass
9
-
10
- `@firstclass` works like `@dataclass` (same keyword args) and adds conversion to and from
11
- packed bytes, `ctypes`, a C struct header, JSON and XML. Pure Python 3.10+, no dependencies.
12
-
13
- ```python
14
- from firstclass import firstclass, uint8_t, uint16_t, int32_t, float32_t, bool_t, char_array, bytes_t, array_t
15
-
16
- @firstclass
17
- class Point:
18
- x: int32_t
19
- y: int32_t
20
-
21
- @firstclass
22
- class Packet:
23
- kind: uint8_t
24
- ok: bool_t
25
- temp: float32_t
26
- name: char_array[8] # NUL-padded UTF-8
27
- mac: bytes_t[6] # exactly 6 bytes
28
- samples: array_t[uint16_t, 4]
29
- origin: Point # nested
30
-
31
- p = Packet(1, True, 21.5, "sensor", b"\x00\x11\x22\x33\x44\x55", [1, 2, 3, 4], Point(10, -20))
32
-
33
- raw = p.to_bytes() # packed, little-endian
34
- assert Packet.from_bytes(raw) == p
35
- Packet.sizeof() # 36
36
- ```
37
-
38
- ## Types
39
-
40
- | Marker | C | Bytes |
41
- |---|---|---|
42
- | `uint8_t` `uint16_t` `uint32_t` `uint64_t` | `uintN_t` | 1/2/4/8 |
43
- | `int8_t` `int16_t` `int32_t` `int64_t` | `intN_t` | 1/2/4/8 |
44
- | `float32_t` / `float64_t` | `float` / `double` | 4 / 8 |
45
- | `bool_t` | `bool` | 1 |
46
- | `char_t` | `char` | 1 |
47
- | `char_array[N]` | `char x[N]` | N (UTF-8, NUL-padded) |
48
- | `bytes_t[N]` | `uint8_t x[N]` | N |
49
- | `array_t[T, N]` | `T x[N]` | N * size(T) |
50
- | another `@firstclass` class | `struct Name` | its size |
51
-
52
- Values are checked: out of range ints, over-long strings and wrong lengths raise
53
- `ValueError`; wrong Python types raise `TypeError`. Nothing is truncated or coerced silently.
54
-
55
- Raw `ctypes` types work as annotations too (packed, same layout as a `_pack_ = 1` Structure):
56
- `ctypes.c_int`, `c_uint64`, `c_size_t`, `c_bool`, `c_float`, `c_double`, `c_char`, `c_uint16 * 4`,
57
- `c_char * 8`. Pointers (`c_void_p`, `POINTER(T)`, `c_char_p`) are stored as the address (an `int`,
58
- 0 = NULL) at pointer size; `to_ctypes()` gives a real pointer field you can dereference. The
59
- address only means something inside the same process. Sizes follow the platform
60
- (`c_long` is 4 bytes on Windows, 8 on Linux).
61
-
62
- ## Methods added to every class
63
-
64
- | Method | |
65
- |---|---|
66
- | `obj.to_bytes()` / `Cls.from_bytes(data)` | binary; `from_bytes` needs exactly `sizeof()` bytes |
67
- | `Cls.sizeof()` | size in bytes |
68
- | `obj.to_dict()` / `Cls.from_dict(d)` | JSON-compatible dict (bytes as hex) |
69
- | `obj.to_json(**kw)` / `Cls.from_json(s)` | `kw` goes to `json.dumps` |
70
- | `obj.to_xml(indent=None)` / `Cls.from_xml(s)` | `<Packet><kind>1</kind>...</Packet>`, arrays as `<item>` |
71
- | `obj.to_ctypes()` / `Cls.from_ctypes(c)` / `Cls.ctypes_type()` | `ctypes.Structure` with matching layout |
72
- | `Cls.c_header()` | C source for the struct(s), nested ones first |
73
-
74
- ## Layout options
75
-
76
- ```python
77
- @firstclass(byteorder="big") # default "little"
78
- @firstclass(align=True) # default False = packed, no padding (like __attribute__((packed)))
79
- @firstclass(frozen=True, slots=True) # any dataclass option
80
- ```
81
-
82
- `align=True` inserts natural-alignment padding like a normal C struct (assumes the x86-64 style
83
- rule: alignment equals size). Padding bytes are written as zero and ignored on read.
84
- C headers describe the struct only; C compilers use native byte order, so `byteorder` is noted
85
- in a comment.
86
-
87
- ## Notes
88
-
89
- - Fields with plain types (`str`, `int`) work for JSON/XML only; `to_bytes()` raises
90
- `TypeError` naming the field.
91
- - Use `from __future__ import annotations` freely; annotations are resolved lazily. Classes
92
- defined inside functions that refer to local names are not resolvable.
93
- - Don't parse untrusted XML with `from_xml` without extra care (stdlib `xml.etree`).
94
- - Field names that collide with the generated methods (`to_bytes`, ...) raise `TypeError`.
95
-
96
- ## Example
97
-
98
- `python examples/example.py` shows bytes, JSON, XML, ctypes and the C header from one class.
99
-
100
- ## Develop
101
-
102
- ```
103
- python -m unittest discover -s tests
104
- ```
1
+ Metadata-Version: 2.4
2
+ Name: firstclass
3
+ Version: 0.0.3
4
+ Summary: dataclass with bytes, ctypes, C struct, JSON and XML serialization
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # firstclass
9
+
10
+ `@firstclass` works like `@dataclass` (same keyword args) and adds conversion to and from
11
+ packed bytes, `ctypes`, a C struct header, JSON and XML. Pure Python 3.10+, no dependencies.
12
+
13
+ ```python
14
+ from firstclass import firstclass, uint8_t, uint16_t, int32_t, float32_t, bool_t, char_array, bytes_t, array_t
15
+
16
+ @firstclass
17
+ class Point:
18
+ x: int32_t
19
+ y: int32_t
20
+
21
+ @firstclass
22
+ class Packet:
23
+ kind: uint8_t
24
+ ok: bool_t
25
+ temp: float32_t
26
+ name: char_array[8] # NUL-padded UTF-8
27
+ mac: bytes_t[6] # exactly 6 bytes
28
+ samples: array_t[uint16_t, 4]
29
+ origin: Point # nested
30
+
31
+ p = Packet(1, True, 21.5, "sensor", b"\x00\x11\x22\x33\x44\x55", [1, 2, 3, 4], Point(10, -20))
32
+
33
+ raw = p.to_bytes() # packed, little-endian
34
+ assert Packet.from_bytes(raw) == p
35
+ Packet.sizeof() # 36
36
+ ```
37
+
38
+ ## Types
39
+
40
+ | Marker | C | Bytes |
41
+ |---|---|---|
42
+ | `uint8_t` `uint16_t` `uint32_t` `uint64_t` | `uintN_t` | 1/2/4/8 |
43
+ | `int8_t` `int16_t` `int32_t` `int64_t` | `intN_t` | 1/2/4/8 |
44
+ | `float32_t` / `float64_t` | `float` / `double` | 4 / 8 |
45
+ | `bool_t` | `bool` | 1 |
46
+ | `char_t` | `char` | 1 |
47
+ | `char_array[N]` | `char x[N]` | N (UTF-8, NUL-padded) |
48
+ | `bytes_t[N]` | `uint8_t x[N]` | N |
49
+ | `array_t[T, N]` | `T x[N]` | N * size(T) |
50
+ | `enum_t[Color, uint8_t]` | `uint8_t x` (+ `enum Color {..}` in header) | storage size |
51
+ | `bitfield[uint8_t, 3]` | `uint8_t x : 3` | shares a storage unit |
52
+ | another `@firstclass` class | `struct Name` | its size |
53
+
54
+ Values are checked: out of range ints, over-long strings and wrong lengths raise
55
+ `ValueError`; wrong Python types raise `TypeError`. Nothing is truncated or coerced silently.
56
+
57
+ **Enums.** Int-valued `Enum`/`IntEnum`/`IntFlag` classes. `enum_t[Color, uint8_t]` picks the width;
58
+ a bare `Color` annotation is 32-bit like a C `int` (unsigned unless a value is negative). Values
59
+ are stored, and written to JSON/XML, as ints; reading an unknown value raises `ValueError`.
60
+ `c_header()` emits `enum Color { Color_RED = 1, ... };`.
61
+
62
+ **Bitfields.** `bitfield[uint8_t, 3]` is 3 bits of a `uint8_t` unit, like `uint8_t x : 3`.
63
+ Neighbouring bitfields with the same storage type share a unit while they fit; otherwise a new
64
+ unit starts. Little-endian fills from the lowest bit, big-endian from the highest (what ctypes
65
+ does). Signed storage (`int8_t`) gives signed fields. Mixing storage types in one run is not
66
+ portable across C compilers; keep a run on one type. No arrays of bitfields.
67
+
68
+ Raw `ctypes` types work as annotations too (packed, same layout as a `_pack_ = 1` Structure):
69
+ `ctypes.c_int`, `c_uint64`, `c_size_t`, `c_bool`, `c_float`, `c_double`, `c_char`, `c_uint16 * 4`,
70
+ `c_char * 8`. Pointers (`c_void_p`, `POINTER(T)`, `c_char_p`) are stored as the address (an `int`,
71
+ 0 = NULL) at pointer size; `to_ctypes()` gives a real pointer field you can dereference. The
72
+ address only means something inside the same process. Sizes follow the platform
73
+ (`c_long` is 4 bytes on Windows, 8 on Linux).
74
+
75
+ ## Methods added to every class
76
+
77
+ | Method | |
78
+ |---|---|
79
+ | `obj.to_bytes()` / `Cls.from_bytes(data)` | binary; `from_bytes` needs exactly `sizeof()` bytes |
80
+ | `Cls.unpack_from(buffer, offset=0)` | like `struct.unpack_from`: read one object at `offset`, trailing bytes are fine; any buffer type |
81
+ | `Cls.sizeof()` | size in bytes |
82
+ | `obj.to_dict()` / `Cls.from_dict(d)` | JSON-compatible dict (bytes as hex) |
83
+ | `obj.to_json(**kw)` / `Cls.from_json(s)` | `kw` goes to `json.dumps` |
84
+ | `obj.to_xml(indent=None)` / `Cls.from_xml(s)` | `<Packet><kind>1</kind>...</Packet>`, arrays as `<item>` |
85
+ | `obj.to_ctypes()` / `Cls.from_ctypes(c)` / `Cls.ctypes_type()` | `ctypes.Structure` with matching layout |
86
+ | `Cls.c_header()` | C source for the struct(s), nested ones first |
87
+
88
+ ## Import C/C++ headers
89
+
90
+ ```python
91
+ from firstclass import from_header # also: firstclass.from_header
92
+
93
+ t = from_header("protocol.h") # or from_header(text="struct P { ... };")
94
+ pkt = t.Packet(1, True, ...) # structs -> @firstclass classes
95
+ t.Color.RED # enums / enum class -> IntEnum
96
+ t.constants["MAX_LEN"] # integer #defines, consts, enum members
97
+ t.skipped # {"name": "reason"} for anything not convertible
98
+ ```
99
+
100
+ Understood: `struct`/`class`/`typedef struct`, nested and anonymous-member structs, `enum` and
101
+ `enum class : type` (explicit values, implicit increments, constant expressions), bitfields, arrays
102
+ (multi-dimensional, sizes from `#define`/enum/`const`/`sizeof`), `char[N]` -> `char_array`,
103
+ `uint8_t[N]` -> `bytes_t`, pointers and function pointers (stored as addresses), `typedef`/`using`,
104
+ `#define`/`#if`/`#ifdef`/`#elif` (preset macros with `defines={...}`), `namespace`, `extern "C"`,
105
+ `#pragma pack(push/pop/N)` and `__attribute__((packed))`. Layout follows C: natural alignment
106
+ padding unless the struct is packed; force it with `align=True/False`. Options: `byteorder`,
107
+ `long_size` (size of C `long`; default is this machine's).
108
+
109
+ Skipped with a reason instead of failing: unions, flexible array members, unnamed padding
110
+ bitfields, inheritance, virtual members, unknown types (`#include`d headers are not read), `pack(2)`
111
+ /`pack(4)`, `aligned(N)`, `long double`, empty structs, anything depending on a skipped struct.
112
+ Plain C enums are 32-bit like C unless `: type` is given. C field names that are Python keywords
113
+ (`class`, `from`, ...) get a trailing underscore (`class_`).
114
+
115
+ ## Layout options
116
+
117
+ ```python
118
+ @firstclass(byteorder="big") # default "little"
119
+ @firstclass(align=True) # default False = packed, no padding (like __attribute__((packed)))
120
+ @firstclass(frozen=True, slots=True) # any dataclass option
121
+ ```
122
+
123
+ `align=True` inserts natural-alignment padding like a normal C struct (assumes the x86-64 style
124
+ rule: alignment equals size). Padding bytes are written as zero and ignored on read.
125
+ C headers describe the struct only; C compilers use native byte order, so `byteorder` is noted
126
+ in a comment.
127
+
128
+ ## Notes
129
+
130
+ - Fields with plain types (`str`, `int`) work for JSON/XML only; `to_bytes()` raises
131
+ `TypeError` naming the field.
132
+ - Use `from __future__ import annotations` freely; annotations are resolved lazily. Classes
133
+ defined inside functions that refer to local names are not resolvable.
134
+ - Don't parse untrusted XML with `from_xml` without extra care (stdlib `xml.etree`).
135
+ - Field names that collide with the generated methods (`to_bytes`, ...) raise `TypeError`.
136
+
137
+ ## Example
138
+
139
+ `python examples/example.py` defines a class in Python, then imports `examples/protocol.h` (enums, `enum class`,
140
+ bitfields, `#pragma pack` structs) and uses the generated classes.
141
+
142
+ ## Develop
143
+
144
+ ```
145
+ python -m unittest discover -s tests
146
+ ```
@@ -1,97 +1,139 @@
1
- # firstclass
2
-
3
- `@firstclass` works like `@dataclass` (same keyword args) and adds conversion to and from
4
- packed bytes, `ctypes`, a C struct header, JSON and XML. Pure Python 3.10+, no dependencies.
5
-
6
- ```python
7
- from firstclass import firstclass, uint8_t, uint16_t, int32_t, float32_t, bool_t, char_array, bytes_t, array_t
8
-
9
- @firstclass
10
- class Point:
11
- x: int32_t
12
- y: int32_t
13
-
14
- @firstclass
15
- class Packet:
16
- kind: uint8_t
17
- ok: bool_t
18
- temp: float32_t
19
- name: char_array[8] # NUL-padded UTF-8
20
- mac: bytes_t[6] # exactly 6 bytes
21
- samples: array_t[uint16_t, 4]
22
- origin: Point # nested
23
-
24
- p = Packet(1, True, 21.5, "sensor", b"\x00\x11\x22\x33\x44\x55", [1, 2, 3, 4], Point(10, -20))
25
-
26
- raw = p.to_bytes() # packed, little-endian
27
- assert Packet.from_bytes(raw) == p
28
- Packet.sizeof() # 36
29
- ```
30
-
31
- ## Types
32
-
33
- | Marker | C | Bytes |
34
- |---|---|---|
35
- | `uint8_t` `uint16_t` `uint32_t` `uint64_t` | `uintN_t` | 1/2/4/8 |
36
- | `int8_t` `int16_t` `int32_t` `int64_t` | `intN_t` | 1/2/4/8 |
37
- | `float32_t` / `float64_t` | `float` / `double` | 4 / 8 |
38
- | `bool_t` | `bool` | 1 |
39
- | `char_t` | `char` | 1 |
40
- | `char_array[N]` | `char x[N]` | N (UTF-8, NUL-padded) |
41
- | `bytes_t[N]` | `uint8_t x[N]` | N |
42
- | `array_t[T, N]` | `T x[N]` | N * size(T) |
43
- | another `@firstclass` class | `struct Name` | its size |
44
-
45
- Values are checked: out of range ints, over-long strings and wrong lengths raise
46
- `ValueError`; wrong Python types raise `TypeError`. Nothing is truncated or coerced silently.
47
-
48
- Raw `ctypes` types work as annotations too (packed, same layout as a `_pack_ = 1` Structure):
49
- `ctypes.c_int`, `c_uint64`, `c_size_t`, `c_bool`, `c_float`, `c_double`, `c_char`, `c_uint16 * 4`,
50
- `c_char * 8`. Pointers (`c_void_p`, `POINTER(T)`, `c_char_p`) are stored as the address (an `int`,
51
- 0 = NULL) at pointer size; `to_ctypes()` gives a real pointer field you can dereference. The
52
- address only means something inside the same process. Sizes follow the platform
53
- (`c_long` is 4 bytes on Windows, 8 on Linux).
54
-
55
- ## Methods added to every class
56
-
57
- | Method | |
58
- |---|---|
59
- | `obj.to_bytes()` / `Cls.from_bytes(data)` | binary; `from_bytes` needs exactly `sizeof()` bytes |
60
- | `Cls.sizeof()` | size in bytes |
61
- | `obj.to_dict()` / `Cls.from_dict(d)` | JSON-compatible dict (bytes as hex) |
62
- | `obj.to_json(**kw)` / `Cls.from_json(s)` | `kw` goes to `json.dumps` |
63
- | `obj.to_xml(indent=None)` / `Cls.from_xml(s)` | `<Packet><kind>1</kind>...</Packet>`, arrays as `<item>` |
64
- | `obj.to_ctypes()` / `Cls.from_ctypes(c)` / `Cls.ctypes_type()` | `ctypes.Structure` with matching layout |
65
- | `Cls.c_header()` | C source for the struct(s), nested ones first |
66
-
67
- ## Layout options
68
-
69
- ```python
70
- @firstclass(byteorder="big") # default "little"
71
- @firstclass(align=True) # default False = packed, no padding (like __attribute__((packed)))
72
- @firstclass(frozen=True, slots=True) # any dataclass option
73
- ```
74
-
75
- `align=True` inserts natural-alignment padding like a normal C struct (assumes the x86-64 style
76
- rule: alignment equals size). Padding bytes are written as zero and ignored on read.
77
- C headers describe the struct only; C compilers use native byte order, so `byteorder` is noted
78
- in a comment.
79
-
80
- ## Notes
81
-
82
- - Fields with plain types (`str`, `int`) work for JSON/XML only; `to_bytes()` raises
83
- `TypeError` naming the field.
84
- - Use `from __future__ import annotations` freely; annotations are resolved lazily. Classes
85
- defined inside functions that refer to local names are not resolvable.
86
- - Don't parse untrusted XML with `from_xml` without extra care (stdlib `xml.etree`).
87
- - Field names that collide with the generated methods (`to_bytes`, ...) raise `TypeError`.
88
-
89
- ## Example
90
-
91
- `python examples/example.py` shows bytes, JSON, XML, ctypes and the C header from one class.
92
-
93
- ## Develop
94
-
95
- ```
96
- python -m unittest discover -s tests
97
- ```
1
+ # firstclass
2
+
3
+ `@firstclass` works like `@dataclass` (same keyword args) and adds conversion to and from
4
+ packed bytes, `ctypes`, a C struct header, JSON and XML. Pure Python 3.10+, no dependencies.
5
+
6
+ ```python
7
+ from firstclass import firstclass, uint8_t, uint16_t, int32_t, float32_t, bool_t, char_array, bytes_t, array_t
8
+
9
+ @firstclass
10
+ class Point:
11
+ x: int32_t
12
+ y: int32_t
13
+
14
+ @firstclass
15
+ class Packet:
16
+ kind: uint8_t
17
+ ok: bool_t
18
+ temp: float32_t
19
+ name: char_array[8] # NUL-padded UTF-8
20
+ mac: bytes_t[6] # exactly 6 bytes
21
+ samples: array_t[uint16_t, 4]
22
+ origin: Point # nested
23
+
24
+ p = Packet(1, True, 21.5, "sensor", b"\x00\x11\x22\x33\x44\x55", [1, 2, 3, 4], Point(10, -20))
25
+
26
+ raw = p.to_bytes() # packed, little-endian
27
+ assert Packet.from_bytes(raw) == p
28
+ Packet.sizeof() # 36
29
+ ```
30
+
31
+ ## Types
32
+
33
+ | Marker | C | Bytes |
34
+ |---|---|---|
35
+ | `uint8_t` `uint16_t` `uint32_t` `uint64_t` | `uintN_t` | 1/2/4/8 |
36
+ | `int8_t` `int16_t` `int32_t` `int64_t` | `intN_t` | 1/2/4/8 |
37
+ | `float32_t` / `float64_t` | `float` / `double` | 4 / 8 |
38
+ | `bool_t` | `bool` | 1 |
39
+ | `char_t` | `char` | 1 |
40
+ | `char_array[N]` | `char x[N]` | N (UTF-8, NUL-padded) |
41
+ | `bytes_t[N]` | `uint8_t x[N]` | N |
42
+ | `array_t[T, N]` | `T x[N]` | N * size(T) |
43
+ | `enum_t[Color, uint8_t]` | `uint8_t x` (+ `enum Color {..}` in header) | storage size |
44
+ | `bitfield[uint8_t, 3]` | `uint8_t x : 3` | shares a storage unit |
45
+ | another `@firstclass` class | `struct Name` | its size |
46
+
47
+ Values are checked: out of range ints, over-long strings and wrong lengths raise
48
+ `ValueError`; wrong Python types raise `TypeError`. Nothing is truncated or coerced silently.
49
+
50
+ **Enums.** Int-valued `Enum`/`IntEnum`/`IntFlag` classes. `enum_t[Color, uint8_t]` picks the width;
51
+ a bare `Color` annotation is 32-bit like a C `int` (unsigned unless a value is negative). Values
52
+ are stored, and written to JSON/XML, as ints; reading an unknown value raises `ValueError`.
53
+ `c_header()` emits `enum Color { Color_RED = 1, ... };`.
54
+
55
+ **Bitfields.** `bitfield[uint8_t, 3]` is 3 bits of a `uint8_t` unit, like `uint8_t x : 3`.
56
+ Neighbouring bitfields with the same storage type share a unit while they fit; otherwise a new
57
+ unit starts. Little-endian fills from the lowest bit, big-endian from the highest (what ctypes
58
+ does). Signed storage (`int8_t`) gives signed fields. Mixing storage types in one run is not
59
+ portable across C compilers; keep a run on one type. No arrays of bitfields.
60
+
61
+ Raw `ctypes` types work as annotations too (packed, same layout as a `_pack_ = 1` Structure):
62
+ `ctypes.c_int`, `c_uint64`, `c_size_t`, `c_bool`, `c_float`, `c_double`, `c_char`, `c_uint16 * 4`,
63
+ `c_char * 8`. Pointers (`c_void_p`, `POINTER(T)`, `c_char_p`) are stored as the address (an `int`,
64
+ 0 = NULL) at pointer size; `to_ctypes()` gives a real pointer field you can dereference. The
65
+ address only means something inside the same process. Sizes follow the platform
66
+ (`c_long` is 4 bytes on Windows, 8 on Linux).
67
+
68
+ ## Methods added to every class
69
+
70
+ | Method | |
71
+ |---|---|
72
+ | `obj.to_bytes()` / `Cls.from_bytes(data)` | binary; `from_bytes` needs exactly `sizeof()` bytes |
73
+ | `Cls.unpack_from(buffer, offset=0)` | like `struct.unpack_from`: read one object at `offset`, trailing bytes are fine; any buffer type |
74
+ | `Cls.sizeof()` | size in bytes |
75
+ | `obj.to_dict()` / `Cls.from_dict(d)` | JSON-compatible dict (bytes as hex) |
76
+ | `obj.to_json(**kw)` / `Cls.from_json(s)` | `kw` goes to `json.dumps` |
77
+ | `obj.to_xml(indent=None)` / `Cls.from_xml(s)` | `<Packet><kind>1</kind>...</Packet>`, arrays as `<item>` |
78
+ | `obj.to_ctypes()` / `Cls.from_ctypes(c)` / `Cls.ctypes_type()` | `ctypes.Structure` with matching layout |
79
+ | `Cls.c_header()` | C source for the struct(s), nested ones first |
80
+
81
+ ## Import C/C++ headers
82
+
83
+ ```python
84
+ from firstclass import from_header # also: firstclass.from_header
85
+
86
+ t = from_header("protocol.h") # or from_header(text="struct P { ... };")
87
+ pkt = t.Packet(1, True, ...) # structs -> @firstclass classes
88
+ t.Color.RED # enums / enum class -> IntEnum
89
+ t.constants["MAX_LEN"] # integer #defines, consts, enum members
90
+ t.skipped # {"name": "reason"} for anything not convertible
91
+ ```
92
+
93
+ Understood: `struct`/`class`/`typedef struct`, nested and anonymous-member structs, `enum` and
94
+ `enum class : type` (explicit values, implicit increments, constant expressions), bitfields, arrays
95
+ (multi-dimensional, sizes from `#define`/enum/`const`/`sizeof`), `char[N]` -> `char_array`,
96
+ `uint8_t[N]` -> `bytes_t`, pointers and function pointers (stored as addresses), `typedef`/`using`,
97
+ `#define`/`#if`/`#ifdef`/`#elif` (preset macros with `defines={...}`), `namespace`, `extern "C"`,
98
+ `#pragma pack(push/pop/N)` and `__attribute__((packed))`. Layout follows C: natural alignment
99
+ padding unless the struct is packed; force it with `align=True/False`. Options: `byteorder`,
100
+ `long_size` (size of C `long`; default is this machine's).
101
+
102
+ Skipped with a reason instead of failing: unions, flexible array members, unnamed padding
103
+ bitfields, inheritance, virtual members, unknown types (`#include`d headers are not read), `pack(2)`
104
+ /`pack(4)`, `aligned(N)`, `long double`, empty structs, anything depending on a skipped struct.
105
+ Plain C enums are 32-bit like C unless `: type` is given. C field names that are Python keywords
106
+ (`class`, `from`, ...) get a trailing underscore (`class_`).
107
+
108
+ ## Layout options
109
+
110
+ ```python
111
+ @firstclass(byteorder="big") # default "little"
112
+ @firstclass(align=True) # default False = packed, no padding (like __attribute__((packed)))
113
+ @firstclass(frozen=True, slots=True) # any dataclass option
114
+ ```
115
+
116
+ `align=True` inserts natural-alignment padding like a normal C struct (assumes the x86-64 style
117
+ rule: alignment equals size). Padding bytes are written as zero and ignored on read.
118
+ C headers describe the struct only; C compilers use native byte order, so `byteorder` is noted
119
+ in a comment.
120
+
121
+ ## Notes
122
+
123
+ - Fields with plain types (`str`, `int`) work for JSON/XML only; `to_bytes()` raises
124
+ `TypeError` naming the field.
125
+ - Use `from __future__ import annotations` freely; annotations are resolved lazily. Classes
126
+ defined inside functions that refer to local names are not resolvable.
127
+ - Don't parse untrusted XML with `from_xml` without extra care (stdlib `xml.etree`).
128
+ - Field names that collide with the generated methods (`to_bytes`, ...) raise `TypeError`.
129
+
130
+ ## Example
131
+
132
+ `python examples/example.py` defines a class in Python, then imports `examples/protocol.h` (enums, `enum class`,
133
+ bitfields, `#pragma pack` structs) and uses the generated classes.
134
+
135
+ ## Develop
136
+
137
+ ```
138
+ python -m unittest discover -s tests
139
+ ```
@@ -1,10 +1,13 @@
1
1
  from ._core import firstclass
2
+ from ._header import from_header
2
3
  from ._specs import (
3
4
  array_t,
5
+ bitfield,
4
6
  bool_t,
5
7
  bytes_t,
6
8
  char_array,
7
9
  char_t,
10
+ enum_t,
8
11
  float32_t,
9
12
  float64_t,
10
13
  int8_t,
@@ -18,9 +21,11 @@ from ._specs import (
18
21
  )
19
22
 
20
23
  __all__ = [
21
- "firstclass",
24
+ "firstclass", "from_header",
22
25
  "uint8_t", "uint16_t", "uint32_t", "uint64_t",
23
26
  "int8_t", "int16_t", "int32_t", "int64_t",
24
27
  "float32_t", "float64_t", "bool_t", "char_t",
25
- "char_array", "bytes_t", "array_t",
28
+ "char_array", "bytes_t", "array_t", "bitfield", "enum_t",
26
29
  ]
30
+
31
+ firstclass.from_header = from_header # also reachable as the decorator's attribute