pystructtype 0.2.0__tar.gz → 0.4.0__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.
Files changed (33) hide show
  1. {pystructtype-0.2.0 → pystructtype-0.4.0}/.github/workflows/build.yml +4 -4
  2. {pystructtype-0.2.0 → pystructtype-0.4.0}/.github/workflows/release.yml +1 -1
  3. {pystructtype-0.2.0 → pystructtype-0.4.0}/.gitignore +4 -0
  4. {pystructtype-0.2.0 → pystructtype-0.4.0}/PKG-INFO +41 -16
  5. {pystructtype-0.2.0 → pystructtype-0.4.0}/README.md +38 -12
  6. {pystructtype-0.2.0 → pystructtype-0.4.0}/docs/conf.py +3 -1
  7. {pystructtype-0.2.0 → pystructtype-0.4.0}/pyproject.toml +40 -27
  8. {pystructtype-0.2.0 → pystructtype-0.4.0}/src/pystructtype/__init__.py +4 -4
  9. pystructtype-0.4.0/src/pystructtype/bitstype.py +86 -0
  10. {pystructtype-0.2.0 → pystructtype-0.4.0}/src/pystructtype/structdataclass.py +103 -146
  11. {pystructtype-0.2.0 → pystructtype-0.4.0}/src/pystructtype/structtypes.py +65 -15
  12. {pystructtype-0.2.0 → pystructtype-0.4.0}/src/pystructtype/utils.py +8 -4
  13. {pystructtype-0.2.0 → pystructtype-0.4.0}/test/examples.py +14 -13
  14. pystructtype-0.4.0/test/test_bitstype.py +92 -0
  15. pystructtype-0.4.0/test/test_examples.py +9 -0
  16. pystructtype-0.4.0/test/test_structdataclass_additional.py +425 -0
  17. pystructtype-0.4.0/test/test_structtypes.py +150 -0
  18. pystructtype-0.4.0/test/test_utils.py +31 -0
  19. {pystructtype-0.2.0 → pystructtype-0.4.0}/tox.ini +2 -2
  20. pystructtype-0.4.0/uv.lock +797 -0
  21. pystructtype-0.2.0/.python-version +0 -1
  22. pystructtype-0.2.0/setup.cfg +0 -6
  23. pystructtype-0.2.0/src/pystructtype/bitstype.py +0 -142
  24. pystructtype-0.2.0/test/test_ctypes.py +0 -50
  25. pystructtype-0.2.0/uv.lock +0 -655
  26. {pystructtype-0.2.0 → pystructtype-0.4.0}/.readthedocs.yaml +0 -0
  27. {pystructtype-0.2.0 → pystructtype-0.4.0}/LICENSE.txt +0 -0
  28. {pystructtype-0.2.0 → pystructtype-0.4.0}/docs/autoapi_templates/python/module.rst_t +0 -0
  29. {pystructtype-0.2.0 → pystructtype-0.4.0}/docs/autoapi_templates/python/package.rst_t +0 -0
  30. {pystructtype-0.2.0 → pystructtype-0.4.0}/docs/index.rst +0 -0
  31. {pystructtype-0.2.0 → pystructtype-0.4.0}/src/pystructtype/py.typed +0 -0
  32. {pystructtype-0.2.0 → pystructtype-0.4.0}/test/__init__.py +0 -0
  33. {pystructtype-0.2.0 → pystructtype-0.4.0}/test/py.typed +0 -0
@@ -14,13 +14,13 @@ jobs:
14
14
  - name: Set up Python
15
15
  uses: actions/setup-python@v5
16
16
  with:
17
- python-version: 3.13
17
+ python-version: 3.14
18
18
 
19
19
  - name: Run Tests
20
20
  run: |
21
21
  uv sync
22
22
  uv tool install tox
23
- tox -e py313
23
+ tox -e py314
24
24
  check:
25
25
  runs-on: ubuntu-latest
26
26
  steps:
@@ -33,7 +33,7 @@ jobs:
33
33
  - name: Set up Python
34
34
  uses: actions/setup-python@v5
35
35
  with:
36
- python-version: 3.13
36
+ python-version: 3.14
37
37
 
38
38
  - name: Check Formatting
39
39
  run: |
@@ -53,7 +53,7 @@ jobs:
53
53
  - name: Set up Python
54
54
  uses: actions/setup-python@v5
55
55
  with:
56
- python-version: 3.13
56
+ python-version: 3.14
57
57
 
58
58
  - name: Check Formatting
59
59
  run: |
@@ -20,7 +20,7 @@ jobs:
20
20
  - name: Set up Python
21
21
  uses: actions/setup-python@v5
22
22
  with:
23
- python-version: 3.13
23
+ python-version: 3.14
24
24
 
25
25
  - name: Release
26
26
  run: |
@@ -10,7 +10,11 @@ wheels/
10
10
  .venv
11
11
 
12
12
  # Coverage
13
+ .coverage
13
14
  .coverage.*
14
15
 
15
16
  # PyCharm
16
17
  .idea*
18
+
19
+ # Extra
20
+ .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pystructtype
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Leverage Python Types to Define C-Struct Interfaces
5
5
  Project-URL: Homepage, https://github.com/fchorney/pystructtype
6
6
  Project-URL: Documentation, https://pystructtype.readthedocs.io/en/latest/
@@ -15,10 +15,9 @@ Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Natural Language :: English
17
17
  Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Topic :: Utilities
20
- Requires-Python: >=3.13
21
- Requires-Dist: loguru>=0.7.3
20
+ Requires-Python: <4.0,>=3.14
22
21
  Description-Content-Type: text/markdown
23
22
 
24
23
  # PyStructTypes
@@ -57,7 +56,6 @@ struct MyStruct {
57
56
  ```
58
57
 
59
58
  ```python
60
- @struct_dataclass
61
59
  class MyStruct(StructDataclass):
62
60
  myNum: int16_t
63
61
  myLetter: char_t
@@ -84,7 +82,6 @@ struct MyStruct {
84
82
  };
85
83
  ```
86
84
  ```python
87
- @struct_dataclass
88
85
  class MyStruct(StructDataclass):
89
86
  myInts: Annotated[list[uint8_t], TypeMeta(size=4)]
90
87
  myBiggerInts: Annotated[list[uint16_t], TypeMeta(size=2)]
@@ -110,7 +107,6 @@ struct MyStruct {
110
107
  ```
111
108
 
112
109
  ```python
113
- @struct_dataclass
114
110
  class MyStruct(StructDataclass):
115
111
  myInt: uint8_t = 5
116
112
  myInts: Annnotated[list[uint8_t], TypeMeta(size=2, default=1)]
@@ -121,6 +117,39 @@ s.decode([10, 5, 6])
121
117
  # MyStruct(myInt=10, myInts=[5, 6])
122
118
  ```
123
119
 
120
+ # String / char[] Type
121
+
122
+ Defining c-string types is a little different. Instead of using
123
+ `size` in the `TypeMeta`, we need to instead use `chunk_size`.
124
+
125
+ This is because the way the struct format is defined for c-strings needs
126
+ to know how big the string data is expected to be so that it can put the
127
+ whole string in a single variable.
128
+
129
+ The `chunk_size` is also introduced to allow for `char[][]` for converting
130
+ a list of strings.
131
+
132
+ ```c
133
+ struct MyStruct {
134
+ char myStr[3];
135
+ char myStrList[2][3];
136
+ };
137
+ ```
138
+ ```python
139
+ class MyStruct(StructDataclass):
140
+ myStr: Annotated[string_t, TypeMeta[str](chunk_size=3)]
141
+ myStrList: Annotated[list[string_t], TypeMeta[str](size=2, chunk_size=3)]
142
+
143
+
144
+ s = MyStruct()
145
+ s.decode([65, 66, 67, 68, 69, 70, 71, 72, 73])
146
+ # MyStruct(myStr=b"ABC", myStrList=[b"DEF", b"GHI"])
147
+ ```
148
+
149
+ If you instead try to define this as a list of `char_t` types,
150
+ you would only be able to end up with
151
+ `MyStruct(myStr=[b"A", b"B", b"C"], myStrList=[b"D", b"E", b"F", b"G", b"H", b"I"])`
152
+
124
153
  # The Bits Abstraction
125
154
 
126
155
  This library includes a `bits` abstraction to map bits to variables for easier access.
@@ -136,8 +165,9 @@ enum ConfigFlags {
136
165
  ```
137
166
 
138
167
  ```python
139
- @bits(uint8_t, {"lights_flag": 0, "platform_flag": 1})
140
- class FlagsType(BitsType): ...
168
+ class FlagsType(BitsType):
169
+ __bits_type__ = uint8_t
170
+ __bits_definition__ = {"lights_flag": 0, "platform_flag": 1}
141
171
 
142
172
  f = FlagsType()
143
173
  f.decode([3])
@@ -167,7 +197,6 @@ struct MyStruct {
167
197
  ```
168
198
 
169
199
  ```python
170
- @struct_dataclass
171
200
  class EnabledSensors(StructDataclass):
172
201
  # We can define the actual data we are ingesting here
173
202
  # This mirrors the `uint8_t enabledSensors[5]` data
@@ -228,7 +257,6 @@ s.decode([15, 15, 15, 15, 0])
228
257
  # [False, False, False, False],
229
258
  # [True, True, True, True],
230
259
  # [False, False, False, False],
231
- # [False, False, False, False]
232
260
  # ]
233
261
 
234
262
  # With the get/set functioned defined, we can access the data
@@ -254,15 +282,13 @@ struct LEDS {
254
282
  ```
255
283
 
256
284
  ```python
257
- @struct_dataclass
258
285
  class RGB(StructDataclass):
259
286
  r: uint8_t
260
287
  g: uint8_t
261
288
  b: uint8_t
262
289
 
263
- @struct_dataclass
264
290
  class LEDS(StructDataclass):
265
- lights: Annotated[list[RGB], TypeMeta(size=3])]
291
+ lights: Annotated[list[RGB], TypeMeta(size=3)]
266
292
 
267
293
  l = LEDS()
268
294
  l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
@@ -272,11 +298,10 @@ l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
272
298
  # Future Updates
273
299
 
274
300
  - Bitfield: Similar to the `Bits` abstraction. An easy way to define bitfields
275
- - C-Strings: Make a base class to handle C strings (arrays of chars)
276
301
  - Potentially more ways to define bits (dicts/lists/etc).
277
302
  - Potentially allowing list defaults to be entire pre-defined lists.
278
303
  - ???
279
304
 
280
305
  # Examples
281
306
 
282
- You can see a more fully fledged example in the `test/examples.py` file.
307
+ You can see a more fully fledged example in the `test/examples.py` file.
@@ -34,7 +34,6 @@ struct MyStruct {
34
34
  ```
35
35
 
36
36
  ```python
37
- @struct_dataclass
38
37
  class MyStruct(StructDataclass):
39
38
  myNum: int16_t
40
39
  myLetter: char_t
@@ -61,7 +60,6 @@ struct MyStruct {
61
60
  };
62
61
  ```
63
62
  ```python
64
- @struct_dataclass
65
63
  class MyStruct(StructDataclass):
66
64
  myInts: Annotated[list[uint8_t], TypeMeta(size=4)]
67
65
  myBiggerInts: Annotated[list[uint16_t], TypeMeta(size=2)]
@@ -87,7 +85,6 @@ struct MyStruct {
87
85
  ```
88
86
 
89
87
  ```python
90
- @struct_dataclass
91
88
  class MyStruct(StructDataclass):
92
89
  myInt: uint8_t = 5
93
90
  myInts: Annnotated[list[uint8_t], TypeMeta(size=2, default=1)]
@@ -98,6 +95,39 @@ s.decode([10, 5, 6])
98
95
  # MyStruct(myInt=10, myInts=[5, 6])
99
96
  ```
100
97
 
98
+ # String / char[] Type
99
+
100
+ Defining c-string types is a little different. Instead of using
101
+ `size` in the `TypeMeta`, we need to instead use `chunk_size`.
102
+
103
+ This is because the way the struct format is defined for c-strings needs
104
+ to know how big the string data is expected to be so that it can put the
105
+ whole string in a single variable.
106
+
107
+ The `chunk_size` is also introduced to allow for `char[][]` for converting
108
+ a list of strings.
109
+
110
+ ```c
111
+ struct MyStruct {
112
+ char myStr[3];
113
+ char myStrList[2][3];
114
+ };
115
+ ```
116
+ ```python
117
+ class MyStruct(StructDataclass):
118
+ myStr: Annotated[string_t, TypeMeta[str](chunk_size=3)]
119
+ myStrList: Annotated[list[string_t], TypeMeta[str](size=2, chunk_size=3)]
120
+
121
+
122
+ s = MyStruct()
123
+ s.decode([65, 66, 67, 68, 69, 70, 71, 72, 73])
124
+ # MyStruct(myStr=b"ABC", myStrList=[b"DEF", b"GHI"])
125
+ ```
126
+
127
+ If you instead try to define this as a list of `char_t` types,
128
+ you would only be able to end up with
129
+ `MyStruct(myStr=[b"A", b"B", b"C"], myStrList=[b"D", b"E", b"F", b"G", b"H", b"I"])`
130
+
101
131
  # The Bits Abstraction
102
132
 
103
133
  This library includes a `bits` abstraction to map bits to variables for easier access.
@@ -113,8 +143,9 @@ enum ConfigFlags {
113
143
  ```
114
144
 
115
145
  ```python
116
- @bits(uint8_t, {"lights_flag": 0, "platform_flag": 1})
117
- class FlagsType(BitsType): ...
146
+ class FlagsType(BitsType):
147
+ __bits_type__ = uint8_t
148
+ __bits_definition__ = {"lights_flag": 0, "platform_flag": 1}
118
149
 
119
150
  f = FlagsType()
120
151
  f.decode([3])
@@ -144,7 +175,6 @@ struct MyStruct {
144
175
  ```
145
176
 
146
177
  ```python
147
- @struct_dataclass
148
178
  class EnabledSensors(StructDataclass):
149
179
  # We can define the actual data we are ingesting here
150
180
  # This mirrors the `uint8_t enabledSensors[5]` data
@@ -205,7 +235,6 @@ s.decode([15, 15, 15, 15, 0])
205
235
  # [False, False, False, False],
206
236
  # [True, True, True, True],
207
237
  # [False, False, False, False],
208
- # [False, False, False, False]
209
238
  # ]
210
239
 
211
240
  # With the get/set functioned defined, we can access the data
@@ -231,15 +260,13 @@ struct LEDS {
231
260
  ```
232
261
 
233
262
  ```python
234
- @struct_dataclass
235
263
  class RGB(StructDataclass):
236
264
  r: uint8_t
237
265
  g: uint8_t
238
266
  b: uint8_t
239
267
 
240
- @struct_dataclass
241
268
  class LEDS(StructDataclass):
242
- lights: Annotated[list[RGB], TypeMeta(size=3])]
269
+ lights: Annotated[list[RGB], TypeMeta(size=3)]
243
270
 
244
271
  l = LEDS()
245
272
  l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
@@ -249,11 +276,10 @@ l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
249
276
  # Future Updates
250
277
 
251
278
  - Bitfield: Similar to the `Bits` abstraction. An easy way to define bitfields
252
- - C-Strings: Make a base class to handle C strings (arrays of chars)
253
279
  - Potentially more ways to define bits (dicts/lists/etc).
254
280
  - Potentially allowing list defaults to be entire pre-defined lists.
255
281
  - ???
256
282
 
257
283
  # Examples
258
284
 
259
- You can see a more fully fledged example in the `test/examples.py` file.
285
+ You can see a more fully fledged example in the `test/examples.py` file.
@@ -1,6 +1,8 @@
1
1
  import tomllib
2
2
  from datetime import datetime
3
3
 
4
+ from sphinx.application import Sphinx
5
+
4
6
  # Sphinx Base --------------------------------------------------------------------------
5
7
  # Extensions
6
8
  extensions = [
@@ -61,7 +63,7 @@ exclude_patterns = ["autoapi_templates"]
61
63
 
62
64
 
63
65
  # Add any Sphinx plugin settings here that don't have global variables exposed.
64
- def setup(app):
66
+ def setup(app: Sphinx) -> None:
65
67
  # App Settings ---------------------------------------------------------------------
66
68
  # Set source filetype(s)
67
69
  # Allow .rst files
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pystructtype"
3
- version = "0.2.0"
3
+ version = "0.4.0"
4
4
  description = "Leverage Python Types to Define C-Struct Interfaces"
5
5
  readme = "README.md"
6
6
  authors = [{name = "fchorney", email = "github@djsbx.com"}]
@@ -13,13 +13,11 @@ classifiers = [
13
13
  "Operating System :: OS Independent",
14
14
  "Intended Audience :: Developers",
15
15
  "Topic :: Utilities",
16
- "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3.14",
17
17
  ]
18
18
  keywords = ["struct", "cstruct", "type"]
19
- requires-python = ">=3.13"
20
- dependencies = [
21
- "loguru>=0.7.3",
22
- ]
19
+ requires-python = ">=3.14,<4.0"
20
+ dependencies = []
23
21
 
24
22
  [project.urls]
25
23
  Homepage = "https://github.com/fchorney/pystructtype"
@@ -29,22 +27,28 @@ Issues = "https://github.com/fchorney/pystructtype/issues"
29
27
 
30
28
  [dependency-groups]
31
29
  dev = [
32
- "coverage[toml]",
33
- "mypy",
34
- "pytest",
35
- "pytest-cov",
36
- "ruff",
37
- "sphinx",
38
- "sphinx-autoapi",
39
- "sphinx-rtd-theme",
40
- "tox",
41
- "tox-uv",
30
+ "coverage[toml]>=7.6.12",
31
+ "mypy>=1.19.1",
32
+ "pre-commit>=4.5.1",
33
+ "pytest>=9.0.2",
34
+ "pytest-cov>=6.3.0",
35
+ "ruff>=0.15.6",
36
+ "sphinx>=9.1.0",
37
+ "sphinx-autoapi>=3.8.0",
38
+ "sphinx-rtd-theme>=3.1.0",
39
+ "tox>=4.49.1",
40
+ "tox-uv>=1.33.4",
42
41
  ]
43
42
 
44
43
  [build-system]
45
44
  requires = ["hatchling"]
46
45
  build-backend = "hatchling.build"
47
46
 
47
+ [tool.mypy]
48
+ strict = false
49
+ check_untyped_defs = true
50
+ exclude = ["venv", ".venv"]
51
+
48
52
  [tool.coverage.run]
49
53
  relative_files = true
50
54
 
@@ -58,30 +62,39 @@ exclude_lines = ["if __name__ == .__main__.:", "def __str__", "def __repr__", "p
58
62
  package = true
59
63
 
60
64
  [tool.ruff]
65
+ target-version = "py314"
61
66
  line-length = 120
62
67
  indent-width = 4
63
-
64
- target-version = "py313"
68
+ exclude = []
65
69
 
66
70
  [tool.ruff.lint]
67
71
  select = [
68
- "F", # PyFlakes
69
- "E", # Error
70
- "I", # iSort
71
- "N", # Pep8-Naming
72
- "B", # Flake8-BugBear
73
- "UP", # PyUpgrade
74
- "RUF", # Ruff Specific Rules
72
+ "E", # pycodestyle errors
73
+ "W", # pycodestyle warnings
74
+ "F", # pyflakes
75
+ "I", # isort
76
+ "N", # pep8-naming
77
+ "B", # flake8-bugbear
78
+ "C4", # flake8-comprehensions
79
+ "UP", # pyupgrade
80
+ "ARG001", # unused arguments in functions
81
+ "RUF", # ruff specific rules
82
+ ]
83
+ ignore = [
84
+ "B008", # do not perform function calls in argument defaults
85
+ "W191", # indentation contains tabs
75
86
  ]
76
- ignore = []
77
87
  fixable = ["ALL"]
78
88
  unfixable = []
79
89
 
80
90
  [tool.ruff.format]
81
- # Like Black
82
91
  quote-style = "double"
83
92
  indent-style = "space"
84
93
  skip-magic-trailing-comma = false
85
94
  line-ending = "auto"
86
95
  docstring-code-format = true
87
96
  docstring-code-line-length = "dynamic"
97
+
98
+ [tool.ruff.lint.pyupgrade]
99
+ # Preserve types, even if a file imports `from __future__ import annotations`.
100
+ keep-runtime-typing = true
@@ -1,5 +1,5 @@
1
- from pystructtype.bitstype import BitsType, bits
2
- from pystructtype.structdataclass import StructDataclass, struct_dataclass
1
+ from pystructtype.bitstype import BitsType
2
+ from pystructtype.structdataclass import StructDataclass
3
3
  from pystructtype.structtypes import (
4
4
  TypeInfo,
5
5
  TypeMeta,
@@ -10,6 +10,7 @@ from pystructtype.structtypes import (
10
10
  int16_t,
11
11
  int32_t,
12
12
  int64_t,
13
+ string_t,
13
14
  uint8_t,
14
15
  uint16_t,
15
16
  uint32_t,
@@ -21,7 +22,6 @@ __all__ = [
21
22
  "StructDataclass",
22
23
  "TypeInfo",
23
24
  "TypeMeta",
24
- "bits",
25
25
  "char_t",
26
26
  "double_t",
27
27
  "float_t",
@@ -29,7 +29,7 @@ __all__ = [
29
29
  "int16_t",
30
30
  "int32_t",
31
31
  "int64_t",
32
- "struct_dataclass",
32
+ "string_t",
33
33
  "uint8_t",
34
34
  "uint16_t",
35
35
  "uint32_t",
@@ -0,0 +1,86 @@
1
+ import itertools
2
+ from collections.abc import Mapping
3
+ from dataclasses import field
4
+ from types import MappingProxyType
5
+ from typing import Annotated, ClassVar
6
+
7
+ from pystructtype.structdataclass import StructDataclass
8
+ from pystructtype.structtypes import TypeMeta
9
+ from pystructtype.utils import int_to_bool_list
10
+
11
+
12
+ class BitsType(StructDataclass):
13
+ """
14
+ Base class for bitfield structs. Subclasses must define __bits_type__ and __bits_definition__.
15
+ """
16
+
17
+ __bits_type__: ClassVar[type]
18
+ __bits_definition__: ClassVar[dict[str, int | list[int]] | Mapping[str, int | list[int]]]
19
+
20
+ _raw: int
21
+ _meta: dict[str, int | list[int]]
22
+
23
+ def __init_subclass__(cls, **kwargs):
24
+ super().__init_subclass__(**kwargs)
25
+ # Check for required attributes
26
+ if not hasattr(cls, "__bits_type__") or not hasattr(cls, "__bits_definition__"):
27
+ raise TypeError(
28
+ "Subclasses of BitsType must define __bits_type__ and __bits_definition__ class attributes."
29
+ )
30
+ bits_type = cls.__bits_type__
31
+ definition = cls.__bits_definition__
32
+
33
+ # Automatically wrap in MappingProxyType if it's a dict and not already immutable
34
+ if isinstance(definition, dict) and not isinstance(definition, MappingProxyType):
35
+ definition = MappingProxyType(definition)
36
+ cls.__bits_definition__ = definition
37
+
38
+ # # Remove __bits_type__ and __bits_definition__ from __annotations__ if present
39
+ # cls.__annotations__.pop("__bits_type__", None)
40
+ # cls.__annotations__.pop("__bits_definition__", None)
41
+
42
+ # Set the correct type for the raw data
43
+ cls._raw = 0
44
+ cls.__annotations__["_raw"] = bits_type
45
+
46
+ cls._meta = field(default_factory=dict)
47
+
48
+ # Create the defined attributes, defaults, and annotations in the class
49
+ for key, value in definition.items():
50
+ if isinstance(value, list):
51
+ setattr(
52
+ cls,
53
+ key,
54
+ field(default_factory=lambda v=len(value): [False for _ in range(v)]), # type: ignore
55
+ )
56
+ cls.__annotations__[key] = Annotated[list[bool], TypeMeta(size=len(value))]
57
+ else:
58
+ setattr(cls, key, False)
59
+ cls.__annotations__[key] = bool
60
+
61
+ def __post_init__(self) -> None:
62
+ super().__post_init__()
63
+ self._meta = dict(self.__bits_definition__)
64
+
65
+ def _decode(self, data: list[int]) -> None:
66
+ super()._decode(data)
67
+ bin_data = int_to_bool_list(self._raw, self._byte_length)
68
+ for k, v in self._meta.items():
69
+ if isinstance(v, list):
70
+ steps = [bin_data[idx] for idx in v]
71
+ setattr(self, k, steps)
72
+ else:
73
+ setattr(self, k, bin_data[v])
74
+
75
+ def _encode(self) -> list[int]:
76
+ bin_data = list(itertools.repeat(False, self._byte_length * 8))
77
+ for k, v in self._meta.items():
78
+ if isinstance(v, list):
79
+ steps = getattr(self, k)
80
+ for idx, bit_idx in enumerate(v):
81
+ bin_data[bit_idx] = steps[idx]
82
+ else:
83
+ bin_data[v] = getattr(self, k)
84
+ self._raw = sum(int(v) << i for i, v in enumerate(bin_data))
85
+ # Return _raw as a list of bytes (little-endian)
86
+ return super()._encode()