sltcodec 1.1.0__tar.gz → 1.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sltcodec
3
- Version: 1.1.0
3
+ Version: 1.2.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
@@ -145,7 +145,7 @@ Output:
145
145
  ```python
146
146
  bytearray(b"\x03\x04")
147
147
  StructInstance(struct_def=StructDef(...), field_instances=[...])
148
- [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=[...], scale=1.0, repeat=None),
148
+ [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=StructDef(...), scale=1.0, repeat=None),
149
149
  value=StructInstance(struct_def=StructDef(...), field_instances=[...]))]
150
150
  ```
151
151
 
@@ -204,6 +204,10 @@ print(encoded_float, decoded_float)
204
204
  You can persist reusable structure definitions by name with
205
205
  `save_struct_def_dict` / `load_struct_def_dict`.
206
206
 
207
+ `InfoSize` values in `offset` and `size` are saved as typed dictionaries in
208
+ JSON. Expression-based offsets and sizes remain strings and are resolved when
209
+ the definition is used.
210
+
207
211
  ```python
208
212
  from pathlib import Path
209
213
 
@@ -232,10 +236,48 @@ print(loaded["Header"].description)
232
236
  Example output:
233
237
 
234
238
  ```python
235
- bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances=[...])
236
- bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
239
+ Header
240
+ Simple header
241
+ ```
242
+
243
+ ## Enum Definitions
244
+
245
+ `FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
246
+ itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
247
+ `EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
248
+ attached as `FieldInstance.enum_item`.
249
+
250
+ ```python
251
+ from sltcore import InfoSize
252
+ from sltcodec import EnumDef, FieldDef, decode
253
+
254
+ status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
255
+
256
+ struct_def = [
257
+ FieldDef(name="status",
258
+ offset=InfoSize(0, 0),
259
+ size=InfoSize(1, 0),
260
+ type="unsigned int",
261
+ enum_def_name="Status"),
262
+ ]
263
+
264
+ decoded = decode(struct_def, bytearray(b"\x01"),
265
+ enum_def_dict={"Status": status_enum})
266
+
267
+ print(decoded.field_instances[0].value)
268
+ print(decoded.field_instances[0].enum_item)
269
+ ```
270
+
271
+ Output:
272
+
273
+ ```python
274
+ 1
275
+ ('NG', 1)
237
276
  ```
238
277
 
278
+ You can also persist enum definitions by name with
279
+ `save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
280
+
239
281
  ## Development
240
282
 
241
283
  ```bash
@@ -128,7 +128,7 @@ Output:
128
128
  ```python
129
129
  bytearray(b"\x03\x04")
130
130
  StructInstance(struct_def=StructDef(...), field_instances=[...])
131
- [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=[...], scale=1.0, repeat=None),
131
+ [FieldInstance(field_def=FieldDef(name='pair', offset=InfoSize(byte=0, bit=0), size=InfoSize(byte=2, bit=0), type=StructDef(...), scale=1.0, repeat=None),
132
132
  value=StructInstance(struct_def=StructDef(...), field_instances=[...]))]
133
133
  ```
134
134
 
@@ -187,6 +187,10 @@ print(encoded_float, decoded_float)
187
187
  You can persist reusable structure definitions by name with
188
188
  `save_struct_def_dict` / `load_struct_def_dict`.
189
189
 
190
+ `InfoSize` values in `offset` and `size` are saved as typed dictionaries in
191
+ JSON. Expression-based offsets and sizes remain strings and are resolved when
192
+ the definition is used.
193
+
190
194
  ```python
191
195
  from pathlib import Path
192
196
 
@@ -215,10 +219,48 @@ print(loaded["Header"].description)
215
219
  Example output:
216
220
 
217
221
  ```python
218
- bytearray(b"\x01\x07") StructInstance(struct_def=StructDef(...), field_instances=[...])
219
- bytearray(b"\x02?\xc0\x00\x00") StructInstance(struct_def=StructDef(...), field_instances=[...])
222
+ Header
223
+ Simple header
224
+ ```
225
+
226
+ ## Enum Definitions
227
+
228
+ `FieldDef.enum_def_name` holds only the name of an `EnumDef`, not the definition
229
+ itself. Pass the actual definitions via an `enum_def_dict` (mapping name to
230
+ `EnumDef`) to `decode` / `encode` so the matching `EnumDef` can be resolved and
231
+ attached as `FieldInstance.enum_item`.
232
+
233
+ ```python
234
+ from sltcore import InfoSize
235
+ from sltcodec import EnumDef, FieldDef, decode
236
+
237
+ status_enum = EnumDef(name="Status", values={"OK": 0, "NG": 1})
238
+
239
+ struct_def = [
240
+ FieldDef(name="status",
241
+ offset=InfoSize(0, 0),
242
+ size=InfoSize(1, 0),
243
+ type="unsigned int",
244
+ enum_def_name="Status"),
245
+ ]
246
+
247
+ decoded = decode(struct_def, bytearray(b"\x01"),
248
+ enum_def_dict={"Status": status_enum})
249
+
250
+ print(decoded.field_instances[0].value)
251
+ print(decoded.field_instances[0].enum_item)
252
+ ```
253
+
254
+ Output:
255
+
256
+ ```python
257
+ 1
258
+ ('NG', 1)
220
259
  ```
221
260
 
261
+ You can also persist enum definitions by name with
262
+ `save_enum_def_dict` / `load_enum_def_dict`, mirroring `StructDef` dictionaries.
263
+
222
264
  ## Development
223
265
 
224
266
  ```bash
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sltcodec"
3
- version = "1.1.0"
3
+ version = "1.2.1"
4
4
  description = "Decode and encode bytearrays according to struct layout definitions using sltcore."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -368,7 +368,7 @@ def decode_field(
368
368
  repeat=field_def.repeat,
369
369
  description=field_def.description,
370
370
  range_expression=field_def.range_expression,
371
- enum_def=field_def.enum_def,
371
+ enum_def_name=field_def.enum_def_name,
372
372
  byte_swap=byte_swap,
373
373
  )
374
374
  if isinstance(resolved_type, StructDef):
@@ -104,8 +104,9 @@ class FieldDef:
104
104
  default=None, metadata={"desc": "The description of the field"})
105
105
  range_expression: str | None = field(
106
106
  default=None, metadata={"desc": "The value range expression"})
107
- enum_def: EnumDef | None = field(
108
- default=None, metadata={"desc": "The enum definition for the field"})
107
+ enum_def_name: str | None = field(
108
+ default=None,
109
+ metadata={"desc": "The name of the enum definition for the field"})
109
110
  byte_swap: bool | str = field(
110
111
  default=False,
111
112
  metadata={"desc": "Whether to reverse the field byte order"})
@@ -137,7 +138,7 @@ class FieldDef:
137
138
  repeat=None,
138
139
  description=self.description,
139
140
  range_expression=split_range_expression,
140
- enum_def=self.enum_def,
141
+ enum_def_name=self.enum_def_name,
141
142
  byte_swap=split_byte_swap)
142
143
 
143
144
  @staticmethod
@@ -158,17 +159,19 @@ class FieldDef:
158
159
 
159
160
  def to_dict(self) -> dict[str, Any]:
160
161
  """Convert this field definition to a JSON-serializable dictionary."""
162
+
163
+ def _serialize_info_like(value: Any) -> Any:
164
+ if isinstance(value, (Info, InfoSize)):
165
+ return json.loads(value.to_json())
166
+ return value
167
+
161
168
  return {
162
169
  "name":
163
170
  self.name,
164
171
  "offset":
165
- self.offset.serialize() if isinstance(self.offset,
166
- (Info,
167
- InfoSize)) else self.offset,
172
+ _serialize_info_like(self.offset),
168
173
  "size":
169
- self.size.serialize() if isinstance(self.size,
170
- (Info,
171
- InfoSize)) else self.size,
174
+ _serialize_info_like(self.size),
172
175
  "type": {
173
176
  "__type__": "StructDef",
174
177
  "fields": self.type.to_dict(),
@@ -184,8 +187,8 @@ class FieldDef:
184
187
  self.description,
185
188
  "range_expression":
186
189
  self.range_expression,
187
- "enum_def":
188
- None if self.enum_def is None else self.enum_def.serialize(),
190
+ "enum_def_name":
191
+ self.enum_def_name,
189
192
  "byte_swap":
190
193
  self.byte_swap,
191
194
  }
@@ -205,7 +208,7 @@ class FieldDef:
205
208
  repeat = data.get("repeat")
206
209
  description = data.get("description")
207
210
  range_expression = data.get("range_expression")
208
- enum_def_data = data.get("enum_def")
211
+ enum_def_name = data.get("enum_def_name")
209
212
  byte_swap = data.get("byte_swap", False)
210
213
 
211
214
  def _deserialize_info_like(value: Any) -> Any:
@@ -242,14 +245,6 @@ class FieldDef:
242
245
  else:
243
246
  type_value = type_data
244
247
 
245
- if enum_def_data is None:
246
- enum_def = None
247
- elif (isinstance(enum_def_data, dict)
248
- and enum_def_data.get("__type__") == "EnumDef"):
249
- enum_def = EnumDef.deserialize(enum_def_data)
250
- else:
251
- enum_def = None
252
-
253
248
  return cls(
254
249
  name=name,
255
250
  offset=offset,
@@ -259,7 +254,7 @@ class FieldDef:
259
254
  repeat=repeat,
260
255
  description=description,
261
256
  range_expression=range_expression,
262
- enum_def=enum_def,
257
+ enum_def_name=enum_def_name,
263
258
  byte_swap=byte_swap,
264
259
  )
265
260
 
@@ -279,7 +274,7 @@ class FieldDef:
279
274
  -1 if self.repeat is None else self.repeat,
280
275
  "" if self.description is None else self.description,
281
276
  "" if self.range_expression is None else self.range_expression,
282
- self._sortable_enum_type(self.enum_def),
277
+ "" if self.enum_def_name is None else self.enum_def_name,
283
278
  self._sortable_byte_swap(self.byte_swap),
284
279
  )
285
280
 
@@ -297,19 +292,6 @@ class FieldDef:
297
292
  return (0, json.dumps(value.to_dict(), sort_keys=True))
298
293
  return (1, value)
299
294
 
300
- @staticmethod
301
- def _sortable_enum_type(value: EnumDef | None) -> str:
302
- """Build a comparable key for enum definition metadata."""
303
- if value is None:
304
- return ""
305
- return json.dumps(
306
- {
307
- "name": value.name,
308
- "description": value.description,
309
- "values": value.values,
310
- },
311
- sort_keys=True)
312
-
313
295
  @staticmethod
314
296
  def _sortable_byte_swap(value: bool | str) -> tuple[int, str]:
315
297
  """Build a comparable key for a byte-swap flag or expression."""
@@ -369,10 +351,10 @@ class FieldInstance:
369
351
  field_def: FieldDef,
370
352
  enum_def_dict: dict[str, EnumDef] | None) -> EnumDef | None:
371
353
  """Resolve enum definition from field metadata or lookup dictionary."""
372
- if field_def.enum_def is not None:
373
- return field_def.enum_def
374
354
  if not enum_def_dict:
375
355
  return None
356
+ if field_def.enum_def_name is not None:
357
+ return enum_def_dict.get(field_def.enum_def_name)
376
358
  if isinstance(field_def.type, str) and field_def.type in enum_def_dict:
377
359
  return enum_def_dict[field_def.type]
378
360
  return enum_def_dict.get(field_def.name)
@@ -1,4 +1,5 @@
1
1
  """Tests for the sltcodec module."""
2
+ import json
2
3
  from enum import Enum
3
4
  from pathlib import Path
4
5
 
@@ -179,7 +180,7 @@ def test_repeated_field_preserves_range_and_enum_metadata():
179
180
  type="unsigned int",
180
181
  repeat=2,
181
182
  range_expression="0 <= value <= 255",
182
- enum_def=enum_def)
183
+ enum_def_name=enum_def.name)
183
184
 
184
185
  encoded = encode(
185
186
  StructInstance(struct_def=StructDef(fields=[field_def]),
@@ -192,8 +193,8 @@ def test_repeated_field_preserves_range_and_enum_metadata():
192
193
  "0 <= value[0] <= 255")
193
194
  assert decoded.field_instances[1].field_def.range_expression == (
194
195
  "0 <= value[1] <= 255")
195
- assert decoded.field_instances[0].field_def.enum_def == enum_def
196
- assert decoded.field_instances[1].field_def.enum_def == enum_def
196
+ assert decoded.field_instances[0].field_def.enum_def_name == enum_def.name
197
+ assert decoded.field_instances[1].field_def.enum_def_name == enum_def.name
197
198
 
198
199
 
199
200
  def test_split_repeat_replaces_name_in_range_expression():
@@ -242,23 +243,15 @@ def test_field_def_to_json_from_json_round_trip():
242
243
  repeat=3,
243
244
  description="A repeated value",
244
245
  range_expression="0 <= value <= 255",
245
- enum_def=enum_def)
246
+ enum_def_name=enum_def.name)
246
247
 
247
248
  payload = field_def.to_json()
248
249
  restored = FieldDef.from_json(payload)
249
250
 
250
251
  assert restored == field_def
251
252
  assert restored.range_expression == "0 <= value <= 255"
252
- assert restored.enum_def == enum_def
253
- assert restored.to_dict()["enum_def"] == {
254
- "__type__": "EnumDef",
255
- "name": "ValueKind",
256
- "description": None,
257
- "values": {
258
- member.name: member.value
259
- for member in ValueKind
260
- },
261
- }
253
+ assert restored.enum_def_name == enum_def.name
254
+ assert restored.to_dict()["enum_def_name"] == "ValueKind"
262
255
 
263
256
 
264
257
  def test_field_def_byte_swap_json_round_trip():
@@ -425,9 +418,10 @@ def test_field_instance_from_value_sets_enum_item_from_field_enum_def():
425
418
  offset=InfoSize(0, 0),
426
419
  size=InfoSize(1, 0),
427
420
  type="unsigned int",
428
- enum_def=enum_def)
421
+ enum_def_name=enum_def.name)
429
422
 
430
- field_instance = FieldInstance.from_value(field_def, 1)
423
+ field_instance = FieldInstance.from_value(
424
+ field_def, 1, enum_def_dict={enum_def.name: enum_def})
431
425
 
432
426
  assert field_instance.enum_item == ("MANUAL", 1)
433
427
 
@@ -480,6 +474,10 @@ def test_struct_def_dict_load_and_save(tmp_path: Path):
480
474
  path = tmp_path / "field_defs.json"
481
475
 
482
476
  save_struct_def_dict(path, {"value": struct_def})
477
+ saved_field = json.loads(path.read_text())["value"]["fields"][0]
478
+
479
+ assert isinstance(saved_field["offset"], dict)
480
+ assert isinstance(saved_field["size"], dict)
483
481
  loaded = load_struct_def_dict(path)
484
482
 
485
483
  assert loaded["value"] == struct_def
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "sltcodec"
77
- version = "1.0.0"
77
+ version = "1.2.0"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "sltcalc" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes