Hydrogenlib-Objective-Struct 0.0.1__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.
@@ -0,0 +1,17 @@
1
+ # Dist and build files
2
+ /**/__pycache__/
3
+ /**/dist/
4
+ /**/build/
5
+ /**/.venv/
6
+
7
+ # IDE settings
8
+ /.idea/
9
+ /.vscode/
10
+
11
+ # Files in Dev
12
+ /**/.temp/
13
+ /**/.tests/
14
+ /**/tests
15
+ /**/dev-scripts/
16
+ /**/uv.lock
17
+ /**/.hydro-ignore
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: Hydrogenlib-Objective-Struct
3
+ Version: 0.0.1
4
+ Project-URL: Documentation, https://github.com/LittleNightSong/HydrogenLib#readme
5
+ Project-URL: Issues, https://github.com/LittleNightSong/HydrogenLib/issues
6
+ Project-URL: Source, https://github.com/LittleNightSong/HydrogenLib
7
+ Author-email: LittleNightSong <LittleNightSongYO@outlook.com>
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: Implementation :: CPython
14
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
15
+ Requires-Python: >=3.12
File without changes
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "Hydrogenlib-Objective-Struct"
7
+ dynamic = ["version"]
8
+ description = ''
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ keywords = []
12
+ classifiers = ["Development Status :: 3 - Alpha", "Programming Language :: Python", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"]
13
+ dependencies = []
14
+
15
+ [[project.authors]]
16
+ name = "LittleNightSong"
17
+ email = "LittleNightSongYO@outlook.com"
18
+ [project.urls]
19
+ Documentation = "https://github.com/LittleNightSong/HydrogenLib#readme"
20
+ Issues = "https://github.com/LittleNightSong/HydrogenLib/issues"
21
+ Source = "https://github.com/LittleNightSong/HydrogenLib"
22
+
23
+ [tool.hatch.version]
24
+ path = "src/_hydrogenlib_objective_struct/__about__.py"
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["src/_hydrogenlib_objective_struct"]
28
+
@@ -0,0 +1,3 @@
1
+ from .methods import pack, unpack
2
+ from .objective_struct import Struct
3
+ from .types import *
@@ -0,0 +1,26 @@
1
+ import struct
2
+ import typing
3
+ from collections.abc import Buffer
4
+ from typing import Any
5
+
6
+
7
+ class _CType(typing.Protocol):
8
+ __ctype__: str
9
+
10
+
11
+ def to_format_string(format: tuple[str | _CType, ...]):
12
+ format = ''.join(
13
+ map(
14
+ lambda x: x if isinstance(x, str) else x.__ctype__,
15
+ format
16
+ )
17
+ )
18
+ return format
19
+
20
+
21
+ def pack(format: tuple[str | _CType, ...], *data: Any) -> bytes:
22
+ return struct.pack(to_format_string(format), *data)
23
+
24
+
25
+ def unpack(format: tuple[str | _CType], data: Buffer):
26
+ return struct.unpack(to_format_string(format), data)
@@ -0,0 +1,223 @@
1
+ import copy
2
+ import inspect
3
+ import struct
4
+ from collections import OrderedDict
5
+ from collections.abc import Buffer
6
+ from typing import Self, Generator
7
+
8
+ from _hydrogenlib_core.typefunc import AutoSlots
9
+
10
+ endian_control_characters = tuple('<@=!>')
11
+
12
+ _AOT_TEMPLATE = """
13
+ def pack(self):
14
+ return self.__st_struct__.pack({selfattrs})
15
+
16
+ def pack_into(self, buffer, offset=0):
17
+ self.__st_struct__.pack_into(buffer, offset, {selfattrs})
18
+
19
+ @classmethod
20
+ def unpack(cls, buffer):
21
+ self = object.__new__(cls)
22
+ {selfattrs} = self.__st_struct__.unpack(buffer)
23
+ return self
24
+
25
+ @classmethod
26
+ def unpack_from(cls, buffer, offset=0):
27
+ self = object.__new__(cls)
28
+ {selfattrs} = cls.__st_struct__.unpack_from(buffer, offset)
29
+ return self
30
+
31
+ @classmethod
32
+ def iter_unpack(cls, buffer):
33
+ for values in cls.__st_struct__.iter_unpack(buffer):
34
+ self = object.__new__(cls)
35
+ {selfattrs} = values
36
+ yield self
37
+
38
+ @classmethod
39
+ def iter_unpack_reused(cls, buffer):
40
+ self = object.__new__(cls)
41
+ for values in cls.__st_struct__.iter_unpack(buffer):
42
+ {selfattrs} = values
43
+ yield self
44
+
45
+ def update_from(self, buffer, offset=0):
46
+ {selfattrs} = self.__st_struct__.unpack_from(buffer, offset)
47
+ return self
48
+
49
+ def __init__(self, {init_signature}):
50
+ {selfattrs} = {attrs}
51
+
52
+ """
53
+
54
+
55
+ class Struct(AutoSlots):
56
+ """
57
+ Base class for creating structured data with a defined format, suitable for packing and unpacking binary data.
58
+
59
+ This class allows the creation of a structure that can be used to pack and unpack binary data according to a
60
+ specified format. It supports type annotations for defining the fields of the structure.
61
+ The class automatically generates the necessary format string for the `struct` module based on the provided
62
+ field types. It also provides methods for comparing instances, packing and unpacking data,
63
+ and converting the structure to a dictionary representation.
64
+
65
+ Usage:
66
+ class MyStruct(Struct):
67
+ a: int
68
+ b: bool
69
+ c: float
70
+ """
71
+ __no_slots__ = (
72
+ '__st_fields__', '__st_struct__', '__st_format__', '__st_order__',
73
+ '__ost_fields__'
74
+ )
75
+
76
+ __st_fields__: dict[str, type] = None
77
+ __st_struct__: struct.Struct = None
78
+ __st_format__: str = ''
79
+ __st_order__: str = '@'
80
+
81
+ # optimize attributes
82
+ __ost_fields__: tuple[str, ...] = None
83
+
84
+ # __ost_methods__: dict[str, Callable] = None
85
+
86
+ def __init_subclass__(cls, *, middle=False, endian: str = '@', **kwargs):
87
+ if middle:
88
+ return
89
+
90
+ from .types import get_ctype
91
+
92
+ cls.__st_fields__ = copy.copy(cls.__st_fields__) or OrderedDict() # 继承父类型的字段
93
+
94
+ new_fields = OrderedDict()
95
+ for name, anno in inspect.get_annotations(cls).items():
96
+ if name in cls.__st_fields__:
97
+ raise TypeError(f"Field {name!r} is already defined")
98
+ new_fields[name] = get_ctype(anno)
99
+
100
+ cls.__st_fields__.update(new_fields)
101
+ cls.__st_format__ += ''.join(new_fields.values()) # 更新新字段的 format string
102
+
103
+ if cls.__st_format__.startswith(endian_control_characters): # 修改字节序
104
+ if cls.__st_format__[0] != endian:
105
+ cls.__st_format__ = endian + cls.__st_format__[1:]
106
+
107
+ cls.__st_struct__ = struct.Struct(cls.__st_format__) # 构造 Struct 类型
108
+
109
+ # 优化
110
+ # 生成 ost-fields
111
+ # cls.__ost_fields__ = tuple(cls.__st_fields__.keys())
112
+
113
+ cls.__ost_fields__ = field_names = tuple(cls.__st_fields__.keys())
114
+
115
+ selfattrs = ', '.join(
116
+ map(
117
+ lambda x: f'self.{x}',
118
+ field_names
119
+ )
120
+ )
121
+
122
+ def _processor(name):
123
+ if name in cls.__migrated__:
124
+ return f'{name}={cls.__migrated__[name]}'
125
+ else:
126
+ return f'{name}'
127
+
128
+ # 生成 init 方法的签名
129
+ init_signature = ', '.join(
130
+ map(
131
+ _processor,
132
+ field_names
133
+ )
134
+ )
135
+
136
+ # 普通的字段元组
137
+ simple_attrs = ', '.join(field_names)
138
+
139
+ # 生成 ost-methods
140
+ gl = {}
141
+ exec(
142
+ _AOT_TEMPLATE.format(
143
+ selfattrs=selfattrs,
144
+ init_signature=init_signature,
145
+ attrs=simple_attrs,
146
+ ),
147
+ globals=gl
148
+ )
149
+
150
+ cls.__ost_methods__ = gl
151
+
152
+ cls.pack = gl['pack']
153
+ cls.pack_into = gl['pack_into']
154
+ cls.unpack = gl['unpack']
155
+ cls.unpack_from = gl['unpack_from']
156
+ cls.iter_unpack = gl['iter_unpack']
157
+ cls.iter_unpack_reused = gl['iter_unpack_reused']
158
+ cls.update_from = gl['update_from']
159
+ cls.__init__ = gl['__init__']
160
+
161
+ def __init__(self, *args, **kwargs) -> None:
162
+ ...
163
+
164
+ def __eq__(self, other):
165
+ if isinstance(other, Struct):
166
+ return other.__st_format__ == other.__st_format__ and self.to_dict() == other.to_dict()
167
+ elif isinstance(other, dict):
168
+ return self.to_dict() == other
169
+ elif isinstance(other, bytes):
170
+ return self.unpack(other) == self
171
+ else:
172
+ raise NotImplementedError(other)
173
+
174
+ def pack(self) -> bytes:
175
+ ...
176
+
177
+ def pack_into(self, buffer: Buffer, offset=0) -> None:
178
+ ...
179
+
180
+ @classmethod
181
+ def unpack(cls, buffer) -> Self:
182
+ ...
183
+
184
+ @classmethod
185
+ def unpack_from(cls, buffer, offset=0) -> Self:
186
+ ...
187
+
188
+ @classmethod
189
+ def iter_unpack(cls, buffer) -> Generator[Self, None, None]:
190
+ ...
191
+
192
+ @classmethod
193
+ def iter_unpack_reused(cls, buffer) -> Generator[Self, None, None]:
194
+ ...
195
+
196
+ def update_from(self, buffer: Buffer, offset=0) -> None:
197
+ ...
198
+
199
+ def to_dict(self):
200
+ return {
201
+ k: getattr(self, k)
202
+ for k in self.__ost_fields__
203
+ }
204
+
205
+ def __str__(self):
206
+ dct = self.to_dict()
207
+ kv_string = ', '.join(
208
+ map(
209
+ lambda x: f"{x[0]}={x[1]!r}"
210
+ , dct.items()
211
+ )
212
+ )
213
+ return f'{self.__class__.__name__}' f"({kv_string})"
214
+
215
+ def __repr__(self):
216
+ dct = self.to_dict()
217
+ kv_string = ', '.join(
218
+ map(
219
+ lambda x: f"{x[0]}={x[1]!r}"
220
+ , dct.items()
221
+ )
222
+ )
223
+ return f'{self.__class__.__name__}[{self.__st_format__}]' f"({kv_string})"
@@ -0,0 +1,14 @@
1
+ import builtins
2
+
3
+ from .types import *
4
+
5
+
6
+ def get_ctype(cls):
7
+ try:
8
+ return cls.__ctype__
9
+ except AttributeError:
10
+ return {
11
+ builtins.int: 'i',
12
+ builtins.float: 'f',
13
+ builtins.bool: '?',
14
+ }[cls]
@@ -0,0 +1,6 @@
1
+ def make_struct_type[T](name, pytype: type[T], ctype) -> type[T]:
2
+ return type(
3
+ name, (pytype,), {
4
+ '__ctype__': ctype
5
+ }
6
+ )
@@ -0,0 +1,101 @@
1
+ import builtins
2
+ import sys
3
+ from functools import lru_cache
4
+
5
+ from .base import make_struct_type
6
+
7
+
8
+ @lru_cache(maxsize=32)
9
+ def pad(size: int):
10
+ return make_struct_type(
11
+ f'pad_{size}', builtins.bytes, f'{size}x'
12
+ )
13
+
14
+
15
+ char = make_struct_type(
16
+ 'char', builtins.bytes, 'c'
17
+ )
18
+
19
+ signed_char = make_struct_type(
20
+ 'signed_char', builtins.int, 'b'
21
+ )
22
+
23
+ unsigned_char = make_struct_type(
24
+ 'unsigned_char', builtins.int, 'B'
25
+ )
26
+
27
+ # bool = make_struct_type(
28
+ # 'bool', builtins.int, '?'
29
+ # ) # 布尔类型不能添加子类,也不允许添加新的类属性,只能特殊处理
30
+
31
+ short = make_struct_type(
32
+ 'short', builtins.int, 'h'
33
+ )
34
+
35
+ unsigned_short = make_struct_type(
36
+ 'unsigned_short', builtins.int, 'H'
37
+ )
38
+
39
+ unsigned_int = make_struct_type(
40
+ 'unsigned_int', builtins.int, 'I'
41
+ )
42
+
43
+ long = make_struct_type(
44
+ 'long', builtins.int, 'l'
45
+ )
46
+
47
+ unsigned_long = make_struct_type(
48
+ 'unsigned_long', builtins.int, 'L'
49
+ )
50
+
51
+ long_long = make_struct_type(
52
+ 'long_long', builtins.int, 'q'
53
+ )
54
+
55
+ unsigned_long_long = make_struct_type(
56
+ 'unsigned_long_long', builtins.int, 'Q'
57
+ )
58
+
59
+ double = make_struct_type(
60
+ 'double', builtins.float, 'd'
61
+ )
62
+
63
+ short_float = make_struct_type(
64
+ 'short_float', builtins.float, 'e'
65
+ )
66
+
67
+ size_t = make_struct_type(
68
+ 'size_t', builtins.int, 'n'
69
+ )
70
+
71
+ ssize_t = make_struct_type(
72
+ 'ssize_t', builtins.int, 'N'
73
+ )
74
+
75
+
76
+ # string (char[]), length must be specified when used
77
+ @lru_cache(maxsize=32)
78
+ def string(length):
79
+ return make_struct_type(f'string_{length}', builtins.bytes, f'{length}s')
80
+
81
+
82
+ # pascal string (length-prefixed)
83
+ @lru_cache(maxsize=32)
84
+ def pascal_string(length):
85
+ return make_struct_type(f'pascal_string_{length}', builtins.bytes, f'{length}p')
86
+
87
+
88
+ # pointer-sized integer (platform dependent)
89
+ pointer = make_struct_type(
90
+ 'pointer', builtins.int, 'P'
91
+ )
92
+
93
+ # >= 3.14
94
+ if sys.version_info.major >= 3 and sys.version_info.minor >= 14:
95
+ float_complex = make_struct_type(
96
+ 'complex', builtins.complex, 'F'
97
+ )
98
+
99
+ double_complex = make_struct_type(
100
+ 'double_complex', builtins.complex, 'D'
101
+ )
@@ -0,0 +1,69 @@
1
+ import sys
2
+
3
+
4
+ class _ctype:
5
+ __ctype__: str
6
+
7
+
8
+ def pad(size: int) -> type: ...
9
+
10
+
11
+ class char(bytes, _ctype): ...
12
+
13
+
14
+ class signed_char(int, _ctype): ...
15
+
16
+
17
+ class unsigned_char(int, _ctype): ...
18
+
19
+
20
+ class long(int, _ctype): ...
21
+
22
+
23
+ class unsigned_long(int, _ctype): ...
24
+
25
+
26
+ class short(int, _ctype): ...
27
+
28
+
29
+ class unsigned_short(int, _ctype): ...
30
+
31
+
32
+ class unsigned_int(int, _ctype): ...
33
+
34
+
35
+ class long_long(int, _ctype): ...
36
+
37
+
38
+ class unsigned_long_long(int, _ctype): ...
39
+
40
+
41
+ class double(float, _ctype): ...
42
+
43
+
44
+ class short_double(float, _ctype): ...
45
+
46
+
47
+ class short_float(float, _ctype): ...
48
+
49
+
50
+ class size_t(int, _ctype): ...
51
+
52
+
53
+ class ssize_t(int, _ctype): ...
54
+
55
+
56
+ def string(length: int) -> type[bytes]: ...
57
+
58
+
59
+ def pascal_string(length: int) -> type[bytes]: ...
60
+
61
+
62
+ class pointer(int, _ctype): ...
63
+
64
+
65
+ if sys.version_info >= (3, 14, 0):
66
+ class float_complex(complex, _ctype): ...
67
+
68
+
69
+ class double_complex(complex, _ctype): ...