sltcodec 2.4.1__tar.gz → 2.5.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.
- {sltcodec-2.4.1 → sltcodec-2.5.1}/PKG-INFO +26 -4
- {sltcodec-2.4.1 → sltcodec-2.5.1}/README.md +16 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/README_pypi.md +24 -2
- {sltcodec-2.4.1 → sltcodec-2.5.1}/pyproject.toml +2 -2
- {sltcodec-2.4.1 → sltcodec-2.5.1}/src/sltcodec/__init__.py +3 -2
- {sltcodec-2.4.1 → sltcodec-2.5.1}/src/sltcodec/codec.py +85 -9
- {sltcodec-2.4.1 → sltcodec-2.5.1}/tests/test_codec.py +117 -2
- {sltcodec-2.4.1 → sltcodec-2.5.1}/uv.lock +5 -5
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.gitignore +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.python-version +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.vscode/launch.json +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/.vscode/settings.json +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/LICENSE +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/bump_major.ps1 +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/bump_minor.ps1 +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/bump_patch.ps1 +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/src/sltcodec/types.py +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/tests/test_persistence.py +0 -0
- {sltcodec-2.4.1 → sltcodec-2.5.1}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.1
|
|
4
4
|
Summary: Decode and encode bytearrays according to struct layout definitions using sltcore.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fangface-hub/StructLayoutToolkitCodec
|
|
6
6
|
Project-URL: Documentation, https://readthedocs.org
|
|
@@ -12,7 +12,7 @@ License-File: LICENSE
|
|
|
12
12
|
Keywords: byte,deserialization,layout,serialization,struct
|
|
13
13
|
Requires-Python: >=3.12
|
|
14
14
|
Requires-Dist: sltcalc>=1.0.0
|
|
15
|
-
Requires-Dist: sltcore>=1.
|
|
15
|
+
Requires-Dist: sltcore>=1.6.0
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
18
18
|
# StructLayoutToolkitCodec
|
|
@@ -63,6 +63,26 @@ encoded = encode(layout, instance, bytearray())
|
|
|
63
63
|
decoded = decode(layout, encoded)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## Progress Callbacks
|
|
67
|
+
|
|
68
|
+
`encode` and `decode` accept an optional `progress_callback` argument. The
|
|
69
|
+
callback is typed as `ProgressCallback` and receives a `float` progress value
|
|
70
|
+
from `0.0` to `1.0`.
|
|
71
|
+
|
|
72
|
+
Progress is calculated for top-level fields as `(offset + size) / total_size`.
|
|
73
|
+
Nested structure encode/decode calls pass `None` internally, so recursive
|
|
74
|
+
fields do not emit additional callback events.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from sltcodec import ProgressCallback, decode
|
|
78
|
+
|
|
79
|
+
def update_progress(progress: float) -> None:
|
|
80
|
+
progress_bar.value = progress
|
|
81
|
+
|
|
82
|
+
progress_callback: ProgressCallback = update_progress
|
|
83
|
+
decoded = decode(layout, encoded, progress_callback=progress_callback)
|
|
84
|
+
```
|
|
85
|
+
|
|
66
86
|
`decode` returns a `StructInstance` whose `size` is the actual end of the
|
|
67
87
|
decoded layout. `decode_field` stores the actual decoded size in the returned
|
|
68
88
|
`FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
|
|
@@ -138,6 +158,7 @@ The following symbols are exported by `sltcodec.__all__` in this order:
|
|
|
138
158
|
| Symbol | Description |
|
|
139
159
|
| --- | --- |
|
|
140
160
|
| `PRIMITIVE_TYPES` | Set of built-in field type names supported by the codec. |
|
|
161
|
+
| `ProgressCallback` | Callable type alias for encode/decode progress updates. |
|
|
141
162
|
| `EnumDef` | Immutable definition of an enumeration. |
|
|
142
163
|
| `EnumDict` | Dictionary-like container for `EnumDef` objects. |
|
|
143
164
|
| `FieldDef` | Immutable definition of one structured field. |
|
|
@@ -310,15 +331,16 @@ beginning with `_` are internal helpers and are not part of the public API.
|
|
|
310
331
|
| Definition | Signature | Description |
|
|
311
332
|
| --- | --- | --- |
|
|
312
333
|
| `PRIMITIVE_TYPES` | `set[str]` | Built-in field type names. |
|
|
334
|
+
| `ProgressCallback` | `Callable[[float], None]` | Progress callback type for top-level encode/decode updates. |
|
|
313
335
|
| `save_struct_layout` | `(struct_layout, path) -> None` | Save a layout to a JSON file. |
|
|
314
336
|
| `load_struct_layout` | `(path) -> StructLayout` | Load a layout from a JSON file. |
|
|
315
337
|
| `save_struct_def_dict` | `(path, struct_def_dict) -> None` | Save a structure-definition dictionary through the layout format. |
|
|
316
338
|
| `load_struct_def_dict` | `(path) -> dict[str, StructDef]` | Load a structure-definition dictionary. |
|
|
317
339
|
| `save_enum_def_dict` | `(path, enum_def_dict) -> None` | Save an enum-definition dictionary through the layout format. |
|
|
318
340
|
| `load_enum_def_dict` | `(path) -> dict[str, EnumDef]` | Load an enum-definition dictionary. |
|
|
319
|
-
| `encode` | `(struct_layout, struct_instance, buf, padding_alignment_bits=32) -> bytearray` | Encode a complete structure. |
|
|
341
|
+
| `encode` | `(struct_layout, struct_instance, buf, padding_alignment_bits=32, progress_callback=None) -> bytearray` | Encode a complete structure. |
|
|
320
342
|
| `decode_field` | `(field_def, data, env=None, type_dict=None, padding_alignment_bits=32) -> FieldInstance \| None` | Decode one field. |
|
|
321
|
-
| `decode` | `(struct_layout, data, padding_alignment_bits=32) -> StructInstance` | Decode a complete structure. |
|
|
343
|
+
| `decode` | `(struct_layout, data, padding_alignment_bits=32, progress_callback=None) -> StructInstance` | Decode a complete structure. |
|
|
322
344
|
|
|
323
345
|
`save_struct_def_dict`, `load_struct_def_dict`, `save_enum_def_dict`, and
|
|
324
346
|
`load_enum_def_dict` are available from `sltcodec.codec` for dictionary-level
|
|
@@ -57,6 +57,22 @@ definition is resolved by `struct_layout.struct_def_name` from
|
|
|
57
57
|
`struct_layout.type_dict.struct_dict`; enum definitions are resolved from the
|
|
58
58
|
same `TypeDict`.
|
|
59
59
|
|
|
60
|
+
`encode` and `decode` accept an optional `progress_callback` argument typed as
|
|
61
|
+
`ProgressCallback`. The callback receives a `float` progress value calculated
|
|
62
|
+
from the top-level field extent: `(offset + size) / total_size`. Recursive
|
|
63
|
+
nested structure calls pass `None`, so callers receive progress updates only
|
|
64
|
+
for the top-level encode or decode operation.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from sltcodec import ProgressCallback, decode
|
|
68
|
+
|
|
69
|
+
def update_progress(progress: float) -> None:
|
|
70
|
+
progress_bar.value = progress
|
|
71
|
+
|
|
72
|
+
progress_callback: ProgressCallback = update_progress
|
|
73
|
+
decoded = decode(layout, data, progress_callback=progress_callback)
|
|
74
|
+
```
|
|
75
|
+
|
|
60
76
|
Layout persistence uses the explicit names `save_struct_layout` and
|
|
61
77
|
`load_struct_layout`. The former `save_type_dict` and `load_type_dict` API is
|
|
62
78
|
not part of the current public interface.
|
|
@@ -46,6 +46,26 @@ encoded = encode(layout, instance, bytearray())
|
|
|
46
46
|
decoded = decode(layout, encoded)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Progress Callbacks
|
|
50
|
+
|
|
51
|
+
`encode` and `decode` accept an optional `progress_callback` argument. The
|
|
52
|
+
callback is typed as `ProgressCallback` and receives a `float` progress value
|
|
53
|
+
from `0.0` to `1.0`.
|
|
54
|
+
|
|
55
|
+
Progress is calculated for top-level fields as `(offset + size) / total_size`.
|
|
56
|
+
Nested structure encode/decode calls pass `None` internally, so recursive
|
|
57
|
+
fields do not emit additional callback events.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from sltcodec import ProgressCallback, decode
|
|
61
|
+
|
|
62
|
+
def update_progress(progress: float) -> None:
|
|
63
|
+
progress_bar.value = progress
|
|
64
|
+
|
|
65
|
+
progress_callback: ProgressCallback = update_progress
|
|
66
|
+
decoded = decode(layout, encoded, progress_callback=progress_callback)
|
|
67
|
+
```
|
|
68
|
+
|
|
49
69
|
`decode` returns a `StructInstance` whose `size` is the actual end of the
|
|
50
70
|
decoded layout. `decode_field` stores the actual decoded size in the returned
|
|
51
71
|
`FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
|
|
@@ -121,6 +141,7 @@ The following symbols are exported by `sltcodec.__all__` in this order:
|
|
|
121
141
|
| Symbol | Description |
|
|
122
142
|
| --- | --- |
|
|
123
143
|
| `PRIMITIVE_TYPES` | Set of built-in field type names supported by the codec. |
|
|
144
|
+
| `ProgressCallback` | Callable type alias for encode/decode progress updates. |
|
|
124
145
|
| `EnumDef` | Immutable definition of an enumeration. |
|
|
125
146
|
| `EnumDict` | Dictionary-like container for `EnumDef` objects. |
|
|
126
147
|
| `FieldDef` | Immutable definition of one structured field. |
|
|
@@ -293,15 +314,16 @@ beginning with `_` are internal helpers and are not part of the public API.
|
|
|
293
314
|
| Definition | Signature | Description |
|
|
294
315
|
| --- | --- | --- |
|
|
295
316
|
| `PRIMITIVE_TYPES` | `set[str]` | Built-in field type names. |
|
|
317
|
+
| `ProgressCallback` | `Callable[[float], None]` | Progress callback type for top-level encode/decode updates. |
|
|
296
318
|
| `save_struct_layout` | `(struct_layout, path) -> None` | Save a layout to a JSON file. |
|
|
297
319
|
| `load_struct_layout` | `(path) -> StructLayout` | Load a layout from a JSON file. |
|
|
298
320
|
| `save_struct_def_dict` | `(path, struct_def_dict) -> None` | Save a structure-definition dictionary through the layout format. |
|
|
299
321
|
| `load_struct_def_dict` | `(path) -> dict[str, StructDef]` | Load a structure-definition dictionary. |
|
|
300
322
|
| `save_enum_def_dict` | `(path, enum_def_dict) -> None` | Save an enum-definition dictionary through the layout format. |
|
|
301
323
|
| `load_enum_def_dict` | `(path) -> dict[str, EnumDef]` | Load an enum-definition dictionary. |
|
|
302
|
-
| `encode` | `(struct_layout, struct_instance, buf, padding_alignment_bits=32) -> bytearray` | Encode a complete structure. |
|
|
324
|
+
| `encode` | `(struct_layout, struct_instance, buf, padding_alignment_bits=32, progress_callback=None) -> bytearray` | Encode a complete structure. |
|
|
303
325
|
| `decode_field` | `(field_def, data, env=None, type_dict=None, padding_alignment_bits=32) -> FieldInstance \| None` | Decode one field. |
|
|
304
|
-
| `decode` | `(struct_layout, data, padding_alignment_bits=32) -> StructInstance` | Decode a complete structure. |
|
|
326
|
+
| `decode` | `(struct_layout, data, padding_alignment_bits=32, progress_callback=None) -> StructInstance` | Decode a complete structure. |
|
|
305
327
|
|
|
306
328
|
`save_struct_def_dict`, `load_struct_def_dict`, `save_enum_def_dict`, and
|
|
307
329
|
`load_enum_def_dict` are available from `sltcodec.codec` for dictionary-level
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sltcodec"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.5.1"
|
|
4
4
|
description = "Decode and encode bytearrays according to struct layout definitions using sltcore."
|
|
5
5
|
readme = "README_pypi.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"sltcalc>=1.0.0",
|
|
9
|
-
"sltcore>=1.
|
|
9
|
+
"sltcore>=1.6.0",
|
|
10
10
|
]
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from .codec import (PRIMITIVE_TYPES, decode, encode,
|
|
2
|
-
save_struct_layout)
|
|
1
|
+
from .codec import (PRIMITIVE_TYPES, ProgressCallback, decode, encode,
|
|
2
|
+
load_struct_layout, save_struct_layout)
|
|
3
3
|
from .types import (EnumDef, EnumDict, FieldDef, FieldInstance, StructDef,
|
|
4
4
|
StructDict, StructInstance, StructLayout, TypeDict)
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"PRIMITIVE_TYPES",
|
|
8
|
+
"ProgressCallback",
|
|
8
9
|
"EnumDef",
|
|
9
10
|
"EnumDict",
|
|
10
11
|
"FieldDef",
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Callable
|
|
8
8
|
|
|
9
9
|
from sltcalc import SltEval
|
|
10
10
|
from sltcore import Info, InfoSize, bits_get, bits_set
|
|
@@ -27,6 +27,58 @@ _PRIMITIVE_TYPES = PRIMITIVE_TYPES
|
|
|
27
27
|
|
|
28
28
|
_DEFAULT_PADDING_ALIGNMENT_BITS = 32
|
|
29
29
|
|
|
30
|
+
ProgressCallback = Callable[[float], None]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _notify_progress(progress_callback: ProgressCallback | None,
|
|
34
|
+
offset: InfoSize, size: InfoSize, total_bits: int) -> None:
|
|
35
|
+
"""Notify caller of top-level encode/decode progress."""
|
|
36
|
+
if progress_callback is None or total_bits <= 0:
|
|
37
|
+
return
|
|
38
|
+
progress = min((offset + size).bits / total_bits, 1.0)
|
|
39
|
+
progress_callback(progress)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _estimate_encode_total_bits(struct_instance: StructInstance,
|
|
43
|
+
initial_size_bits: int) -> int:
|
|
44
|
+
"""Estimate encoded top-level extent before mutating the output buffer."""
|
|
45
|
+
env: dict[str, Any] = {}
|
|
46
|
+
total_bits = max(struct_instance.size.bits, initial_size_bits)
|
|
47
|
+
|
|
48
|
+
for field_value in struct_instance.field_instances:
|
|
49
|
+
field_def = field_value.field_def
|
|
50
|
+
repeat_until_end = field_def.repeat == "end"
|
|
51
|
+
repeat = _resolve_repeat(field_def.repeat, env)
|
|
52
|
+
value = field_value.value
|
|
53
|
+
if _is_padding_field_def(field_def):
|
|
54
|
+
padding_size = _resolve_info_size(field_def.size, env)
|
|
55
|
+
value = bytearray(padding_size.bytes)
|
|
56
|
+
if repeat_until_end:
|
|
57
|
+
repeat_values = list(value)
|
|
58
|
+
repeat = len(repeat_values)
|
|
59
|
+
if repeat is not None and repeat > 1:
|
|
60
|
+
current_offset = _resolve_info_size(field_def.offset, env)
|
|
61
|
+
values = repeat_values if repeat_until_end else list(value)
|
|
62
|
+
|
|
63
|
+
for i in range(repeat):
|
|
64
|
+
field_def_repeat = _repeated_field_def(field_def, i,
|
|
65
|
+
current_offset,
|
|
66
|
+
field_def.size)
|
|
67
|
+
resolved_size = _resolve_info_size(field_def_repeat.size, env)
|
|
68
|
+
total_bits = max(total_bits,
|
|
69
|
+
(current_offset + resolved_size).bits)
|
|
70
|
+
env[field_def_repeat.name] = values[i]
|
|
71
|
+
if isinstance(current_offset, InfoSize):
|
|
72
|
+
current_offset += resolved_size
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
resolved_offset = _resolve_info_size(field_def.offset, env)
|
|
76
|
+
resolved_size = _resolve_info_size(field_def.size, env)
|
|
77
|
+
total_bits = max(total_bits, (resolved_offset + resolved_size).bits)
|
|
78
|
+
env[field_def.name] = value
|
|
79
|
+
|
|
80
|
+
return total_bits
|
|
81
|
+
|
|
30
82
|
|
|
31
83
|
def _as_field_defs(struct_def: StructDef | list[FieldDef]) -> list[FieldDef]:
|
|
32
84
|
"""Normalize StructDef/list inputs to a list of field definitions."""
|
|
@@ -253,12 +305,11 @@ def _prepare_field_info(
|
|
|
253
305
|
raise TypeError(
|
|
254
306
|
"Nested StructDef fields must be encoded with StructInstance "
|
|
255
307
|
"values")
|
|
256
|
-
nested_bytes = encode(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
padding_alignment_bits=padding_alignment_bits)
|
|
308
|
+
nested_bytes = encode(
|
|
309
|
+
_layout_for_struct_def(value.struct_def,
|
|
310
|
+
type_dict,
|
|
311
|
+
name=field_def.name), value, bytearray(),
|
|
312
|
+
padding_alignment_bits, None)
|
|
262
313
|
info = Info.from_bytes(bytes(nested_bytes), size, scale=field_def.scale)
|
|
263
314
|
else:
|
|
264
315
|
info = _encode_primitive(resolved_type, value, size, field_def.scale)
|
|
@@ -323,6 +374,7 @@ def encode(
|
|
|
323
374
|
struct_instance: StructInstance,
|
|
324
375
|
buf: bytearray,
|
|
325
376
|
padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
|
|
377
|
+
progress_callback: ProgressCallback | None = None,
|
|
326
378
|
) -> bytearray:
|
|
327
379
|
"""Encode decode() result into a bytearray.
|
|
328
380
|
|
|
@@ -337,6 +389,9 @@ def encode(
|
|
|
337
389
|
The base bytearray instance to write into.
|
|
338
390
|
padding_alignment_bits : int, optional
|
|
339
391
|
The padding alignment boundary in bits, by default 32.
|
|
392
|
+
progress_callback : Callable[[float], None] | None, optional
|
|
393
|
+
Called after each top-level field is encoded with progress in the
|
|
394
|
+
range 0.0 to 1.0. Nested recursive encodes are skipped.
|
|
340
395
|
|
|
341
396
|
Returns
|
|
342
397
|
-------
|
|
@@ -353,6 +408,7 @@ def encode(
|
|
|
353
408
|
type_dict = struct_layout.type_dict
|
|
354
409
|
env: dict[str, Any] = {}
|
|
355
410
|
has_padding = False
|
|
411
|
+
total_bits = _estimate_encode_total_bits(struct_instance, len(buf) * 8)
|
|
356
412
|
|
|
357
413
|
for field_value in struct_instance.field_instances:
|
|
358
414
|
field_def = field_value.field_def
|
|
@@ -378,13 +434,19 @@ def encode(
|
|
|
378
434
|
resolved_size = _resolve_info_size(field_def_repeat.size, env)
|
|
379
435
|
encode_field(field_def_repeat, values[i], buf, env, type_dict,
|
|
380
436
|
padding_alignment_bits)
|
|
437
|
+
_notify_progress(progress_callback, current_offset,
|
|
438
|
+
resolved_size, total_bits)
|
|
381
439
|
env[field_def_repeat.name] = values[i]
|
|
382
440
|
if isinstance(current_offset, InfoSize):
|
|
383
441
|
current_offset += resolved_size
|
|
384
442
|
continue
|
|
385
443
|
|
|
444
|
+
resolved_offset = _resolve_info_size(field_def.offset, env)
|
|
445
|
+
resolved_size = _resolve_info_size(field_def.size, env)
|
|
386
446
|
encode_field(field_def, value, buf, env, type_dict,
|
|
387
447
|
padding_alignment_bits)
|
|
448
|
+
_notify_progress(progress_callback, resolved_offset, resolved_size,
|
|
449
|
+
total_bits)
|
|
388
450
|
env[field_def.name] = value
|
|
389
451
|
|
|
390
452
|
if has_padding and struct_instance.size.bytes > len(buf):
|
|
@@ -437,8 +499,12 @@ def decode_field(
|
|
|
437
499
|
nested_data = bytearray(info.to_bytes)
|
|
438
500
|
if _struct_def_has_dynamic_extent(resolved_type) and offset.bit == 0:
|
|
439
501
|
nested_data = bytearray(data[offset.byte:])
|
|
440
|
-
nested_value = decode(
|
|
441
|
-
|
|
502
|
+
nested_value = decode(
|
|
503
|
+
nested_layout,
|
|
504
|
+
nested_data,
|
|
505
|
+
padding_alignment_bits,
|
|
506
|
+
None,
|
|
507
|
+
)
|
|
442
508
|
actual_size = nested_value.size
|
|
443
509
|
else:
|
|
444
510
|
actual_size = info.info_size
|
|
@@ -507,6 +573,7 @@ def decode(
|
|
|
507
573
|
struct_layout: StructLayout,
|
|
508
574
|
data: bytearray | bytes,
|
|
509
575
|
padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
|
|
576
|
+
progress_callback: ProgressCallback | None = None,
|
|
510
577
|
) -> StructInstance:
|
|
511
578
|
"""Decode a bytearray into field values according to a layout.
|
|
512
579
|
|
|
@@ -519,6 +586,9 @@ def decode(
|
|
|
519
586
|
The data to decode.
|
|
520
587
|
padding_alignment_bits : int, optional
|
|
521
588
|
The padding alignment boundary in bits, by default 32.
|
|
589
|
+
progress_callback : Callable[[float], None] | None, optional
|
|
590
|
+
Called after each top-level field is decoded with progress in the
|
|
591
|
+
range 0.0 to 1.0. Nested recursive decodes are skipped.
|
|
522
592
|
Returns
|
|
523
593
|
-------
|
|
524
594
|
StructInstance
|
|
@@ -604,6 +674,8 @@ def decode(
|
|
|
604
674
|
if field_instance is not None:
|
|
605
675
|
env[field_instance.field_def.name] = field_instance.value
|
|
606
676
|
result.append_field_instance(field_instance)
|
|
677
|
+
_notify_progress(progress_callback, resolved_offset,
|
|
678
|
+
field_instance.field_def.size, data_bits)
|
|
607
679
|
current_position = (resolved_offset +
|
|
608
680
|
_resolve_info_size(field_def.size, env))
|
|
609
681
|
continue
|
|
@@ -643,6 +715,8 @@ def decode(
|
|
|
643
715
|
env[field_instance.field_def.name] = field_instance.value
|
|
644
716
|
result.append_field_instance(field_instance)
|
|
645
717
|
actual_size = field_instance.field_def.size
|
|
718
|
+
_notify_progress(progress_callback, current_offset,
|
|
719
|
+
actual_size, data_bits)
|
|
646
720
|
else:
|
|
647
721
|
actual_size = resolved_size
|
|
648
722
|
|
|
@@ -688,6 +762,8 @@ def decode(
|
|
|
688
762
|
env[field_instance.field_def.name] = field_instance.value
|
|
689
763
|
result.append_field_instance(field_instance)
|
|
690
764
|
actual_size = field_instance.field_def.size
|
|
765
|
+
_notify_progress(progress_callback, current_offset, actual_size,
|
|
766
|
+
data_bits)
|
|
691
767
|
else:
|
|
692
768
|
actual_size = _resolve_info_size(field_def_repeat.size, env)
|
|
693
769
|
if isinstance(current_offset, InfoSize) and isinstance(
|
|
@@ -5,8 +5,8 @@ import pytest
|
|
|
5
5
|
from sltcore import InfoSize
|
|
6
6
|
|
|
7
7
|
from sltcodec import (PRIMITIVE_TYPES, EnumDef, FieldDef, FieldInstance,
|
|
8
|
-
StructDef, StructInstance, StructLayout,
|
|
9
|
-
encode)
|
|
8
|
+
ProgressCallback, StructDef, StructInstance, StructLayout,
|
|
9
|
+
TypeDict, decode, encode)
|
|
10
10
|
from sltcodec.codec import _resolve_info_size, decode_field
|
|
11
11
|
|
|
12
12
|
|
|
@@ -42,6 +42,13 @@ def test_primitive_types_are_exposed_at_package_root():
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def test_progress_callback_type_is_exposed_at_package_root():
|
|
46
|
+
"""Test that ProgressCallback is exposed for external consumers."""
|
|
47
|
+
progress_callback: ProgressCallback = lambda progress: None
|
|
48
|
+
|
|
49
|
+
assert callable(progress_callback)
|
|
50
|
+
|
|
51
|
+
|
|
45
52
|
def test_encode_and_decode_round_trip():
|
|
46
53
|
"""Test that encoding and then decoding returns the original data."""
|
|
47
54
|
struct_def = [
|
|
@@ -75,6 +82,44 @@ def test_encode_and_decode_round_trip():
|
|
|
75
82
|
bytearray()) == encoded
|
|
76
83
|
|
|
77
84
|
|
|
85
|
+
def test_encode_and_decode_progress_callbacks_use_top_level_progress():
|
|
86
|
+
"""Test progress callbacks report top-level encoded/decoded progress."""
|
|
87
|
+
fields = [
|
|
88
|
+
FieldDef(name="head",
|
|
89
|
+
offset=InfoSize(0, 0),
|
|
90
|
+
size=InfoSize(1, 0),
|
|
91
|
+
type="unsigned int"),
|
|
92
|
+
FieldDef(name="tail",
|
|
93
|
+
offset=InfoSize(1, 0),
|
|
94
|
+
size=InfoSize(1, 0),
|
|
95
|
+
type="unsigned int"),
|
|
96
|
+
]
|
|
97
|
+
struct_def = StructDef(fields=fields)
|
|
98
|
+
struct_instance = StructInstance(
|
|
99
|
+
struct_def=struct_def,
|
|
100
|
+
field_instances=[
|
|
101
|
+
FieldInstance(fields[0], 0x12),
|
|
102
|
+
FieldInstance(fields[1], 0x34),
|
|
103
|
+
],
|
|
104
|
+
)
|
|
105
|
+
encode_progress = []
|
|
106
|
+
decode_progress = []
|
|
107
|
+
|
|
108
|
+
encoded = encode(layout_for(struct_def),
|
|
109
|
+
struct_instance,
|
|
110
|
+
bytearray(),
|
|
111
|
+
progress_callback=encode_progress.append)
|
|
112
|
+
decoded = decode(layout_for(struct_def),
|
|
113
|
+
encoded,
|
|
114
|
+
progress_callback=decode_progress.append)
|
|
115
|
+
|
|
116
|
+
assert encoded == bytearray(b"\x12\x34")
|
|
117
|
+
assert [field_instance.value
|
|
118
|
+
for field_instance in decoded.field_instances] == [0x12, 0x34]
|
|
119
|
+
assert encode_progress == [0.5, 1.0]
|
|
120
|
+
assert decode_progress == [0.5, 1.0]
|
|
121
|
+
|
|
122
|
+
|
|
78
123
|
def test_decode_accepts_struct_layout():
|
|
79
124
|
"""Decode a legacy field list via a StructLayout bundle."""
|
|
80
125
|
struct_def = StructDef(
|
|
@@ -166,6 +211,33 @@ def test_encode_layout_handles_repeat():
|
|
|
166
211
|
]
|
|
167
212
|
|
|
168
213
|
|
|
214
|
+
def test_progress_callbacks_use_repeated_top_level_field_extent():
|
|
215
|
+
"""Test repeated fields report progress against the encoded extent."""
|
|
216
|
+
field_def = FieldDef(name="value",
|
|
217
|
+
offset=InfoSize(0, 0),
|
|
218
|
+
size=InfoSize(1, 0),
|
|
219
|
+
type="unsigned int",
|
|
220
|
+
repeat=2)
|
|
221
|
+
struct_def = StructDef(fields=[field_def])
|
|
222
|
+
struct_instance = StructInstance(
|
|
223
|
+
struct_def=struct_def,
|
|
224
|
+
field_instances=[FieldInstance(field_def, [1, 2])],
|
|
225
|
+
)
|
|
226
|
+
encode_progress = []
|
|
227
|
+
decode_progress = []
|
|
228
|
+
|
|
229
|
+
encoded = encode(layout_for(struct_def),
|
|
230
|
+
struct_instance,
|
|
231
|
+
bytearray(),
|
|
232
|
+
progress_callback=encode_progress.append)
|
|
233
|
+
decode(layout_for([field_def]),
|
|
234
|
+
encoded,
|
|
235
|
+
progress_callback=decode_progress.append)
|
|
236
|
+
|
|
237
|
+
assert encode_progress == [0.5, 1.0]
|
|
238
|
+
assert decode_progress == [0.5, 1.0]
|
|
239
|
+
|
|
240
|
+
|
|
169
241
|
def test_expression_repeat_uses_previous_field_value():
|
|
170
242
|
"""Test that repeat expressions are evaluated against the field env."""
|
|
171
243
|
seed_field = FieldDef(name="count",
|
|
@@ -363,6 +435,49 @@ def test_encode_recurses_for_nested_field_types():
|
|
|
363
435
|
]
|
|
364
436
|
|
|
365
437
|
|
|
438
|
+
def test_progress_callbacks_skip_nested_recursive_fields():
|
|
439
|
+
"""Test nested recursive encode/decode calls do not report progress."""
|
|
440
|
+
child_field_defs = [
|
|
441
|
+
FieldDef(name="left",
|
|
442
|
+
offset=InfoSize(0, 0),
|
|
443
|
+
size=InfoSize(1, 0),
|
|
444
|
+
type="unsigned int"),
|
|
445
|
+
FieldDef(name="right",
|
|
446
|
+
offset=InfoSize(1, 0),
|
|
447
|
+
size=InfoSize(1, 0),
|
|
448
|
+
type="unsigned int"),
|
|
449
|
+
]
|
|
450
|
+
parent_field_def = FieldDef(name="pair",
|
|
451
|
+
offset=InfoSize(0, 0),
|
|
452
|
+
size=InfoSize(2, 0),
|
|
453
|
+
type=StructDef(fields=child_field_defs))
|
|
454
|
+
child_instance = StructInstance(
|
|
455
|
+
struct_def=StructDef(fields=child_field_defs),
|
|
456
|
+
field_instances=[
|
|
457
|
+
FieldInstance(child_field_defs[0], 3),
|
|
458
|
+
FieldInstance(child_field_defs[1], 4),
|
|
459
|
+
],
|
|
460
|
+
)
|
|
461
|
+
parent_struct_def = StructDef(fields=[parent_field_def])
|
|
462
|
+
parent_instance = StructInstance(
|
|
463
|
+
struct_def=parent_struct_def,
|
|
464
|
+
field_instances=[FieldInstance(parent_field_def, child_instance)],
|
|
465
|
+
)
|
|
466
|
+
encode_progress = []
|
|
467
|
+
decode_progress = []
|
|
468
|
+
|
|
469
|
+
encoded = encode(layout_for(parent_struct_def),
|
|
470
|
+
parent_instance,
|
|
471
|
+
bytearray(),
|
|
472
|
+
progress_callback=encode_progress.append)
|
|
473
|
+
decode(layout_for(parent_struct_def),
|
|
474
|
+
encoded,
|
|
475
|
+
progress_callback=decode_progress.append)
|
|
476
|
+
|
|
477
|
+
assert encode_progress == [1.0]
|
|
478
|
+
assert decode_progress == [1.0]
|
|
479
|
+
|
|
480
|
+
|
|
366
481
|
def test_decode_field_uses_nested_struct_instance_size():
|
|
367
482
|
"""Test that nested decoding determines the field's actual size."""
|
|
368
483
|
child_field_def = FieldDef(name="value",
|
|
@@ -74,7 +74,7 @@ wheels = [
|
|
|
74
74
|
|
|
75
75
|
[[package]]
|
|
76
76
|
name = "sltcodec"
|
|
77
|
-
version = "2.
|
|
77
|
+
version = "2.5.0"
|
|
78
78
|
source = { editable = "." }
|
|
79
79
|
dependencies = [
|
|
80
80
|
{ name = "sltcalc" },
|
|
@@ -89,7 +89,7 @@ dev = [
|
|
|
89
89
|
[package.metadata]
|
|
90
90
|
requires-dist = [
|
|
91
91
|
{ name = "sltcalc", specifier = ">=1.0.0" },
|
|
92
|
-
{ name = "sltcore", specifier = ">=1.
|
|
92
|
+
{ name = "sltcore", specifier = ">=1.6.0" },
|
|
93
93
|
]
|
|
94
94
|
|
|
95
95
|
[package.metadata.requires-dev]
|
|
@@ -97,9 +97,9 @@ dev = [{ name = "pytest", specifier = ">=9.1.1" }]
|
|
|
97
97
|
|
|
98
98
|
[[package]]
|
|
99
99
|
name = "sltcore"
|
|
100
|
-
version = "1.
|
|
100
|
+
version = "1.6.0"
|
|
101
101
|
source = { registry = "https://pypi.org/simple" }
|
|
102
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
102
|
+
sdist = { url = "https://files.pythonhosted.org/packages/32/01/89dc1e6c0429710d7329a3dab769e1c9f8b148ab1592c69df53592dbf8c2/sltcore-1.6.0.tar.gz", hash = "sha256:872ada7183ae5f065a40dc44ee5062bb7cc781a4758b56cd740bb77e9e1fdc6e", size = 20648, upload-time = "2026-08-21T14:49:00.72Z" }
|
|
103
103
|
wheels = [
|
|
104
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/a4/b5/d35ac0fcab3a16efdc80814a59d1788a962e8ac8d8005dc6c18435baf655/sltcore-1.6.0-py3-none-any.whl", hash = "sha256:d3bf525275fda9159b97c2663d14bb73b56b70869e07f0d79727883bcbedd8d8", size = 12707, upload-time = "2026-08-21T14:48:59.749Z" },
|
|
105
105
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|