Skip to content

Commit db9e5b8

Browse files
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
1 parent 1f84255 commit db9e5b8

File tree

4 files changed

+112
-172
lines changed

4 files changed

+112
-172
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
191191
- Use git version to set package version (#238).
192192
- Extract tarantool.Datetime encode and decode to external
193193
functions (PR #252).
194+
- Extract tarantool.Interval encode and decode to external
195+
functions (PR #252).
194196

195197
### Fixed
196198
- Package build (#238).

tarantool/msgpack_ext/interval.py

+96-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,50 @@
11
"""
22
Tarantool `datetime.interval`_ extension type support module.
33
4-
Refer to :mod:`~tarantool.msgpack_ext.types.interval`.
4+
The interval MessagePack representation looks like this:
5+
6+
.. code-block:: text
7+
8+
+--------+-------------------------+-------------+----------------+
9+
| MP_EXT | Size of packed interval | MP_INTERVAL | PackedInterval |
10+
+--------+-------------------------+-------------+----------------+
11+
12+
Packed interval consists of:
13+
14+
* Packed number of non-zero fields.
15+
* Packed non-null fields.
16+
17+
Each packed field has the following structure:
18+
19+
.. code-block:: text
20+
21+
+----------+=====================+
22+
| field ID | field value |
23+
+----------+=====================+
24+
25+
The number of defined (non-null) fields can be zero. In this case,
26+
the packed interval will be encoded as integer 0.
27+
28+
List of the field IDs:
29+
30+
* 0 – year
31+
* 1 – month
32+
* 2 – week
33+
* 3 – day
34+
* 4 – hour
35+
* 5 – minute
36+
* 6 – second
37+
* 7 – nanosecond
38+
* 8 – adjust
539
640
.. _datetime.interval: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-interval-type
741
"""
842

9-
from tarantool.msgpack_ext.types.interval import Interval
43+
import msgpack
44+
45+
from tarantool.error import MsgpackError
46+
47+
from tarantool.msgpack_ext.types.interval import Interval, Adjust, id_map
1048

1149
EXT_ID = 6
1250
"""
@@ -22,11 +60,25 @@ def encode(obj):
2260
2361
:return: Encoded interval.
2462
:rtype: :obj:`bytes`
25-
26-
:raise: :exc:`tarantool.Interval.msgpack_encode` exceptions
2763
"""
2864

29-
return obj.msgpack_encode()
65+
buf = bytes()
66+
67+
count = 0
68+
for field_id in id_map.keys():
69+
field_name = id_map[field_id]
70+
value = getattr(obj, field_name)
71+
72+
if field_name == 'adjust':
73+
value = value.value
74+
75+
if value != 0:
76+
buf = buf + msgpack.packb(field_id) + msgpack.packb(value)
77+
count = count + 1
78+
79+
buf = msgpack.packb(count) + buf
80+
81+
return buf
3082

3183
def decode(data):
3284
"""
@@ -38,7 +90,44 @@ def decode(data):
3890
:return: Decoded interval.
3991
:rtype: :class:`tarantool.Interval`
4092
41-
:raise: :exc:`tarantool.Interval` exceptions
93+
:raise: :exc:`MsgpackError`
4294
"""
4395

44-
return Interval(data)
96+
# If MessagePack data does not contain a field value, it is zero.
97+
# If built not from MessagePack data, set argument values later.
98+
kwargs = {
99+
'year': 0,
100+
'month': 0,
101+
'week': 0,
102+
'day': 0,
103+
'hour': 0,
104+
'minute': 0,
105+
'sec': 0,
106+
'nsec': 0,
107+
'adjust': Adjust(0),
108+
}
109+
110+
if len(data) != 0:
111+
# To create an unpacker is the only way to parse
112+
# a sequence of values in Python msgpack module.
113+
unpacker = msgpack.Unpacker()
114+
unpacker.feed(data)
115+
field_count = unpacker.unpack()
116+
for _ in range(field_count):
117+
field_id = unpacker.unpack()
118+
value = unpacker.unpack()
119+
120+
if field_id not in id_map:
121+
raise MsgpackError(f'Unknown interval field id {field_id}')
122+
123+
field_name = id_map[field_id]
124+
125+
if field_name == 'adjust':
126+
try:
127+
value = Adjust(value)
128+
except ValueError as e:
129+
raise MsgpackError(e)
130+
131+
kwargs[id_map[field_id]] = value
132+
133+
return Interval(**kwargs)

tarantool/msgpack_ext/types/interval.py

+12-124
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,9 @@
11
"""
2-
Tarantool `datetime.interval`_ extension type support module.
3-
4-
The interval MessagePack representation looks like this:
5-
6-
.. code-block:: text
7-
8-
+--------+-------------------------+-------------+----------------+
9-
| MP_EXT | Size of packed interval | MP_INTERVAL | PackedInterval |
10-
+--------+-------------------------+-------------+----------------+
11-
12-
Packed interval consists of:
13-
14-
* Packed number of non-zero fields.
15-
* Packed non-null fields.
16-
17-
Each packed field has the following structure:
18-
19-
.. code-block:: text
20-
21-
+----------+=====================+
22-
| field ID | field value |
23-
+----------+=====================+
24-
25-
The number of defined (non-null) fields can be zero. In this case,
26-
the packed interval will be encoded as integer 0.
27-
28-
List of the field IDs:
29-
30-
* 0 – year
31-
* 1 – month
32-
* 2 – week
33-
* 3 – day
34-
* 4 – hour
35-
* 5 – minute
36-
* 6 – second
37-
* 7 – nanosecond
38-
* 8 – adjust
2+
Tarantool `datetime.interval`_ extension type implementation module.
393
"""
404

41-
import msgpack
425
from enum import Enum
436

44-
from tarantool.error import MsgpackError
45-
467
id_map = {
478
0: 'year',
489
1: 'month',
@@ -97,14 +58,10 @@ class Interval():
9758
.. _datetime.interval: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-interval-type
9859
"""
9960

100-
def __init__(self, data=None, *, year=0, month=0, week=0,
61+
def __init__(self, *, year=0, month=0, week=0,
10162
day=0, hour=0, minute=0, sec=0,
10263
nsec=0, adjust=Adjust.NONE):
10364
"""
104-
:param data: MessagePack binary data to decode. If provided,
105-
all other parameters are ignored.
106-
:type data: :obj:`bytes`, optional
107-
10865
:param year: Interval year value.
10966
:type year: :obj:`int`, optional
11067
@@ -132,61 +89,17 @@ def __init__(self, data=None, *, year=0, month=0, week=0,
13289
:param adjust: Interval adjustment rule. Refer to
13390
:meth:`~tarantool.Datetime.__add__`.
13491
:type adjust: :class:`~tarantool.IntervalAdjust`, optional
135-
136-
:raise: :exc:`ValueError`
13792
"""
138-
139-
# If MessagePack data does not contain a field value, it is zero.
140-
# If built not from MessagePack data, set argument values later.
141-
self.year = 0
142-
self.month = 0
143-
self.week = 0
144-
self.day = 0
145-
self.hour = 0
146-
self.minute = 0
147-
self.sec = 0
148-
self.nsec = 0
149-
self.adjust = Adjust(0)
150-
151-
if data is not None:
152-
if not isinstance(data, bytes):
153-
raise ValueError('data argument (first positional argument) ' +
154-
'expected to be a "bytes" instance')
155-
156-
if len(data) == 0:
157-
return
158-
159-
# To create an unpacker is the only way to parse
160-
# a sequence of values in Python msgpack module.
161-
unpacker = msgpack.Unpacker()
162-
unpacker.feed(data)
163-
field_count = unpacker.unpack()
164-
for _ in range(field_count):
165-
field_id = unpacker.unpack()
166-
value = unpacker.unpack()
167-
168-
if field_id not in id_map:
169-
raise MsgpackError(f'Unknown interval field id {field_id}')
170-
171-
field_name = id_map[field_id]
172-
173-
if field_name == 'adjust':
174-
try:
175-
value = Adjust(value)
176-
except ValueError as e:
177-
raise MsgpackError(e)
178-
179-
setattr(self, id_map[field_id], value)
180-
else:
181-
self.year = year
182-
self.month = month
183-
self.week = week
184-
self.day = day
185-
self.hour = hour
186-
self.minute = minute
187-
self.sec = sec
188-
self.nsec = nsec
189-
self.adjust = adjust
93+
94+
self.year = year
95+
self.month = month
96+
self.week = week
97+
self.day = day
98+
self.hour = hour
99+
self.minute = minute
100+
self.sec = sec
101+
self.nsec = nsec
102+
self.adjust = adjust
190103

191104
def __add__(self, other):
192105
"""
@@ -319,28 +232,3 @@ def __repr__(self):
319232
f'nsec={self.nsec}, adjust={self.adjust})'
320233

321234
__str__ = __repr__
322-
323-
def msgpack_encode(self):
324-
"""
325-
Encode an interval object.
326-
327-
:rtype: :obj:`bytes`
328-
"""
329-
330-
buf = bytes()
331-
332-
count = 0
333-
for field_id in id_map.keys():
334-
field_name = id_map[field_id]
335-
value = getattr(self, field_name)
336-
337-
if field_name == 'adjust':
338-
value = value.value
339-
340-
if value != 0:
341-
buf = buf + msgpack.packb(field_id) + msgpack.packb(value)
342-
count = count + 1
343-
344-
buf = msgpack.packb(count) + buf
345-
346-
return buf

test/suites/test_interval.py

+2-41
Original file line numberDiff line numberDiff line change
@@ -57,50 +57,11 @@ def setUp(self):
5757

5858
self.adm("box.space['test']:truncate()")
5959

60-
def test_Interval_bytes_init(self):
61-
dt = tarantool.Interval(b'\x02\x00\x01\x08\x01')
62-
63-
self.assertEqual(dt.year, 1)
64-
self.assertEqual(dt.month, 0)
65-
self.assertEqual(dt.day, 0)
66-
self.assertEqual(dt.hour, 0)
67-
self.assertEqual(dt.minute, 0)
68-
self.assertEqual(dt.sec, 0)
69-
self.assertEqual(dt.nsec, 0)
70-
self.assertEqual(dt.adjust, tarantool.IntervalAdjust.NONE)
71-
72-
def test_Interval_non_bytes_positional_init(self):
60+
def test_Interval_positional_init(self):
7361
self.assertRaisesRegex(
74-
ValueError, re.escape('data argument (first positional argument) ' +
75-
'expected to be a "bytes" instance'),
62+
TypeError, re.escape('__init__() takes 1 positional argument but 2 were given'),
7663
lambda: tarantool.Interval(1))
7764

78-
def test_Interval_bytes_init_ignore_other_fields(self):
79-
dt = tarantool.Interval(b'\x02\x00\x01\x08\x01',
80-
year=2, month=2, day=3, hour=1, minute=2,
81-
sec=3000, nsec=10000000,
82-
adjust=tarantool.IntervalAdjust.LAST)
83-
84-
self.assertEqual(dt.year, 1)
85-
self.assertEqual(dt.month, 0)
86-
self.assertEqual(dt.day, 0)
87-
self.assertEqual(dt.hour, 0)
88-
self.assertEqual(dt.minute, 0)
89-
self.assertEqual(dt.sec, 0)
90-
self.assertEqual(dt.nsec, 0)
91-
self.assertEqual(dt.adjust, tarantool.IntervalAdjust.NONE)
92-
93-
def test_Interval_bytes_init_unknown_field(self):
94-
self.assertRaisesRegex(
95-
MsgpackError, 'Unknown interval field id 9',
96-
lambda: tarantool.Interval(b'\x01\x09\xce\x00\x98\x96\x80'))
97-
98-
def test_Interval_bytes_init_unknown_adjust(self):
99-
self.assertRaisesRegex(
100-
MsgpackError, '3 is not a valid Adjust',
101-
lambda: tarantool.Interval(b'\x02\x07\xce\x00\x98\x96\x80\x08\x03'))
102-
103-
10465
cases = {
10566
'year': {
10667
'python': tarantool.Interval(year=1),

0 commit comments

Comments
 (0)