diff --git a/CHANGELOG.md b/CHANGELOG.md index e3bf669a7f59f0df4f779d0b909150cf418e26c3..ca258b139b1b4588d3376a8e0fa47b6e0d1f31b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,4 @@ # v2.18.3 + +* fixed a bug with `get_remaining_bytes` +* added `unpack_dict` diff --git a/docs/coding/transforms.rst b/docs/coding/transforms.rst index 702c54c160d7457863a7401468cdb2391a3bf0a3..4f44c5eef1958dbb3897dd6df09574e251f19553 100644 --- a/docs/coding/transforms.rst +++ b/docs/coding/transforms.rst @@ -1,6 +1,8 @@ Rudimentary data transforms and algorithms ========================================== +.. autofunction:: satella.coding.transforms.unpack_dict + .. autofunction:: satella.coding.transforms.is_subset .. autoclass:: satella.coding.transforms.merge_list diff --git a/satella/__init__.py b/satella/__init__.py index 4c424a21848cdebf01103e4ecc8ddc7ddd1db505..340f88b41b31755248195bff8411706fcc057261 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.18.3a1' +__version__ = '2.18.3' diff --git a/satella/coding/transforms/__init__.py b/satella/coding/transforms/__init__.py index 4104511eb284b4f60d58f37215e2ad4126e70f59..76c35cb3091de4e5380176399c5cabe87ad4cf77 100644 --- a/satella/coding/transforms/__init__.py +++ b/satella/coding/transforms/__init__.py @@ -16,9 +16,27 @@ from .predicates import is_subset __all__ = ['stringify', 'split_shuffle_and_join', 'one_tuple', 'none_if_false', 'merge_series', 'pad_to_multiple_of_length', 'clip', 'hashables_to_int', 'jsonify', 'intify', 'percentile', 'b64encode', 'linear_interpolate', - 'merge_list', 'is_subset'] + 'merge_list', 'is_subset', 'unpack_dict'] -from satella.coding.typing import T, NoArgCallable, Appendable, Number, Predicate +from satella.coding.typing import T, NoArgCallable, Appendable, Number, Predicate, K, V + + +def unpack_dict(dct: tp.Dict[K, V], *args: K) -> tp.Iterator[V]: + """ + Unpack a dictionary by accessing it's given keys in parallel. + + Example: + + >>> a, b, c = unpack_dict({1:2, 2:3, 4:5}, 1, 2, 4) + >>> assert a == 2 and b == 3 and c == 5 + + :param dct: dictionary to unpack + :param args: keys in this dictionary + :return: an iterator + :raises KeyError: a key was not found + """ + for key in args: + yield dct[key] def none_if_false(y: tp.Any) -> tp.Optional[tp.Any]: diff --git a/satella/parsing.py b/satella/parsing.py index b9cac307394d716eaa5649f6f1e968ed0a6f49a7..5c93095b089675634949fcaa7b63c73d4f2a5859 100644 --- a/satella/parsing.py +++ b/satella/parsing.py @@ -12,13 +12,17 @@ class BinaryParser: This supports __len__ to return the amount of bytes in the stream, and __bytes__ to return the bytes. - :param b_stream: an object that allows indiced access, and allows subscripts to - span ranges, which will return items parseable by struct + This is a zero-copy solution, and :meth:`get_parser` will be zero copy + as well. + + :param b_stream: an object that allows subscripts to + span ranges, which will return items parsable by struct :param offset: initial offset into the stream :param length: optional maximum length of byte count :raises NotEnoughBytes: offset larger than stream length - :ivar offset: offset from which bytes will be readed + :ivar pointer: pointer to the next bytes. Can be read and modified at will to + preserve the earlier state of the BinaryParser. """ def __len__(self) -> int: return self.length @@ -53,7 +57,9 @@ class BinaryParser: def assert_has_bytes(self, n: int) -> None: """ - Assert that we have at least n bytes to consume + Assert that we have at least n bytes to consume. + + This does not advance the pointer. :param n: amount of bytes to consume :raises NotEnoughBytes: not enough bytes remain in the stream! @@ -65,7 +71,7 @@ class BinaryParser: """ Return a subclassed binary parser providing a window to another binary parser's data. - This will advance the pointer by length + This will advance the pointer by length bytes :param length: amount of bytes to view :return: a BinaryParser @@ -117,7 +123,8 @@ class BinaryParser: This must be a single-character struct! - This will advance the pointer by size of st. + This will advance the pointer by size of st. Struct objects + will be served from internal instance-specific cache. :param st: a single-character struct.Struct or a single character struct specification :return: a value returned from it @@ -145,7 +152,8 @@ class BinaryParser: """ Try to obtain as many bytes as this struct requires and return them parsed. - This will advance the pointer by size of st. + This will advance the pointer by size of st. Struct objects + will be served from internal instance-specific cache. :param st: a struct.Struct or a multi character struct specification :return: a tuple of un-parsed values @@ -165,4 +173,6 @@ class BinaryParser: This will not advance the pointer """ - return self.b_stream[self.pointer:self.pointer+self.length] + advance = self.pointer - self.init_ofs + remaining = self.length - advance + return self.b_stream[self.pointer:self.pointer+remaining] diff --git a/tests/test_coding/test_transforms.py b/tests/test_coding/test_transforms.py index 202767ab17b848dfe72cd08536c35bcb4c3d9829..93d7d2b170560612f485e38b134fadb4afb8eda2 100644 --- a/tests/test_coding/test_transforms.py +++ b/tests/test_coding/test_transforms.py @@ -5,11 +5,15 @@ import base64 from satella.coding.transforms import stringify, split_shuffle_and_join, one_tuple, \ merge_series, pad_to_multiple_of_length, clip, b64encode, linear_interpolate, \ - hashables_to_int, none_if_false, merge_list, is_subset + hashables_to_int, none_if_false, merge_list, is_subset, unpack_dict class TestTransforms(unittest.TestCase): + def test_unpack_dict(self): + a, b, c = unpack_dict({1: 2, 2: 3, 4: 5}, 1, 2, 4) + self.assertTrue(a == 2 and b == 3 and c == 5) + def test_is_subset(self): self.assertTrue(is_subset({}, {})) self.assertTrue(is_subset({}, {1: 2})) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 202cacf91c8f013a37d51e01c96aad64128d417e..b1c2e4ae5c5064010a65a2295f287119fe5f47ba 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -28,3 +28,5 @@ class TestParsing(unittest.TestCase): bp3 = bp.get_parser(4) self.assertEqual(bytes(bp3), b'\x00\x00\x00\xFF') self.assertRaises(NotEnoughBytes, lambda: bp2.get_parser(4)) + bp3.skip(3) + self.assertEqual(bp3.get_remaining_bytes(), b'\xFF')