sltcodec 2.6.1__tar.gz → 2.7.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.
- {sltcodec-2.6.1 → sltcodec-2.7.0}/PKG-INFO +1 -1
- {sltcodec-2.6.1 → sltcodec-2.7.0}/pyproject.toml +1 -1
- {sltcodec-2.6.1 → sltcodec-2.7.0}/src/sltcodec/codec.py +37 -16
- {sltcodec-2.6.1 → sltcodec-2.7.0}/tests/test_codec.py +37 -1
- {sltcodec-2.6.1 → sltcodec-2.7.0}/uv.lock +1 -1
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.gitignore +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.python-version +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.vscode/launch.json +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/.vscode/settings.json +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/LICENSE +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/README.md +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/README_pypi.md +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/bump_major.ps1 +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/bump_minor.ps1 +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/bump_patch.ps1 +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/src/sltcodec/__init__.py +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/src/sltcodec/types.py +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/tests/test_persistence.py +0 -0
- {sltcodec-2.6.1 → sltcodec-2.7.0}/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.7.0
|
|
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
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
|
+
import time
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Callable
|
|
8
9
|
|
|
@@ -29,16 +30,34 @@ _DEFAULT_PADDING_ALIGNMENT_BITS = 32
|
|
|
29
30
|
|
|
30
31
|
ProgressCallback = Callable[[float], None]
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
_PROGRESS_MIN_INTERVAL_SEC = 0.1
|
|
34
|
+
_PROGRESS_MIN_DELTA = 0.01
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _notify_progress(progress_callback: ProgressCallback | None,
|
|
38
|
+
progress_state: dict[str, float],
|
|
39
|
+
offset: InfoSize, size: InfoSize, total_bits: int) -> None:
|
|
40
|
+
"""Notify caller of top-level encode/decode progress.
|
|
41
|
+
|
|
42
|
+
Calls are throttled: the callback fires on the first call, at most once
|
|
43
|
+
per 100 ms, or when progress changed by more than 0.01 since the last
|
|
44
|
+
notification. Completion (1.0) is always notified. ``progress_state`` is a
|
|
45
|
+
per-run dict holding the last notification time and progress.
|
|
46
|
+
"""
|
|
47
|
+
if progress_callback is None or total_bits <= 0:
|
|
48
|
+
return
|
|
49
|
+
progress = min((offset + size).bits / total_bits, 1.0)
|
|
50
|
+
now = time.monotonic()
|
|
51
|
+
last_time = progress_state.get("time")
|
|
52
|
+
if (last_time is not None and progress < 1.0
|
|
53
|
+
and now - last_time < _PROGRESS_MIN_INTERVAL_SEC
|
|
54
|
+
and abs(progress - progress_state["progress"]) <=
|
|
55
|
+
_PROGRESS_MIN_DELTA):
|
|
56
|
+
return
|
|
57
|
+
progress_state["time"] = now
|
|
58
|
+
progress_state["progress"] = progress
|
|
59
|
+
progress_callback(progress)
|
|
60
|
+
|
|
42
61
|
def _estimate_encode_total_bits(struct_instance: StructInstance,
|
|
43
62
|
initial_size_bits: int,
|
|
44
63
|
env: dict[str, Any] | None = None) -> int:
|
|
@@ -410,6 +429,7 @@ def encode(
|
|
|
410
429
|
raise ValueError("StructLayout.type_dict is required for encode()")
|
|
411
430
|
|
|
412
431
|
type_dict = struct_layout.type_dict
|
|
432
|
+
progress_state: dict[str, float] = {}
|
|
413
433
|
env = env if env is not None else {}
|
|
414
434
|
has_padding = False
|
|
415
435
|
total_bits = _estimate_encode_total_bits(struct_instance, len(buf) * 8, env)
|
|
@@ -438,7 +458,7 @@ def encode(
|
|
|
438
458
|
resolved_size = _resolve_info_size(field_def_repeat.size, env)
|
|
439
459
|
encode_field(field_def_repeat, values[i], buf, env, type_dict,
|
|
440
460
|
padding_alignment_bits)
|
|
441
|
-
_notify_progress(progress_callback, current_offset,
|
|
461
|
+
_notify_progress(progress_callback, progress_state, current_offset,
|
|
442
462
|
resolved_size, total_bits)
|
|
443
463
|
env[field_def_repeat.name] = values[i]
|
|
444
464
|
if isinstance(current_offset, InfoSize):
|
|
@@ -449,7 +469,7 @@ def encode(
|
|
|
449
469
|
resolved_size = _resolve_info_size(field_def.size, env)
|
|
450
470
|
encode_field(field_def, value, buf, env, type_dict,
|
|
451
471
|
padding_alignment_bits)
|
|
452
|
-
_notify_progress(progress_callback, resolved_offset, resolved_size,
|
|
472
|
+
_notify_progress(progress_callback, progress_state, resolved_offset, resolved_size,
|
|
453
473
|
total_bits)
|
|
454
474
|
env[field_def.name] = value
|
|
455
475
|
|
|
@@ -608,9 +628,10 @@ def decode(
|
|
|
608
628
|
if struct_layout.type_dict is None:
|
|
609
629
|
raise ValueError("StructLayout.type_dict is required for decode()")
|
|
610
630
|
|
|
631
|
+
progress_state: dict[str, float] = {}
|
|
611
632
|
env = env if env is not None else {}
|
|
612
633
|
type_dict = struct_layout.type_dict
|
|
613
|
-
struct_def_obj =
|
|
634
|
+
struct_def_obj = type_dict.struct_dict[struct_layout.struct_def_name]
|
|
614
635
|
result = StructInstance(struct_def=struct_def_obj)
|
|
615
636
|
current_position = InfoSize(0, 0)
|
|
616
637
|
padding_index = 0
|
|
@@ -682,7 +703,7 @@ def decode(
|
|
|
682
703
|
if field_instance is not None:
|
|
683
704
|
env[field_instance.field_def.name] = field_instance.value
|
|
684
705
|
result.append_field_instance(field_instance)
|
|
685
|
-
_notify_progress(progress_callback, resolved_offset,
|
|
706
|
+
_notify_progress(progress_callback, progress_state, resolved_offset,
|
|
686
707
|
field_instance.field_def.size, data_bits)
|
|
687
708
|
current_position = (resolved_offset +
|
|
688
709
|
_resolve_info_size(field_def.size, env))
|
|
@@ -723,7 +744,7 @@ def decode(
|
|
|
723
744
|
env[field_instance.field_def.name] = field_instance.value
|
|
724
745
|
result.append_field_instance(field_instance)
|
|
725
746
|
actual_size = field_instance.field_def.size
|
|
726
|
-
_notify_progress(progress_callback, current_offset,
|
|
747
|
+
_notify_progress(progress_callback, progress_state, current_offset,
|
|
727
748
|
actual_size, data_bits)
|
|
728
749
|
else:
|
|
729
750
|
actual_size = resolved_size
|
|
@@ -770,7 +791,7 @@ def decode(
|
|
|
770
791
|
env[field_instance.field_def.name] = field_instance.value
|
|
771
792
|
result.append_field_instance(field_instance)
|
|
772
793
|
actual_size = field_instance.field_def.size
|
|
773
|
-
_notify_progress(progress_callback, current_offset, actual_size,
|
|
794
|
+
_notify_progress(progress_callback, progress_state, current_offset, actual_size,
|
|
774
795
|
data_bits)
|
|
775
796
|
else:
|
|
776
797
|
actual_size = _resolve_info_size(field_def_repeat.size, env)
|
|
@@ -7,7 +7,9 @@ from sltcore import InfoSize
|
|
|
7
7
|
from sltcodec import (PRIMITIVE_TYPES, EnumDef, FieldDef, FieldInstance,
|
|
8
8
|
ProgressCallback, StructDef, StructInstance, StructLayout,
|
|
9
9
|
TypeDict, decode, encode)
|
|
10
|
-
from sltcodec
|
|
10
|
+
from sltcodec import codec as codec_module
|
|
11
|
+
from sltcodec.codec import (_notify_progress, _resolve_info_size,
|
|
12
|
+
decode_field)
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def layout_for(struct_def):
|
|
@@ -968,3 +970,37 @@ def test_encode_without_padding_does_not_extend_buf_to_struct_size():
|
|
|
968
970
|
|
|
969
971
|
assert encoded is buf
|
|
970
972
|
assert encoded == bytearray(b"\x7f\x00\x00\x00")
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def _run_notify(monkeypatch, steps):
|
|
976
|
+
"""Feed (time, bits) steps through _notify_progress; return reported."""
|
|
977
|
+
clock = {"now": 0.0}
|
|
978
|
+
monkeypatch.setattr(codec_module.time, "monotonic", lambda: clock["now"])
|
|
979
|
+
reported = []
|
|
980
|
+
state = {}
|
|
981
|
+
for now, bits in steps:
|
|
982
|
+
clock["now"] = now
|
|
983
|
+
_notify_progress(reported.append, state, InfoSize(0, 0),
|
|
984
|
+
InfoSize(0, bits), 10000)
|
|
985
|
+
return reported
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def test_notify_progress_throttles_by_progress_delta(monkeypatch):
|
|
989
|
+
"""Test small changes within 100 ms are skipped, >0.01 changes pass."""
|
|
990
|
+
reported = _run_notify(monkeypatch, [(0.0, 100), (0.01, 150),
|
|
991
|
+
(0.02, 300), (0.03, 301),
|
|
992
|
+
(0.04, 500)])
|
|
993
|
+
assert reported == [0.01, 0.03, 0.05]
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def test_notify_progress_throttles_by_time(monkeypatch):
|
|
997
|
+
"""Test tiny changes are reported once 100 ms have elapsed."""
|
|
998
|
+
reported = _run_notify(monkeypatch, [(0.0, 100), (0.05, 101),
|
|
999
|
+
(0.1, 102), (0.15, 103)])
|
|
1000
|
+
assert reported == [0.01, 0.0102]
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def test_notify_progress_always_reports_completion(monkeypatch):
|
|
1004
|
+
"""Test 1.0 is reported even if throttling conditions are not met."""
|
|
1005
|
+
reported = _run_notify(monkeypatch, [(0.0, 9995), (0.01, 10000)])
|
|
1006
|
+
assert reported == [0.9995, 1.0]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|