sltcodec 2.1.1__tar.gz → 2.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sltcodec
3
- Version: 2.1.1
3
+ Version: 2.2.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
@@ -11,7 +11,7 @@ License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  Keywords: byte,deserialization,layout,serialization,struct
13
13
  Requires-Python: >=3.12
14
- Requires-Dist: sltcalc>=0.1.0
14
+ Requires-Dist: sltcalc>=1.0.0
15
15
  Requires-Dist: sltcore>=1.5.0
16
16
  Description-Content-Type: text/markdown
17
17
 
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "sltcodec"
3
- version = "2.1.1"
3
+ version = "2.2.0"
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
- "sltcalc>=0.1.0",
8
+ "sltcalc>=1.0.0",
9
9
  "sltcore>=1.5.0",
10
10
  ]
11
11
  license = "MIT"
@@ -136,20 +136,12 @@ def _resolve_info_size(value: InfoSize | str, env: dict[str, Any]) -> InfoSize:
136
136
  if isinstance(value, str):
137
137
  stleval = SltEval(env)
138
138
  resolved_byte = stleval.eval(value)
139
+ if isinstance(resolved_byte, InfoSize):
140
+ return resolved_byte
139
141
  return InfoSize(resolved_byte, 0)
140
142
  return value
141
143
 
142
144
 
143
- def _resolve_offset(field_def: FieldDef, env: dict[str, Any]) -> InfoSize:
144
- """Resolve a field offset that can be static or expression-based."""
145
- return _resolve_info_size(field_def.offset, env)
146
-
147
-
148
- def _resolve_size(field_def: FieldDef, env: dict[str, Any]) -> InfoSize:
149
- """Resolve a field size that can be static or expression-based."""
150
- return _resolve_info_size(field_def.size, env)
151
-
152
-
153
145
  def _resolve_byte_swap(field_def: FieldDef, env: dict[str, Any]) -> bool:
154
146
  """Resolve a byte-swap flag that can be static or expression-based."""
155
147
  if isinstance(field_def.byte_swap, str):
@@ -157,27 +149,12 @@ def _resolve_byte_swap(field_def: FieldDef, env: dict[str, Any]) -> bool:
157
149
  return field_def.byte_swap
158
150
 
159
151
 
160
- def _byte_swap_info(info: Info, size: InfoSize) -> Info:
161
- """Reverse the byte representation of an Info payload."""
162
- return Info.from_bytes(info.to_bytes[::-1], size, scale=info.scale)
163
-
164
-
165
152
  def _is_padding_field_def(field_def: FieldDef) -> bool:
166
153
  """Check whether a field definition represents padding."""
167
154
  return (field_def.name.startswith("padding[")
168
155
  and field_def.type in ["bytes", "bytearray"])
169
156
 
170
157
 
171
- def _info_size_to_bits(info_size: InfoSize) -> int:
172
- """Convert InfoSize to an absolute bit count."""
173
- return info_size.byte * 8 + info_size.bit
174
-
175
-
176
- def _info_size_from_bits(total_bits: int) -> InfoSize:
177
- """Create InfoSize from an absolute bit count."""
178
- return InfoSize(total_bits >> 3, total_bits & 0x7)
179
-
180
-
181
158
  def _validate_padding_alignment_bits(padding_alignment_bits: int) -> None:
182
159
  """Validate padding alignment as a positive power-of-two bit size."""
183
160
  if (isinstance(padding_alignment_bits, bool)
@@ -237,8 +214,8 @@ def _prepare_field_info(
237
214
  padding_alignment_bits: int = _DEFAULT_PADDING_ALIGNMENT_BITS,
238
215
  ) -> tuple[InfoSize, InfoSize, Info] | None:
239
216
  """Resolve a field definition into an offset, size, and info payload."""
240
- offset = _resolve_offset(field_def, env)
241
- size = _resolve_size(field_def, env)
217
+ offset = _resolve_info_size(field_def.offset, env)
218
+ size = _resolve_info_size(field_def.size, env)
242
219
  if size.byte == 0 and size.bit == 0:
243
220
  return None
244
221
 
@@ -305,7 +282,7 @@ def encode_field(
305
282
 
306
283
  offset, size, info = prepared
307
284
  if _resolve_byte_swap(field_def, env):
308
- info = _byte_swap_info(info, size)
285
+ info = info.byte_swap
309
286
  required_bytes = (offset + size).bytes
310
287
  if len(buf) < required_bytes:
311
288
  buf.extend(b"\x00" * (required_bytes - len(buf)))
@@ -355,17 +332,17 @@ def encode(
355
332
  has_padding = True
356
333
  value = field_value.value
357
334
  if _is_padding_field_def(field_def):
358
- padding_size = _resolve_size(field_def, env)
335
+ padding_size = _resolve_info_size(field_def.size, env)
359
336
  value = bytearray(padding_size.bytes)
360
337
  if field_def.repeat is not None and field_def.repeat > 1:
361
- current_offset = _resolve_offset(field_def, env)
338
+ current_offset = _resolve_info_size(field_def.offset, env)
362
339
  values = list(value)
363
340
 
364
341
  for i in range(field_def.repeat):
365
342
  field_def_repeat = _repeated_field_def(field_def, i,
366
343
  current_offset,
367
344
  field_def.size)
368
- resolved_size = _resolve_size(field_def_repeat, env)
345
+ resolved_size = _resolve_info_size(field_def_repeat.size, env)
369
346
  encode_field(field_def_repeat, values[i], buf, env, type_dict,
370
347
  padding_alignment_bits)
371
348
  env[field_def_repeat.name] = values[i]
@@ -410,14 +387,14 @@ def decode_field(
410
387
  """
411
388
  if env is None:
412
389
  env = {}
413
- offset = _resolve_offset(field_def, env)
414
- size = _resolve_size(field_def, env)
390
+ offset = _resolve_info_size(field_def.offset, env)
391
+ size = _resolve_info_size(field_def.size, env)
415
392
  if size.byte == 0 and size.bit == 0:
416
393
  return None
417
394
  info = bits_get(data, offset, size, scale=field_def.scale)
418
395
  byte_swap = _resolve_byte_swap(field_def, env)
419
396
  if byte_swap:
420
- info = _byte_swap_info(info, size)
397
+ info = info.byte_swap
421
398
  resolved_type = _resolve_field_type(field_def.type, type_dict, env)
422
399
  nested_value = None
423
400
  if isinstance(resolved_type, StructDef):
@@ -526,22 +503,21 @@ def decode(
526
503
 
527
504
  def append_padding_until(target_offset: InfoSize) -> None:
528
505
  nonlocal current_position, padding_index
529
- start_bits = _info_size_to_bits(current_position)
530
- end_bits = _info_size_to_bits(target_offset)
506
+ start_bits = current_position.bits
507
+ end_bits = target_offset.bits
531
508
  if end_bits <= start_bits:
532
509
  return
533
510
 
534
511
  chunk_start_bits = start_bits
535
512
  while chunk_start_bits < end_bits:
536
- chunk_start = _info_size_from_bits(chunk_start_bits)
537
- bits_to_boundary = _info_size_to_bits(
538
- chunk_start.align_to(padding_alignment_bits))
513
+ chunk_start = InfoSize(0, chunk_start_bits)
514
+ bits_to_boundary = chunk_start.align_to(padding_alignment_bits).bits
539
515
  chunk_size_bits = bits_to_boundary or padding_alignment_bits
540
516
  chunk_end_bits = min(end_bits, chunk_start_bits + chunk_size_bits)
541
517
  padding_field_def = FieldDef(
542
518
  name=f"padding[{padding_index}]",
543
- offset=_info_size_from_bits(chunk_start_bits),
544
- size=_info_size_from_bits(chunk_end_bits - chunk_start_bits),
519
+ offset=InfoSize(0, chunk_start_bits),
520
+ size=InfoSize(0, chunk_end_bits - chunk_start_bits),
545
521
  type="bytes",
546
522
  description="Auto-generated padding",
547
523
  )
@@ -556,7 +532,7 @@ def decode(
556
532
  for field_def in _as_field_defs(struct_def_obj):
557
533
  # Handle non-repeated fields
558
534
  if field_def.repeat is None or field_def.repeat <= 1:
559
- resolved_offset = _resolve_offset(field_def, env)
535
+ resolved_offset = _resolve_info_size(field_def.offset, env)
560
536
  append_padding_until(resolved_offset)
561
537
  field_instance = decode_field(field_def, data, env, type_dict,
562
538
  padding_alignment_bits)
@@ -564,10 +540,10 @@ def decode(
564
540
  env[field_instance.field_def.name] = field_instance.value
565
541
  result.append_field_instance(field_instance)
566
542
  current_position = (resolved_offset +
567
- _resolve_size(field_def, env))
543
+ _resolve_info_size(field_def.size, env))
568
544
  continue
569
545
  # Handle repeated fields
570
- current_offset = _resolve_offset(field_def, env)
546
+ current_offset = _resolve_info_size(field_def.offset, env)
571
547
  append_padding_until(current_offset)
572
548
  for i in range(field_def.repeat):
573
549
  field_def_repeat = _repeated_field_def(field_def, i, current_offset,
@@ -579,7 +555,7 @@ def decode(
579
555
  result.append_field_instance(field_instance)
580
556
  actual_size = field_instance.field_def.size
581
557
  else:
582
- actual_size = _resolve_size(field_def_repeat, env)
558
+ actual_size = _resolve_info_size(field_def_repeat.size, env)
583
559
  if isinstance(current_offset, InfoSize) and isinstance(
584
560
  actual_size, InfoSize):
585
561
  current_offset += actual_size
@@ -7,7 +7,7 @@ from sltcore import InfoSize
7
7
  from sltcodec import (PRIMITIVE_TYPES, EnumDef, FieldDef, FieldInstance,
8
8
  StructDef, StructInstance, StructLayout, TypeDict, decode,
9
9
  encode)
10
- from sltcodec.codec import decode_field
10
+ from sltcodec.codec import _resolve_info_size, decode_field
11
11
 
12
12
 
13
13
  def layout_for(struct_def):
@@ -608,6 +608,15 @@ def test_decode_field_sets_actual_size_on_field_instance():
608
608
  assert decoded.field_def.size == InfoSize(1, 0)
609
609
 
610
610
 
611
+ def test_resolve_info_size_returns_expression_info_size_unchanged():
612
+ """Test that expressions resolving to InfoSize preserve the result."""
613
+ info_size = InfoSize(2, 3)
614
+
615
+ resolved = _resolve_info_size("field_size", {"field_size": info_size})
616
+
617
+ assert resolved is info_size
618
+
619
+
611
620
  def test_decode_struct_instance_size_matches_decoded_layout():
612
621
  """Test that decode reports the complete decoded layout size."""
613
622
  field_def = FieldDef(name="value",
@@ -65,16 +65,16 @@ wheels = [
65
65
 
66
66
  [[package]]
67
67
  name = "sltcalc"
68
- version = "0.1.0"
68
+ version = "1.0.0"
69
69
  source = { registry = "https://pypi.org/simple" }
70
- sdist = { url = "https://files.pythonhosted.org/packages/a2/16/2a988c23ac06d05c82e5555e0fb221a8e00786539ebe2826365dcb4f3abe/sltcalc-0.1.0.tar.gz", hash = "sha256:599dc33cf91d18dd257489989065debf5d20d28c299d95f586b371cc86af1b47", size = 10343, upload-time = "2026-08-01T15:06:22.695Z" }
70
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/01/19db81d0e1f1330ab884abf282569b3f05ffe1f2c7b8aff910125295d4be/sltcalc-1.0.0.tar.gz", hash = "sha256:968053bbecc85c30caf5ddfd48962e409c7844ccfc441418d029a4f743030350", size = 10499, upload-time = "2026-08-18T14:38:14.052Z" }
71
71
  wheels = [
72
- { url = "https://files.pythonhosted.org/packages/8a/45/c6f7d8074f1fb743e3902d71d8c028e9edeb032834639fdb72df213fdd11/sltcalc-0.1.0-py3-none-any.whl", hash = "sha256:20f06501a583aec62c670e48b4d21eba02a42997461bd8f85640055092d710fa", size = 4783, upload-time = "2026-08-01T15:06:21.411Z" },
72
+ { url = "https://files.pythonhosted.org/packages/da/77/6d03b5c160ffe42bee2fdabdd46521033794fb02e8975b4da7a1f1c641de/sltcalc-1.0.0-py3-none-any.whl", hash = "sha256:12531fef9633b25e6743c4a41873971c83dffb763590f42e2f1f78accea08088", size = 4824, upload-time = "2026-08-18T14:38:13.247Z" },
73
73
  ]
74
74
 
75
75
  [[package]]
76
76
  name = "sltcodec"
77
- version = "2.1.0"
77
+ version = "2.1.1"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "sltcalc" },
@@ -88,7 +88,7 @@ dev = [
88
88
 
89
89
  [package.metadata]
90
90
  requires-dist = [
91
- { name = "sltcalc", specifier = ">=0.1.0" },
91
+ { name = "sltcalc", specifier = ">=1.0.0" },
92
92
  { name = "sltcore", specifier = ">=1.5.0" },
93
93
  ]
94
94
 
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