pystructtype 0.3.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.3.0 → pystructtype-0.4.0}/.github/workflows/build.yml +4 -4
  2. {pystructtype-0.3.0 → pystructtype-0.4.0}/.github/workflows/release.yml +1 -1
  3. {pystructtype-0.3.0 → pystructtype-0.4.0}/.gitignore +4 -0
  4. {pystructtype-0.3.0 → pystructtype-0.4.0}/PKG-INFO +8 -14
  5. {pystructtype-0.3.0 → pystructtype-0.4.0}/README.md +5 -11
  6. {pystructtype-0.3.0 → pystructtype-0.4.0}/docs/conf.py +3 -1
  7. {pystructtype-0.3.0 → pystructtype-0.4.0}/pyproject.toml +39 -24
  8. {pystructtype-0.3.0 → pystructtype-0.4.0}/src/pystructtype/__init__.py +2 -4
  9. pystructtype-0.4.0/src/pystructtype/bitstype.py +86 -0
  10. {pystructtype-0.3.0 → pystructtype-0.4.0}/src/pystructtype/structdataclass.py +84 -140
  11. {pystructtype-0.3.0 → pystructtype-0.4.0}/src/pystructtype/structtypes.py +50 -15
  12. {pystructtype-0.3.0 → pystructtype-0.4.0}/src/pystructtype/utils.py +8 -4
  13. {pystructtype-0.3.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.3.0 → pystructtype-0.4.0}/tox.ini +2 -2
  20. pystructtype-0.4.0/uv.lock +797 -0
  21. pystructtype-0.3.0/.python-version +0 -1
  22. pystructtype-0.3.0/setup.cfg +0 -6
  23. pystructtype-0.3.0/src/pystructtype/bitstype.py +0 -142
  24. pystructtype-0.3.0/test/test_ctypes.py +0 -76
  25. pystructtype-0.3.0/uv.lock +0 -629
  26. {pystructtype-0.3.0 → pystructtype-0.4.0}/.readthedocs.yaml +0 -0
  27. {pystructtype-0.3.0 → pystructtype-0.4.0}/LICENSE.txt +0 -0
  28. {pystructtype-0.3.0 → pystructtype-0.4.0}/docs/autoapi_templates/python/module.rst_t +0 -0
  29. {pystructtype-0.3.0 → pystructtype-0.4.0}/docs/autoapi_templates/python/package.rst_t +0 -0
  30. {pystructtype-0.3.0 → pystructtype-0.4.0}/docs/index.rst +0 -0
  31. {pystructtype-0.3.0 → pystructtype-0.4.0}/src/pystructtype/py.typed +0 -0
  32. {pystructtype-0.3.0 → pystructtype-0.4.0}/test/__init__.py +0 -0
  33. {pystructtype-0.3.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.3.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,9 +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
20
+ Requires-Python: <4.0,>=3.14
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
  # PyStructTypes
@@ -56,7 +56,6 @@ struct MyStruct {
56
56
  ```
57
57
 
58
58
  ```python
59
- @struct_dataclass
60
59
  class MyStruct(StructDataclass):
61
60
  myNum: int16_t
62
61
  myLetter: char_t
@@ -83,7 +82,6 @@ struct MyStruct {
83
82
  };
84
83
  ```
85
84
  ```python
86
- @struct_dataclass
87
85
  class MyStruct(StructDataclass):
88
86
  myInts: Annotated[list[uint8_t], TypeMeta(size=4)]
89
87
  myBiggerInts: Annotated[list[uint16_t], TypeMeta(size=2)]
@@ -109,7 +107,6 @@ struct MyStruct {
109
107
  ```
110
108
 
111
109
  ```python
112
- @struct_dataclass
113
110
  class MyStruct(StructDataclass):
114
111
  myInt: uint8_t = 5
115
112
  myInts: Annnotated[list[uint8_t], TypeMeta(size=2, default=1)]
@@ -139,7 +136,6 @@ struct MyStruct {
139
136
  };
140
137
  ```
141
138
  ```python
142
- @struct_dataclass
143
139
  class MyStruct(StructDataclass):
144
140
  myStr: Annotated[string_t, TypeMeta[str](chunk_size=3)]
145
141
  myStrList: Annotated[list[string_t], TypeMeta[str](size=2, chunk_size=3)]
@@ -169,8 +165,9 @@ enum ConfigFlags {
169
165
  ```
170
166
 
171
167
  ```python
172
- @bits(uint8_t, {"lights_flag": 0, "platform_flag": 1})
173
- class FlagsType(BitsType): ...
168
+ class FlagsType(BitsType):
169
+ __bits_type__ = uint8_t
170
+ __bits_definition__ = {"lights_flag": 0, "platform_flag": 1}
174
171
 
175
172
  f = FlagsType()
176
173
  f.decode([3])
@@ -200,7 +197,6 @@ struct MyStruct {
200
197
  ```
201
198
 
202
199
  ```python
203
- @struct_dataclass
204
200
  class EnabledSensors(StructDataclass):
205
201
  # We can define the actual data we are ingesting here
206
202
  # This mirrors the `uint8_t enabledSensors[5]` data
@@ -286,15 +282,13 @@ struct LEDS {
286
282
  ```
287
283
 
288
284
  ```python
289
- @struct_dataclass
290
285
  class RGB(StructDataclass):
291
286
  r: uint8_t
292
287
  g: uint8_t
293
288
  b: uint8_t
294
289
 
295
- @struct_dataclass
296
290
  class LEDS(StructDataclass):
297
- lights: Annotated[list[RGB], TypeMeta(size=3])]
291
+ lights: Annotated[list[RGB], TypeMeta(size=3)]
298
292
 
299
293
  l = LEDS()
300
294
  l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
@@ -310,4 +304,4 @@ l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
310
304
 
311
305
  # Examples
312
306
 
313
- 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)]
@@ -117,7 +114,6 @@ struct MyStruct {
117
114
  };
118
115
  ```
119
116
  ```python
120
- @struct_dataclass
121
117
  class MyStruct(StructDataclass):
122
118
  myStr: Annotated[string_t, TypeMeta[str](chunk_size=3)]
123
119
  myStrList: Annotated[list[string_t], TypeMeta[str](size=2, chunk_size=3)]
@@ -147,8 +143,9 @@ enum ConfigFlags {
147
143
  ```
148
144
 
149
145
  ```python
150
- @bits(uint8_t, {"lights_flag": 0, "platform_flag": 1})
151
- class FlagsType(BitsType): ...
146
+ class FlagsType(BitsType):
147
+ __bits_type__ = uint8_t
148
+ __bits_definition__ = {"lights_flag": 0, "platform_flag": 1}
152
149
 
153
150
  f = FlagsType()
154
151
  f.decode([3])
@@ -178,7 +175,6 @@ struct MyStruct {
178
175
  ```
179
176
 
180
177
  ```python
181
- @struct_dataclass
182
178
  class EnabledSensors(StructDataclass):
183
179
  # We can define the actual data we are ingesting here
184
180
  # This mirrors the `uint8_t enabledSensors[5]` data
@@ -264,15 +260,13 @@ struct LEDS {
264
260
  ```
265
261
 
266
262
  ```python
267
- @struct_dataclass
268
263
  class RGB(StructDataclass):
269
264
  r: uint8_t
270
265
  g: uint8_t
271
266
  b: uint8_t
272
267
 
273
- @struct_dataclass
274
268
  class LEDS(StructDataclass):
275
- lights: Annotated[list[RGB], TypeMeta(size=3])]
269
+ lights: Annotated[list[RGB], TypeMeta(size=3)]
276
270
 
277
271
  l = LEDS()
278
272
  l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
@@ -288,4 +282,4 @@ l.decode([1, 2, 3, 4, 5, 6, 7, 8, 9])
288
282
 
289
283
  # Examples
290
284
 
291
- 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.3.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,10 +13,10 @@ 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"
19
+ requires-python = ">=3.14,<4.0"
20
20
  dependencies = []
21
21
 
22
22
  [project.urls]
@@ -27,22 +27,28 @@ Issues = "https://github.com/fchorney/pystructtype/issues"
27
27
 
28
28
  [dependency-groups]
29
29
  dev = [
30
- "coverage[toml]",
31
- "mypy",
32
- "pytest",
33
- "pytest-cov",
34
- "ruff",
35
- "sphinx",
36
- "sphinx-autoapi",
37
- "sphinx-rtd-theme",
38
- "tox",
39
- "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",
40
41
  ]
41
42
 
42
43
  [build-system]
43
44
  requires = ["hatchling"]
44
45
  build-backend = "hatchling.build"
45
46
 
47
+ [tool.mypy]
48
+ strict = false
49
+ check_untyped_defs = true
50
+ exclude = ["venv", ".venv"]
51
+
46
52
  [tool.coverage.run]
47
53
  relative_files = true
48
54
 
@@ -56,30 +62,39 @@ exclude_lines = ["if __name__ == .__main__.:", "def __str__", "def __repr__", "p
56
62
  package = true
57
63
 
58
64
  [tool.ruff]
65
+ target-version = "py314"
59
66
  line-length = 120
60
67
  indent-width = 4
61
-
62
- target-version = "py313"
68
+ exclude = []
63
69
 
64
70
  [tool.ruff.lint]
65
71
  select = [
66
- "F", # PyFlakes
67
- "E", # Error
68
- "I", # iSort
69
- "N", # Pep8-Naming
70
- "B", # Flake8-BugBear
71
- "UP", # PyUpgrade
72
- "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
73
86
  ]
74
- ignore = []
75
87
  fixable = ["ALL"]
76
88
  unfixable = []
77
89
 
78
90
  [tool.ruff.format]
79
- # Like Black
80
91
  quote-style = "double"
81
92
  indent-style = "space"
82
93
  skip-magic-trailing-comma = false
83
94
  line-ending = "auto"
84
95
  docstring-code-format = true
85
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,
@@ -22,7 +22,6 @@ __all__ = [
22
22
  "StructDataclass",
23
23
  "TypeInfo",
24
24
  "TypeMeta",
25
- "bits",
26
25
  "char_t",
27
26
  "double_t",
28
27
  "float_t",
@@ -31,7 +30,6 @@ __all__ = [
31
30
  "int32_t",
32
31
  "int64_t",
33
32
  "string_t",
34
- "struct_dataclass",
35
33
  "uint8_t",
36
34
  "uint16_t",
37
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()