jamcodec 0.1.4__tar.gz → 0.1.6__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.
- {jamcodec-0.1.4 → jamcodec-0.1.6}/PKG-INFO +1 -1
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/types.py +78 -17
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec.egg-info/PKG-INFO +1 -1
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec.egg-info/SOURCES.txt +1 -0
- jamcodec-0.1.6/test/test_bounded_vec.py +191 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/LICENSE +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/README.md +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/__init__.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/base.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/constants.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/exceptions.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec/mixins.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec.egg-info/dependency_links.txt +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec.egg-info/requires.txt +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/jamcodec.egg-info/top_level.txt +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/pyproject.toml +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/requirements.txt +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/setup.cfg +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_array.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_bitarray.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_bitvec.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_boolean.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_bytes.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_compact.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_enum.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_float.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_hashmap.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_integer.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_map.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_mixins.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_option.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_scale_types.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_scalebytes.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_string.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_struct.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_tuple.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_varint.py +0 -0
- {jamcodec-0.1.4 → jamcodec-0.1.6}/test/test_vec.py +0 -0
|
@@ -197,18 +197,18 @@ class Struct(JamCodecTypeDef):
|
|
|
197
197
|
|
|
198
198
|
def encode(self, value: dict) -> JamBytes:
|
|
199
199
|
|
|
200
|
-
data =
|
|
200
|
+
data = bytearray()
|
|
201
201
|
for name, scale_obj in self.arguments.items():
|
|
202
202
|
|
|
203
203
|
if name not in value:
|
|
204
204
|
raise ScaleEncodeException(f'Argument "{name}" of Struct is missing in given value')
|
|
205
205
|
|
|
206
|
-
data
|
|
206
|
+
data.extend(scale_obj.encode(value[name]).data)
|
|
207
207
|
|
|
208
208
|
if value[name] and issubclass(value[name].__class__, JamCodecType):
|
|
209
209
|
value[name] = value[name].serialize()
|
|
210
210
|
|
|
211
|
-
return data
|
|
211
|
+
return JamBytes(data)
|
|
212
212
|
|
|
213
213
|
def decode(self, data) -> dict:
|
|
214
214
|
value = {}
|
|
@@ -277,11 +277,11 @@ class Tuple(JamCodecTypeDef):
|
|
|
277
277
|
if type(value) is not tuple:
|
|
278
278
|
value = (value,)
|
|
279
279
|
|
|
280
|
-
data =
|
|
280
|
+
data = bytearray()
|
|
281
281
|
for idx, scale_obj in enumerate(self.values):
|
|
282
282
|
|
|
283
|
-
data
|
|
284
|
-
return data
|
|
283
|
+
data.extend(scale_obj.encode(value[idx]).data)
|
|
284
|
+
return JamBytes(data)
|
|
285
285
|
|
|
286
286
|
def decode(self, data: JamBytes) -> tuple:
|
|
287
287
|
value = ()
|
|
@@ -615,17 +615,17 @@ class Vec(JamCodecTypeDef):
|
|
|
615
615
|
return Bytes.encode(value)
|
|
616
616
|
|
|
617
617
|
# Encode length of Vec
|
|
618
|
-
data = VarInt64.encode(len(value))
|
|
618
|
+
data = VarInt64.encode(len(value)).data
|
|
619
619
|
|
|
620
620
|
for idx, item in enumerate(value):
|
|
621
621
|
if type(item) is JamBytes:
|
|
622
|
-
data
|
|
622
|
+
data.extend(item.data)
|
|
623
623
|
else:
|
|
624
|
-
data
|
|
624
|
+
data.extend(self.type_def.encode(item).data)
|
|
625
625
|
if item and issubclass(item.__class__, JamCodecType):
|
|
626
626
|
value[idx] = item.serialize()
|
|
627
627
|
|
|
628
|
-
return data
|
|
628
|
+
return JamBytes(data)
|
|
629
629
|
|
|
630
630
|
def decode(self, data: JamBytes) -> Union[list, bytes]:
|
|
631
631
|
|
|
@@ -675,6 +675,67 @@ class Vec(JamCodecTypeDef):
|
|
|
675
675
|
return [self.type_def.example_value()]
|
|
676
676
|
|
|
677
677
|
|
|
678
|
+
class BoundedVec(Vec):
|
|
679
|
+
"""A Vec with inclusive cardinality bounds and a canonical length prefix.
|
|
680
|
+
|
|
681
|
+
``multiple`` requires the element count to be divisible by that value.
|
|
682
|
+
Decoding checks the count before constructing or decoding any element.
|
|
683
|
+
Like Vec(U8), BoundedVec(U8, ...) represents bytes; bounds count bytes,
|
|
684
|
+
including when encoding or deserializing a hex or UTF-8 string.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
def __init__(self, type_def: JamCodecTypeDef, maximum: int, minimum: int = 0, multiple: int = 1):
|
|
688
|
+
if any(type(bound) is not int for bound in (maximum, minimum, multiple)):
|
|
689
|
+
raise ValueError('Sequence bounds and multiple must be integers')
|
|
690
|
+
if not 0 <= minimum <= maximum or multiple <= 0:
|
|
691
|
+
raise ValueError('Expected 0 <= minimum <= maximum and multiple > 0')
|
|
692
|
+
super().__init__(type_def)
|
|
693
|
+
self.maximum = maximum
|
|
694
|
+
self.minimum = minimum
|
|
695
|
+
self.multiple = multiple
|
|
696
|
+
|
|
697
|
+
def _check_count(self, count: int) -> None:
|
|
698
|
+
if not self.minimum <= count <= self.maximum or count % self.multiple:
|
|
699
|
+
raise ValueError(
|
|
700
|
+
f'invalid sequence length {count}: expected {self.minimum}..'
|
|
701
|
+
f'{self.maximum} in multiples of {self.multiple}'
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
def decode(self, data: JamBytes) -> Union[list, bytes]:
|
|
705
|
+
start = data.offset
|
|
706
|
+
count = VarInt64.decode(data)
|
|
707
|
+
if data.data[start:data.offset] != VarInt64.encode(count).data:
|
|
708
|
+
raise ValueError('non-canonical sequence length')
|
|
709
|
+
self._check_count(count)
|
|
710
|
+
|
|
711
|
+
if self.type_def is U8:
|
|
712
|
+
return bytes(data.get_next_bytes(count))
|
|
713
|
+
|
|
714
|
+
values = []
|
|
715
|
+
for _ in range(count):
|
|
716
|
+
item = self.type_def.new()
|
|
717
|
+
item.decode(data)
|
|
718
|
+
values.append(item)
|
|
719
|
+
return values
|
|
720
|
+
|
|
721
|
+
def encode(self, value: Union[list, bytes, bytearray, str]) -> JamBytes:
|
|
722
|
+
if self.type_def is U8:
|
|
723
|
+
value = Bytes.deserialize(value)
|
|
724
|
+
self._check_count(len(value))
|
|
725
|
+
return super().encode(value)
|
|
726
|
+
|
|
727
|
+
def deserialize(self, value: Union[list, bytes, bytearray, str]) -> Union[list, bytes]:
|
|
728
|
+
if self.type_def is U8:
|
|
729
|
+
value = Bytes.deserialize(value)
|
|
730
|
+
self._check_count(len(value))
|
|
731
|
+
return super().deserialize(value)
|
|
732
|
+
|
|
733
|
+
def to_serializable_obj(self, value_object: Union[list, bytes]):
|
|
734
|
+
if self.type_def is U8:
|
|
735
|
+
return value_object
|
|
736
|
+
return super().to_serializable_obj(value_object)
|
|
737
|
+
|
|
738
|
+
|
|
678
739
|
class BitVec(JamCodecTypeDef):
|
|
679
740
|
"""
|
|
680
741
|
A BitVec that represents an array of bits. The bits are however stored encoded. The difference between this
|
|
@@ -762,7 +823,7 @@ class Array(JamCodecTypeDef):
|
|
|
762
823
|
|
|
763
824
|
return JamBytes(value)
|
|
764
825
|
else:
|
|
765
|
-
data =
|
|
826
|
+
data = bytearray()
|
|
766
827
|
|
|
767
828
|
if type(value) is not list:
|
|
768
829
|
raise ScaleEncodeException("Value must be of type list")
|
|
@@ -771,9 +832,9 @@ class Array(JamCodecTypeDef):
|
|
|
771
832
|
raise ScaleEncodeException("Length of list does not match size of array")
|
|
772
833
|
|
|
773
834
|
for item in value:
|
|
774
|
-
data
|
|
835
|
+
data.extend(self.type_def.encode(item).data)
|
|
775
836
|
|
|
776
|
-
return data
|
|
837
|
+
return JamBytes(data)
|
|
777
838
|
|
|
778
839
|
def decode(self, data: JamBytes) -> Union[list, bytes]:
|
|
779
840
|
if self.type_def is U8:
|
|
@@ -981,13 +1042,13 @@ class Map(JamCodecTypeDef):
|
|
|
981
1042
|
value = sorted(value)
|
|
982
1043
|
|
|
983
1044
|
# Encode length of Vec
|
|
984
|
-
data = VarInt64.encode(len(value))
|
|
1045
|
+
data = VarInt64.encode(len(value)).data
|
|
985
1046
|
|
|
986
1047
|
for item_key, item_value in value:
|
|
987
|
-
data
|
|
988
|
-
data
|
|
1048
|
+
data.extend(self.key_def.encode(item_key).data)
|
|
1049
|
+
data.extend(self.value_def.encode(item_value).data)
|
|
989
1050
|
|
|
990
|
-
return data
|
|
1051
|
+
return JamBytes(data)
|
|
991
1052
|
|
|
992
1053
|
def decode(self, data: JamBytes) -> list:
|
|
993
1054
|
# Decode length of Map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from jamcodec.base import JamBytes, JamCodecTypeDef
|
|
5
|
+
from jamcodec.exceptions import RemainingScaleBytesNotEmptyException
|
|
6
|
+
from jamcodec.mixins import Serializable
|
|
7
|
+
from jamcodec.types import BoundedVec, U8, U16, VarInt64, Vec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NoElementAllocation(JamCodecTypeDef):
|
|
11
|
+
def new(self, **kwargs):
|
|
12
|
+
raise AssertionError('Invalid length must be rejected before creating elements')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class BoundedItem(Serializable):
|
|
17
|
+
number: int = field(metadata={'codec': U16})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BoundedPacket(Serializable):
|
|
22
|
+
items: list = field(metadata={'codec': BoundedVec(BoundedItem.to_codec_def(), 2, minimum=1)})
|
|
23
|
+
groups: list = field(metadata={'codec': BoundedVec(BoundedVec(U16, 2), 2)})
|
|
24
|
+
payload: bytes = field(metadata={'codec': BoundedVec(U8, 4, minimum=2, multiple=2)})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestBoundedVec(unittest.TestCase):
|
|
28
|
+
|
|
29
|
+
def test_invalid_constructor_constraints(self):
|
|
30
|
+
invalid = (
|
|
31
|
+
{'maximum': -1},
|
|
32
|
+
{'maximum': 2, 'minimum': -1},
|
|
33
|
+
{'maximum': 2, 'minimum': 3},
|
|
34
|
+
{'maximum': 2, 'multiple': 0},
|
|
35
|
+
{'maximum': 2, 'multiple': -1},
|
|
36
|
+
{'maximum': 2.0},
|
|
37
|
+
{'maximum': 2, 'minimum': '0'},
|
|
38
|
+
{'maximum': 2, 'multiple': 1.0},
|
|
39
|
+
{'maximum': True},
|
|
40
|
+
{'maximum': 2, 'minimum': False},
|
|
41
|
+
{'maximum': 2, 'multiple': True},
|
|
42
|
+
)
|
|
43
|
+
for constraints in invalid:
|
|
44
|
+
with self.subTest(constraints=constraints):
|
|
45
|
+
with self.assertRaises(ValueError):
|
|
46
|
+
BoundedVec(U16, **constraints)
|
|
47
|
+
|
|
48
|
+
def test_inclusive_bounds_and_multiple_roundtrip(self):
|
|
49
|
+
codec = BoundedVec(U16, maximum=6, minimum=2, multiple=2)
|
|
50
|
+
for count in (2, 4, 6):
|
|
51
|
+
with self.subTest(count=count):
|
|
52
|
+
value = list(range(count))
|
|
53
|
+
obj = codec.new()
|
|
54
|
+
encoded = obj.encode(value)
|
|
55
|
+
self.assertEqual(value, codec.new().decode(encoded))
|
|
56
|
+
self.assertEqual(value, codec.new(value=value).serialize())
|
|
57
|
+
|
|
58
|
+
def test_empty_vector_with_default_minimum(self):
|
|
59
|
+
codec = BoundedVec(U16, maximum=0)
|
|
60
|
+
self.assertEqual(JamBytes('0x00'), codec.encode([]))
|
|
61
|
+
self.assertEqual([], codec.deserialize([]))
|
|
62
|
+
self.assertEqual([], codec.decode(JamBytes('0x00')))
|
|
63
|
+
|
|
64
|
+
def test_encode_rejects_invalid_counts(self):
|
|
65
|
+
codec = BoundedVec(U16, maximum=4, minimum=2, multiple=2)
|
|
66
|
+
for count in (0, 1, 3, 5, 6):
|
|
67
|
+
with self.subTest(count=count):
|
|
68
|
+
with self.assertRaises(ValueError):
|
|
69
|
+
codec.encode([0] * count)
|
|
70
|
+
|
|
71
|
+
def test_deserialize_rejects_invalid_counts(self):
|
|
72
|
+
codec = BoundedVec(U16, maximum=4, minimum=2, multiple=2)
|
|
73
|
+
for count in (0, 1, 3, 5, 6):
|
|
74
|
+
with self.subTest(count=count):
|
|
75
|
+
with self.assertRaises(ValueError):
|
|
76
|
+
codec.new(value=[0] * count)
|
|
77
|
+
|
|
78
|
+
def test_decode_checks_count_before_element_allocation(self):
|
|
79
|
+
codec = BoundedVec(NoElementAllocation(), maximum=4, minimum=2, multiple=2)
|
|
80
|
+
for count in (0, 1, 3, 5, 6):
|
|
81
|
+
with self.subTest(count=count):
|
|
82
|
+
with self.assertRaises(ValueError):
|
|
83
|
+
codec.decode(VarInt64.encode(count))
|
|
84
|
+
|
|
85
|
+
def test_huge_count_is_rejected_before_element_allocation(self):
|
|
86
|
+
codec = BoundedVec(NoElementAllocation(), maximum=4)
|
|
87
|
+
data = VarInt64.encode(2 ** 64 - 1)
|
|
88
|
+
with self.assertRaises(ValueError):
|
|
89
|
+
codec.decode(data)
|
|
90
|
+
self.assertEqual(data.length, data.offset)
|
|
91
|
+
|
|
92
|
+
def test_noncanonical_prefix_at_nonzero_offset(self):
|
|
93
|
+
codec = BoundedVec(NoElementAllocation(), maximum=128)
|
|
94
|
+
for prefix in (b'\x80\x00', b'\x80\x01', b'\x80\x7f', b'\xc0\x80\x00', b'\xff' + bytes(8)):
|
|
95
|
+
with self.subTest(prefix=prefix.hex()):
|
|
96
|
+
data = JamBytes(b'\x99' + prefix + b'\x42')
|
|
97
|
+
data.offset = 1
|
|
98
|
+
with self.assertRaises(ValueError):
|
|
99
|
+
codec.decode(data)
|
|
100
|
+
self.assertEqual(1 + len(prefix), data.offset)
|
|
101
|
+
|
|
102
|
+
def test_truncated_prefix_at_nonzero_offset(self):
|
|
103
|
+
codec = BoundedVec(NoElementAllocation(), maximum=128)
|
|
104
|
+
prefixes = [b''] + [bytes([prefix]) + bytes(length - 1) for prefix, length in (
|
|
105
|
+
(0x80, 1), (0xc0, 2), (0xe0, 3), (0xf0, 4),
|
|
106
|
+
(0xf8, 5), (0xfc, 6), (0xfe, 7), (0xff, 8),
|
|
107
|
+
)]
|
|
108
|
+
for prefix in prefixes:
|
|
109
|
+
with self.subTest(prefix=prefix.hex()):
|
|
110
|
+
data = JamBytes(b'\x99' + prefix)
|
|
111
|
+
data.offset = 1
|
|
112
|
+
with self.assertRaises(RemainingScaleBytesNotEmptyException):
|
|
113
|
+
codec.decode(data)
|
|
114
|
+
|
|
115
|
+
def test_wire_encoding_matches_vec_across_prefix_boundary(self):
|
|
116
|
+
codec = BoundedVec(U16, maximum=128)
|
|
117
|
+
for count in (0, 1, 127, 128):
|
|
118
|
+
with self.subTest(count=count):
|
|
119
|
+
value = list(range(count))
|
|
120
|
+
encoded = codec.encode(value)
|
|
121
|
+
self.assertEqual(Vec(U16).encode(value), encoded)
|
|
122
|
+
self.assertEqual(value, codec.new().decode(encoded))
|
|
123
|
+
|
|
124
|
+
def test_new_preserves_vec_wrapper_semantics(self):
|
|
125
|
+
codec = BoundedVec(U16, maximum=2)
|
|
126
|
+
self.assertIsInstance(codec, Vec)
|
|
127
|
+
obj = codec.new(value=[1, 258])
|
|
128
|
+
self.assertEqual(2, len(obj))
|
|
129
|
+
self.assertEqual([1, 258], [item.value for item in obj.value_object])
|
|
130
|
+
encoded = obj.encode()
|
|
131
|
+
self.assertEqual(JamBytes('0x0201000201'), encoded)
|
|
132
|
+
decoded = codec.new(scale=encoded)
|
|
133
|
+
self.assertEqual([1, 258], decoded.value)
|
|
134
|
+
self.assertEqual([1, 258], decoded.to_serializable_obj())
|
|
135
|
+
|
|
136
|
+
def test_decode_consumes_only_vector_at_nonzero_offset(self):
|
|
137
|
+
data = JamBytes('0x99020100020142')
|
|
138
|
+
data.offset = 1
|
|
139
|
+
self.assertEqual([1, 258], BoundedVec(U16, 2).new().decode(data))
|
|
140
|
+
self.assertEqual(6, data.offset)
|
|
141
|
+
self.assertEqual(bytearray(b'\x42'), data.get_remaining_bytes())
|
|
142
|
+
|
|
143
|
+
def test_byte_inputs_use_normalized_cardinality(self):
|
|
144
|
+
codec = BoundedVec(U8, maximum=2, minimum=2, multiple=2)
|
|
145
|
+
for value in (b'\xc3\xa9', bytearray(b'\xc3\xa9'), [195, 169], '0xc3a9', 'é'):
|
|
146
|
+
with self.subTest(value=value):
|
|
147
|
+
self.assertEqual(JamBytes('0x02c3a9'), codec.encode(value))
|
|
148
|
+
self.assertEqual(b'\xc3\xa9', codec.deserialize(value))
|
|
149
|
+
|
|
150
|
+
def test_byte_bounds_reject_normalized_lengths(self):
|
|
151
|
+
codec = BoundedVec(U8, maximum=2, minimum=2, multiple=2)
|
|
152
|
+
for value in ('0x00', '0x000102', 'a', 'éa'):
|
|
153
|
+
for operation in (codec.encode, codec.deserialize):
|
|
154
|
+
with self.subTest(value=value, operation=operation.__name__):
|
|
155
|
+
with self.assertRaises(ValueError):
|
|
156
|
+
operation(value)
|
|
157
|
+
|
|
158
|
+
def test_bytes_decode_and_wrapper_match_vec(self):
|
|
159
|
+
codec = BoundedVec(U8, maximum=2)
|
|
160
|
+
self.assertEqual(b'\xc3\xa9', codec.decode(JamBytes('0x02c3a9')))
|
|
161
|
+
obj = codec.new(scale=JamBytes('0x02c3a9'))
|
|
162
|
+
self.assertEqual(Vec(U8).new().decode(JamBytes('0x02c3a9')), obj.value)
|
|
163
|
+
self.assertEqual('0xc3a9', obj.value)
|
|
164
|
+
self.assertEqual(b'\xc3\xa9', obj.value_object)
|
|
165
|
+
self.assertEqual(b'\xc3\xa9', obj.to_serializable_obj())
|
|
166
|
+
self.assertEqual(JamBytes('0x02c3a9'), obj.encode())
|
|
167
|
+
|
|
168
|
+
def test_truncated_element_payload(self):
|
|
169
|
+
for element, payload in ((U16, b'\x02\x01\x00\x02'), (U8, b'\x02\x01')):
|
|
170
|
+
with self.subTest(element=element):
|
|
171
|
+
with self.assertRaises(RemainingScaleBytesNotEmptyException):
|
|
172
|
+
BoundedVec(element, maximum=2, minimum=1).new().decode(JamBytes(payload))
|
|
173
|
+
|
|
174
|
+
def test_nested_serializable_dataclass_roundtrip(self):
|
|
175
|
+
packet = BoundedPacket(
|
|
176
|
+
items=[BoundedItem(1), BoundedItem(258)],
|
|
177
|
+
groups=[[3, 4], []],
|
|
178
|
+
payload=b'\xc3\xa9',
|
|
179
|
+
)
|
|
180
|
+
serialized = {
|
|
181
|
+
'items': [{'number': 1}, {'number': 258}],
|
|
182
|
+
'groups': [[3, 4], []],
|
|
183
|
+
'payload': '0xc3a9',
|
|
184
|
+
}
|
|
185
|
+
self.assertEqual(serialized, packet.serialize())
|
|
186
|
+
self.assertEqual(packet, BoundedPacket.deserialize(serialized))
|
|
187
|
+
self.assertEqual(packet, BoundedPacket.from_jam_bytes(packet.to_jam_bytes()))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
unittest.main()
|
|
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
|
|
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
|
|
File without changes
|