sltcodec 2.1.0__tar.gz → 2.1.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.1.0 → sltcodec-2.1.1}/PKG-INFO +7 -5
- {sltcodec-2.1.0 → sltcodec-2.1.1}/README.md +6 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/README_pypi.md +6 -4
- {sltcodec-2.1.0 → sltcodec-2.1.1}/pyproject.toml +1 -1
- {sltcodec-2.1.0 → sltcodec-2.1.1}/src/sltcodec/codec.py +61 -28
- {sltcodec-2.1.0 → sltcodec-2.1.1}/src/sltcodec/types.py +0 -41
- {sltcodec-2.1.0 → sltcodec-2.1.1}/tests/test_codec.py +49 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/tests/test_types.py +0 -17
- {sltcodec-2.1.0 → sltcodec-2.1.1}/uv.lock +1 -1
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.gitignore +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.python-version +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.vscode/launch.json +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/.vscode/settings.json +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/LICENSE +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/bump_major.ps1 +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/bump_minor.ps1 +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/bump_patch.ps1 +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/src/sltcodec/__init__.py +0 -0
- {sltcodec-2.1.0 → sltcodec-2.1.1}/tests/test_persistence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -63,6 +63,12 @@ encoded = encode(layout, instance, bytearray())
|
|
|
63
63
|
decoded = decode(layout, encoded)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
`decode` returns a `StructInstance` whose `size` is the actual end of the
|
|
67
|
+
decoded layout. `decode_field` stores the actual decoded size in the returned
|
|
68
|
+
`FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
|
|
69
|
+
from the nested `StructInstance.size`; for repeated fields, the next element's
|
|
70
|
+
offset advances by the actual size of the previous element.
|
|
71
|
+
|
|
66
72
|
## Field Types
|
|
67
73
|
|
|
68
74
|
The public `PRIMITIVE_TYPES` set contains:
|
|
@@ -78,7 +84,6 @@ The public `PRIMITIVE_TYPES` set contains:
|
|
|
78
84
|
Store named structures in `TypeDict.struct_dict` and enums in `TypeDict.enum_dict`. A field refers to an enum by its `enum_def_name`; the codec resolves it through `StructLayout.type_dict`.
|
|
79
85
|
|
|
80
86
|
```python
|
|
81
|
-
| `__lt__(other)` | Compare enum definitions using their serialized ordering. |
|
|
82
87
|
from sltcore import InfoSize
|
|
83
88
|
from sltcodec import EnumDef, FieldDef, StructDef, StructLayout, TypeDict, decode
|
|
84
89
|
|
|
@@ -102,7 +107,6 @@ layout = StructLayout(
|
|
|
102
107
|
)
|
|
103
108
|
|
|
104
109
|
decoded = decode(layout, bytearray(b"\x01"))
|
|
105
|
-
| `__lt__(other)` | Compare field definitions using their serialized ordering. |
|
|
106
110
|
assert decoded.field_instances[0].enum_item == ("NG", 1)
|
|
107
111
|
```
|
|
108
112
|
|
|
@@ -117,7 +121,6 @@ from sltcodec import load_struct_layout, save_struct_layout
|
|
|
117
121
|
path = Path("struct_layout.json")
|
|
118
122
|
save_struct_layout(layout, path)
|
|
119
123
|
loaded = load_struct_layout(path)
|
|
120
|
-
| `__lt__(other)` | Compare field instances using field-definition order. |
|
|
121
124
|
assert loaded == layout
|
|
122
125
|
```
|
|
123
126
|
|
|
@@ -185,7 +188,6 @@ internal implementation details and are omitted from the public method tables.
|
|
|
185
188
|
|
|
186
189
|
| Method | Description |
|
|
187
190
|
| --- | --- |
|
|
188
|
-
| `split_repeat(index, offset=None, size=None)` | Create one field definition from a repeated field. |
|
|
189
191
|
| `to_dict()` | Convert the definition to a JSON-compatible dictionary. |
|
|
190
192
|
| `to_json()` | Convert the definition to a JSON string. |
|
|
191
193
|
| `from_dict(data)` | Create a `FieldDef` from a dictionary. |
|
|
@@ -74,6 +74,12 @@ updated_field = field_instance.with_value(
|
|
|
74
74
|
)
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
Decoded sizes reflect the actual data consumed. `decode` sets
|
|
78
|
+
`StructInstance.size` to the end of the decoded layout, and `decode_field`
|
|
79
|
+
stores the actual size in `FieldInstance.field_def.size`. For nested structures,
|
|
80
|
+
the nested `StructInstance.size` is used; repeated fields advance each next
|
|
81
|
+
offset by the preceding element's actual size.
|
|
82
|
+
|
|
77
83
|
## Versioning And Release
|
|
78
84
|
|
|
79
85
|
Version bump scripts are provided for patch, minor, and major releases:
|
|
@@ -46,6 +46,12 @@ encoded = encode(layout, instance, bytearray())
|
|
|
46
46
|
decoded = decode(layout, encoded)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
`decode` returns a `StructInstance` whose `size` is the actual end of the
|
|
50
|
+
decoded layout. `decode_field` stores the actual decoded size in the returned
|
|
51
|
+
`FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
|
|
52
|
+
from the nested `StructInstance.size`; for repeated fields, the next element's
|
|
53
|
+
offset advances by the actual size of the previous element.
|
|
54
|
+
|
|
49
55
|
## Field Types
|
|
50
56
|
|
|
51
57
|
The public `PRIMITIVE_TYPES` set contains:
|
|
@@ -61,7 +67,6 @@ The public `PRIMITIVE_TYPES` set contains:
|
|
|
61
67
|
Store named structures in `TypeDict.struct_dict` and enums in `TypeDict.enum_dict`. A field refers to an enum by its `enum_def_name`; the codec resolves it through `StructLayout.type_dict`.
|
|
62
68
|
|
|
63
69
|
```python
|
|
64
|
-
| `__lt__(other)` | Compare enum definitions using their serialized ordering. |
|
|
65
70
|
from sltcore import InfoSize
|
|
66
71
|
from sltcodec import EnumDef, FieldDef, StructDef, StructLayout, TypeDict, decode
|
|
67
72
|
|
|
@@ -85,7 +90,6 @@ layout = StructLayout(
|
|
|
85
90
|
)
|
|
86
91
|
|
|
87
92
|
decoded = decode(layout, bytearray(b"\x01"))
|
|
88
|
-
| `__lt__(other)` | Compare field definitions using their serialized ordering. |
|
|
89
93
|
assert decoded.field_instances[0].enum_item == ("NG", 1)
|
|
90
94
|
```
|
|
91
95
|
|
|
@@ -100,7 +104,6 @@ from sltcodec import load_struct_layout, save_struct_layout
|
|
|
100
104
|
path = Path("struct_layout.json")
|
|
101
105
|
save_struct_layout(layout, path)
|
|
102
106
|
loaded = load_struct_layout(path)
|
|
103
|
-
| `__lt__(other)` | Compare field instances using field-definition order. |
|
|
104
107
|
assert loaded == layout
|
|
105
108
|
```
|
|
106
109
|
|
|
@@ -168,7 +171,6 @@ internal implementation details and are omitted from the public method tables.
|
|
|
168
171
|
|
|
169
172
|
| Method | Description |
|
|
170
173
|
| --- | --- |
|
|
171
|
-
| `split_repeat(index, offset=None, size=None)` | Create one field definition from a repeated field. |
|
|
172
174
|
| `to_dict()` | Convert the definition to a JSON-compatible dictionary. |
|
|
173
175
|
| `to_json()` | Convert the definition to a JSON string. |
|
|
174
176
|
| `from_dict(data)` | Create a `FieldDef` from a dictionary. |
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import re
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -259,17 +260,30 @@ def _prepare_field_info(
|
|
|
259
260
|
return offset, size, info
|
|
260
261
|
|
|
261
262
|
|
|
262
|
-
def
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
263
|
+
def _repeated_field_def(field_def: FieldDef, index: int, offset: InfoSize,
|
|
264
|
+
size: InfoSize | str) -> FieldDef:
|
|
265
|
+
"""Create a resolved field definition for one repeated value."""
|
|
266
|
+
repeated_name = f"{field_def.name}[{index}]"
|
|
267
|
+
|
|
268
|
+
def replace_name(value: Any) -> Any:
|
|
269
|
+
if not isinstance(value, str):
|
|
270
|
+
return value
|
|
271
|
+
pattern = (rf"(?<![0-9A-Za-z_]){re.escape(field_def.name)}"
|
|
272
|
+
rf"(?![0-9A-Za-z_])")
|
|
273
|
+
return re.sub(pattern, repeated_name, value)
|
|
274
|
+
|
|
275
|
+
return FieldDef(
|
|
276
|
+
name=repeated_name,
|
|
277
|
+
offset=offset,
|
|
278
|
+
size=size,
|
|
279
|
+
type=replace_name(field_def.type),
|
|
280
|
+
scale=field_def.scale,
|
|
281
|
+
repeat=None,
|
|
282
|
+
description=field_def.description,
|
|
283
|
+
range_expression=replace_name(field_def.range_expression),
|
|
284
|
+
enum_def_name=field_def.enum_def_name,
|
|
285
|
+
byte_swap=replace_name(field_def.byte_swap),
|
|
286
|
+
)
|
|
273
287
|
|
|
274
288
|
|
|
275
289
|
def encode_field(
|
|
@@ -345,17 +359,17 @@ def encode(
|
|
|
345
359
|
value = bytearray(padding_size.bytes)
|
|
346
360
|
if field_def.repeat is not None and field_def.repeat > 1:
|
|
347
361
|
current_offset = _resolve_offset(field_def, env)
|
|
348
|
-
resolved_size = _resolve_size(field_def, env)
|
|
349
362
|
values = list(value)
|
|
350
363
|
|
|
351
364
|
for i in range(field_def.repeat):
|
|
352
|
-
field_def_repeat =
|
|
353
|
-
|
|
365
|
+
field_def_repeat = _repeated_field_def(field_def, i,
|
|
366
|
+
current_offset,
|
|
367
|
+
field_def.size)
|
|
368
|
+
resolved_size = _resolve_size(field_def_repeat, env)
|
|
354
369
|
encode_field(field_def_repeat, values[i], buf, env, type_dict,
|
|
355
370
|
padding_alignment_bits)
|
|
356
371
|
env[field_def_repeat.name] = values[i]
|
|
357
|
-
if isinstance(current_offset, InfoSize)
|
|
358
|
-
resolved_size, InfoSize):
|
|
372
|
+
if isinstance(current_offset, InfoSize):
|
|
359
373
|
current_offset += resolved_size
|
|
360
374
|
continue
|
|
361
375
|
|
|
@@ -405,10 +419,20 @@ def decode_field(
|
|
|
405
419
|
if byte_swap:
|
|
406
420
|
info = _byte_swap_info(info, size)
|
|
407
421
|
resolved_type = _resolve_field_type(field_def.type, type_dict, env)
|
|
422
|
+
nested_value = None
|
|
423
|
+
if isinstance(resolved_type, StructDef):
|
|
424
|
+
nested_layout = _layout_for_struct_def(resolved_type,
|
|
425
|
+
type_dict,
|
|
426
|
+
name=field_def.name)
|
|
427
|
+
nested_value = decode(nested_layout, bytearray(info.to_bytes),
|
|
428
|
+
padding_alignment_bits)
|
|
429
|
+
actual_size = nested_value.size
|
|
430
|
+
else:
|
|
431
|
+
actual_size = info.info_size
|
|
408
432
|
resolved_field_def = FieldDef(
|
|
409
433
|
name=field_def.name,
|
|
410
434
|
offset=offset,
|
|
411
|
-
size=
|
|
435
|
+
size=actual_size,
|
|
412
436
|
type=resolved_type,
|
|
413
437
|
scale=field_def.scale,
|
|
414
438
|
repeat=field_def.repeat,
|
|
@@ -418,13 +442,9 @@ def decode_field(
|
|
|
418
442
|
byte_swap=byte_swap,
|
|
419
443
|
)
|
|
420
444
|
if isinstance(resolved_type, StructDef):
|
|
421
|
-
nested_layout = _layout_for_struct_def(resolved_type,
|
|
422
|
-
type_dict,
|
|
423
|
-
name=field_def.name)
|
|
424
445
|
return FieldInstance(
|
|
425
446
|
field_def=resolved_field_def,
|
|
426
|
-
value=
|
|
427
|
-
padding_alignment_bits),
|
|
447
|
+
value=nested_value,
|
|
428
448
|
)
|
|
429
449
|
if resolved_type == "bool":
|
|
430
450
|
return FieldInstance.from_value(resolved_field_def,
|
|
@@ -548,19 +568,32 @@ def decode(
|
|
|
548
568
|
continue
|
|
549
569
|
# Handle repeated fields
|
|
550
570
|
current_offset = _resolve_offset(field_def, env)
|
|
551
|
-
resolved_size = _resolve_size(field_def, env)
|
|
552
571
|
append_padding_until(current_offset)
|
|
553
572
|
for i in range(field_def.repeat):
|
|
554
|
-
field_def_repeat =
|
|
555
|
-
|
|
573
|
+
field_def_repeat = _repeated_field_def(field_def, i, current_offset,
|
|
574
|
+
field_def.size)
|
|
556
575
|
field_instance = decode_field(field_def_repeat, data, env,
|
|
557
576
|
type_dict, padding_alignment_bits)
|
|
558
577
|
if field_instance is not None:
|
|
559
578
|
env[field_instance.field_def.name] = field_instance.value
|
|
560
579
|
result.append_field_instance(field_instance)
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
580
|
+
actual_size = field_instance.field_def.size
|
|
581
|
+
else:
|
|
582
|
+
actual_size = _resolve_size(field_def_repeat, env)
|
|
583
|
+
if isinstance(current_offset, InfoSize) and isinstance(
|
|
584
|
+
actual_size, InfoSize):
|
|
585
|
+
current_offset += actual_size
|
|
586
|
+
current_position = current_offset
|
|
587
|
+
|
|
588
|
+
actual_size = InfoSize(0, 0)
|
|
589
|
+
for field_instance in result.field_instances:
|
|
590
|
+
field_def = field_instance.field_def
|
|
591
|
+
if (isinstance(field_def.offset, InfoSize)
|
|
592
|
+
and isinstance(field_def.size, InfoSize)):
|
|
593
|
+
field_end = field_def.offset + field_def.size
|
|
594
|
+
if field_end > actual_size:
|
|
595
|
+
actual_size = field_end
|
|
596
|
+
result.size = actual_size
|
|
564
597
|
append_padding_until(result.size)
|
|
565
598
|
|
|
566
599
|
return result
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
-
import re
|
|
6
5
|
from collections.abc import MutableMapping
|
|
7
6
|
from dataclasses import dataclass, field
|
|
8
7
|
from functools import total_ordering
|
|
@@ -112,46 +111,6 @@ class FieldDef:
|
|
|
112
111
|
default=False,
|
|
113
112
|
metadata={"desc": "Whether to reverse the field byte order"})
|
|
114
113
|
|
|
115
|
-
def split_repeat(self,
|
|
116
|
-
index: int,
|
|
117
|
-
offset: InfoSize | str | None = None,
|
|
118
|
-
size: InfoSize | str | None = None) -> "FieldDef":
|
|
119
|
-
"""Create a single repeated-field definition for the given index."""
|
|
120
|
-
split_name = f"{self.name}[{index}]"
|
|
121
|
-
split_offset_source = self.offset if offset is None else offset
|
|
122
|
-
split_size_source = self.size if size is None else size
|
|
123
|
-
split_offset = self._replace_name_in_expression(split_offset_source,
|
|
124
|
-
self.name, split_name)
|
|
125
|
-
split_size = self._replace_name_in_expression(split_size_source,
|
|
126
|
-
self.name, split_name)
|
|
127
|
-
split_type = self._replace_name_in_expression(self.type, self.name,
|
|
128
|
-
split_name)
|
|
129
|
-
split_range_expression = self._replace_name_in_expression(
|
|
130
|
-
self.range_expression, self.name, split_name)
|
|
131
|
-
split_byte_swap = self._replace_name_in_expression(
|
|
132
|
-
self.byte_swap, self.name, split_name)
|
|
133
|
-
|
|
134
|
-
return FieldDef(name=split_name,
|
|
135
|
-
offset=split_offset,
|
|
136
|
-
size=split_size,
|
|
137
|
-
type=split_type,
|
|
138
|
-
scale=self.scale,
|
|
139
|
-
repeat=None,
|
|
140
|
-
description=self.description,
|
|
141
|
-
range_expression=split_range_expression,
|
|
142
|
-
enum_def_name=self.enum_def_name,
|
|
143
|
-
byte_swap=split_byte_swap)
|
|
144
|
-
|
|
145
|
-
@staticmethod
|
|
146
|
-
def _replace_name_in_expression(value: Any, old_name: str,
|
|
147
|
-
new_name: str) -> Any:
|
|
148
|
-
"""Replace standalone old_name tokens in expression-like strings."""
|
|
149
|
-
if not isinstance(value, str):
|
|
150
|
-
return value
|
|
151
|
-
pattern = (rf"(?<![0-9A-Za-z_]){re.escape(old_name)}"
|
|
152
|
-
rf"(?![0-9A-Za-z_])")
|
|
153
|
-
return re.sub(pattern, lambda _: new_name, value)
|
|
154
|
-
|
|
155
114
|
def __lt__(self, other: object) -> bool:
|
|
156
115
|
"""Compare field definitions using a stable serialized sort key."""
|
|
157
116
|
if not isinstance(other, FieldDef):
|
|
@@ -215,6 +215,24 @@ def test_encode_recurses_for_nested_field_types():
|
|
|
215
215
|
]
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
def test_decode_field_uses_nested_struct_instance_size():
|
|
219
|
+
"""Test that nested decoding determines the field's actual size."""
|
|
220
|
+
child_field_def = FieldDef(name="value",
|
|
221
|
+
offset=InfoSize(0, 0),
|
|
222
|
+
size=InfoSize(1, 0),
|
|
223
|
+
type="unsigned int")
|
|
224
|
+
parent_field_def = FieldDef(name="nested",
|
|
225
|
+
offset=InfoSize(0, 0),
|
|
226
|
+
size=InfoSize(4, 0),
|
|
227
|
+
type=StructDef(fields=[child_field_def]))
|
|
228
|
+
|
|
229
|
+
decoded = decode_field(parent_field_def, bytearray(b"\x7f"))
|
|
230
|
+
|
|
231
|
+
assert decoded is not None
|
|
232
|
+
assert decoded.field_def.size == InfoSize(1, 0)
|
|
233
|
+
assert decoded.value.size == InfoSize(1, 0)
|
|
234
|
+
|
|
235
|
+
|
|
218
236
|
def test_repeated_field_preserves_description():
|
|
219
237
|
"""Test that repeated fields keep their description on decoded entries."""
|
|
220
238
|
field_def = FieldDef(name="value",
|
|
@@ -572,6 +590,37 @@ def test_decode_field_returns_field_instance():
|
|
|
572
590
|
assert decoded == FieldInstance(field_def=field_def, value=127)
|
|
573
591
|
|
|
574
592
|
|
|
593
|
+
def test_decode_field_sets_actual_size_on_field_instance():
|
|
594
|
+
"""Test that decode_field stores the resolved Info size."""
|
|
595
|
+
field_def = FieldDef(name="value",
|
|
596
|
+
offset="offset",
|
|
597
|
+
size="field_size",
|
|
598
|
+
type="unsigned int")
|
|
599
|
+
|
|
600
|
+
decoded = decode_field(field_def,
|
|
601
|
+
bytearray(b"\x7f"),
|
|
602
|
+
env={
|
|
603
|
+
"offset": 0,
|
|
604
|
+
"field_size": 1
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
assert decoded is not None
|
|
608
|
+
assert decoded.field_def.size == InfoSize(1, 0)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def test_decode_struct_instance_size_matches_decoded_layout():
|
|
612
|
+
"""Test that decode reports the complete decoded layout size."""
|
|
613
|
+
field_def = FieldDef(name="value",
|
|
614
|
+
offset=InfoSize(2, 0),
|
|
615
|
+
size=InfoSize(2, 0),
|
|
616
|
+
type="unsigned int")
|
|
617
|
+
data = bytearray(b"\x00\x00\x7f\x00")
|
|
618
|
+
|
|
619
|
+
decoded = decode(layout_for(StructDef(fields=[field_def])), data)
|
|
620
|
+
|
|
621
|
+
assert decoded.size == InfoSize(len(data), 0)
|
|
622
|
+
|
|
623
|
+
|
|
575
624
|
def test_encode_padding_fields_use_zero_value_and_extend_to_struct_size():
|
|
576
625
|
"""Test that padding fields are zeroed and trailing bytes are padded.
|
|
577
626
|
|
|
@@ -13,23 +13,6 @@ class ValueKind(Enum):
|
|
|
13
13
|
B = 2
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def test_split_repeat_replaces_name_in_range_expression():
|
|
17
|
-
"""Test split_repeat replaces field-name tokens in range expressions."""
|
|
18
|
-
field_def = FieldDef(
|
|
19
|
-
name="value",
|
|
20
|
-
offset=InfoSize(0, 0),
|
|
21
|
-
size=InfoSize(1, 0),
|
|
22
|
-
type="unsigned int",
|
|
23
|
-
repeat=2,
|
|
24
|
-
range_expression=("0 <= value and value <= limit and value_count"))
|
|
25
|
-
|
|
26
|
-
split = field_def.split_repeat(1)
|
|
27
|
-
|
|
28
|
-
assert split.name == "value[1]"
|
|
29
|
-
assert split.range_expression == (
|
|
30
|
-
"0 <= value[1] and value[1] <= limit and value_count")
|
|
31
|
-
|
|
32
|
-
|
|
33
16
|
def test_field_def_json_round_trip():
|
|
34
17
|
"""Test that FieldDef can be serialized and deserialized from JSON."""
|
|
35
18
|
field_def = FieldDef(name="value",
|
|
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
|