From db9e5b864e4604b477259563487b2e9e85a3f069 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Mon, 24 Oct 2022 16:27:05 +0300 Subject: [PATCH] api: extract interval encode/decode from class Extract tarantool.Interval encode and decode to external functions. This is a breaking change, but since there is no tagged release with Interval yet and API was more internal rather than public, it shouldn't be an issue. Follows #229 --- CHANGELOG.md | 2 + tarantool/msgpack_ext/interval.py | 103 ++++++++++++++++-- tarantool/msgpack_ext/types/interval.py | 136 +++--------------------- test/suites/test_interval.py | 43 +------- 4 files changed, 112 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7b56d8..eed24c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -191,6 +191,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use git version to set package version (#238). - Extract tarantool.Datetime encode and decode to external functions (PR #252). +- Extract tarantool.Interval encode and decode to external + functions (PR #252). ### Fixed - Package build (#238). diff --git a/tarantool/msgpack_ext/interval.py b/tarantool/msgpack_ext/interval.py index 20a791ef..725edc9d 100644 --- a/tarantool/msgpack_ext/interval.py +++ b/tarantool/msgpack_ext/interval.py @@ -1,12 +1,50 @@ """ Tarantool `datetime.interval`_ extension type support module. -Refer to :mod:`~tarantool.msgpack_ext.types.interval`. +The interval MessagePack representation looks like this: + +.. code-block:: text + + +--------+-------------------------+-------------+----------------+ + | MP_EXT | Size of packed interval | MP_INTERVAL | PackedInterval | + +--------+-------------------------+-------------+----------------+ + +Packed interval consists of: + +* Packed number of non-zero fields. +* Packed non-null fields. + +Each packed field has the following structure: + +.. code-block:: text + + +----------+=====================+ + | field ID | field value | + +----------+=====================+ + +The number of defined (non-null) fields can be zero. In this case, +the packed interval will be encoded as integer 0. + +List of the field IDs: + +* 0 – year +* 1 – month +* 2 – week +* 3 – day +* 4 – hour +* 5 – minute +* 6 – second +* 7 – nanosecond +* 8 – adjust .. _datetime.interval: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-interval-type """ -from tarantool.msgpack_ext.types.interval import Interval +import msgpack + +from tarantool.error import MsgpackError + +from tarantool.msgpack_ext.types.interval import Interval, Adjust, id_map EXT_ID = 6 """ @@ -22,11 +60,25 @@ def encode(obj): :return: Encoded interval. :rtype: :obj:`bytes` - - :raise: :exc:`tarantool.Interval.msgpack_encode` exceptions """ - return obj.msgpack_encode() + buf = bytes() + + count = 0 + for field_id in id_map.keys(): + field_name = id_map[field_id] + value = getattr(obj, field_name) + + if field_name == 'adjust': + value = value.value + + if value != 0: + buf = buf + msgpack.packb(field_id) + msgpack.packb(value) + count = count + 1 + + buf = msgpack.packb(count) + buf + + return buf def decode(data): """ @@ -38,7 +90,44 @@ def decode(data): :return: Decoded interval. :rtype: :class:`tarantool.Interval` - :raise: :exc:`tarantool.Interval` exceptions + :raise: :exc:`MsgpackError` """ - return Interval(data) + # If MessagePack data does not contain a field value, it is zero. + # If built not from MessagePack data, set argument values later. + kwargs = { + 'year': 0, + 'month': 0, + 'week': 0, + 'day': 0, + 'hour': 0, + 'minute': 0, + 'sec': 0, + 'nsec': 0, + 'adjust': Adjust(0), + } + + if len(data) != 0: + # To create an unpacker is the only way to parse + # a sequence of values in Python msgpack module. + unpacker = msgpack.Unpacker() + unpacker.feed(data) + field_count = unpacker.unpack() + for _ in range(field_count): + field_id = unpacker.unpack() + value = unpacker.unpack() + + if field_id not in id_map: + raise MsgpackError(f'Unknown interval field id {field_id}') + + field_name = id_map[field_id] + + if field_name == 'adjust': + try: + value = Adjust(value) + except ValueError as e: + raise MsgpackError(e) + + kwargs[id_map[field_id]] = value + + return Interval(**kwargs) diff --git a/tarantool/msgpack_ext/types/interval.py b/tarantool/msgpack_ext/types/interval.py index 62d98145..e90ff0a0 100644 --- a/tarantool/msgpack_ext/types/interval.py +++ b/tarantool/msgpack_ext/types/interval.py @@ -1,48 +1,9 @@ """ -Tarantool `datetime.interval`_ extension type support module. - -The interval MessagePack representation looks like this: - -.. code-block:: text - - +--------+-------------------------+-------------+----------------+ - | MP_EXT | Size of packed interval | MP_INTERVAL | PackedInterval | - +--------+-------------------------+-------------+----------------+ - -Packed interval consists of: - -* Packed number of non-zero fields. -* Packed non-null fields. - -Each packed field has the following structure: - -.. code-block:: text - - +----------+=====================+ - | field ID | field value | - +----------+=====================+ - -The number of defined (non-null) fields can be zero. In this case, -the packed interval will be encoded as integer 0. - -List of the field IDs: - -* 0 – year -* 1 – month -* 2 – week -* 3 – day -* 4 – hour -* 5 – minute -* 6 – second -* 7 – nanosecond -* 8 – adjust +Tarantool `datetime.interval`_ extension type implementation module. """ -import msgpack from enum import Enum -from tarantool.error import MsgpackError - id_map = { 0: 'year', 1: 'month', @@ -97,14 +58,10 @@ class Interval(): .. _datetime.interval: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-interval-type """ - def __init__(self, data=None, *, year=0, month=0, week=0, + def __init__(self, *, year=0, month=0, week=0, day=0, hour=0, minute=0, sec=0, nsec=0, adjust=Adjust.NONE): """ - :param data: MessagePack binary data to decode. If provided, - all other parameters are ignored. - :type data: :obj:`bytes`, optional - :param year: Interval year value. :type year: :obj:`int`, optional @@ -132,61 +89,17 @@ def __init__(self, data=None, *, year=0, month=0, week=0, :param adjust: Interval adjustment rule. Refer to :meth:`~tarantool.Datetime.__add__`. :type adjust: :class:`~tarantool.IntervalAdjust`, optional - - :raise: :exc:`ValueError` """ - - # If MessagePack data does not contain a field value, it is zero. - # If built not from MessagePack data, set argument values later. - self.year = 0 - self.month = 0 - self.week = 0 - self.day = 0 - self.hour = 0 - self.minute = 0 - self.sec = 0 - self.nsec = 0 - self.adjust = Adjust(0) - - if data is not None: - if not isinstance(data, bytes): - raise ValueError('data argument (first positional argument) ' + - 'expected to be a "bytes" instance') - - if len(data) == 0: - return - - # To create an unpacker is the only way to parse - # a sequence of values in Python msgpack module. - unpacker = msgpack.Unpacker() - unpacker.feed(data) - field_count = unpacker.unpack() - for _ in range(field_count): - field_id = unpacker.unpack() - value = unpacker.unpack() - - if field_id not in id_map: - raise MsgpackError(f'Unknown interval field id {field_id}') - - field_name = id_map[field_id] - - if field_name == 'adjust': - try: - value = Adjust(value) - except ValueError as e: - raise MsgpackError(e) - - setattr(self, id_map[field_id], value) - else: - self.year = year - self.month = month - self.week = week - self.day = day - self.hour = hour - self.minute = minute - self.sec = sec - self.nsec = nsec - self.adjust = adjust + + self.year = year + self.month = month + self.week = week + self.day = day + self.hour = hour + self.minute = minute + self.sec = sec + self.nsec = nsec + self.adjust = adjust def __add__(self, other): """ @@ -319,28 +232,3 @@ def __repr__(self): f'nsec={self.nsec}, adjust={self.adjust})' __str__ = __repr__ - - def msgpack_encode(self): - """ - Encode an interval object. - - :rtype: :obj:`bytes` - """ - - buf = bytes() - - count = 0 - for field_id in id_map.keys(): - field_name = id_map[field_id] - value = getattr(self, field_name) - - if field_name == 'adjust': - value = value.value - - if value != 0: - buf = buf + msgpack.packb(field_id) + msgpack.packb(value) - count = count + 1 - - buf = msgpack.packb(count) + buf - - return buf diff --git a/test/suites/test_interval.py b/test/suites/test_interval.py index 63a3c0ca..2de70a11 100644 --- a/test/suites/test_interval.py +++ b/test/suites/test_interval.py @@ -57,50 +57,11 @@ def setUp(self): self.adm("box.space['test']:truncate()") - def test_Interval_bytes_init(self): - dt = tarantool.Interval(b'\x02\x00\x01\x08\x01') - - self.assertEqual(dt.year, 1) - self.assertEqual(dt.month, 0) - self.assertEqual(dt.day, 0) - self.assertEqual(dt.hour, 0) - self.assertEqual(dt.minute, 0) - self.assertEqual(dt.sec, 0) - self.assertEqual(dt.nsec, 0) - self.assertEqual(dt.adjust, tarantool.IntervalAdjust.NONE) - - def test_Interval_non_bytes_positional_init(self): + def test_Interval_positional_init(self): self.assertRaisesRegex( - ValueError, re.escape('data argument (first positional argument) ' + - 'expected to be a "bytes" instance'), + TypeError, re.escape('__init__() takes 1 positional argument but 2 were given'), lambda: tarantool.Interval(1)) - def test_Interval_bytes_init_ignore_other_fields(self): - dt = tarantool.Interval(b'\x02\x00\x01\x08\x01', - year=2, month=2, day=3, hour=1, minute=2, - sec=3000, nsec=10000000, - adjust=tarantool.IntervalAdjust.LAST) - - self.assertEqual(dt.year, 1) - self.assertEqual(dt.month, 0) - self.assertEqual(dt.day, 0) - self.assertEqual(dt.hour, 0) - self.assertEqual(dt.minute, 0) - self.assertEqual(dt.sec, 0) - self.assertEqual(dt.nsec, 0) - self.assertEqual(dt.adjust, tarantool.IntervalAdjust.NONE) - - def test_Interval_bytes_init_unknown_field(self): - self.assertRaisesRegex( - MsgpackError, 'Unknown interval field id 9', - lambda: tarantool.Interval(b'\x01\x09\xce\x00\x98\x96\x80')) - - def test_Interval_bytes_init_unknown_adjust(self): - self.assertRaisesRegex( - MsgpackError, '3 is not a valid Adjust', - lambda: tarantool.Interval(b'\x02\x07\xce\x00\x98\x96\x80\x08\x03')) - - cases = { 'year': { 'python': tarantool.Interval(year=1),