Skip to content

Commit 7181eb0

Browse files
msgpack: support UUID extended type
Tarantool supports UUID type since version 2.4.1 [1]. This patch introduced the support of Tarantool UUID type in msgpack decoders and encoders. The Tarantool UUID type is mapped to the native Python uuid.UUID type. 1. tarantool/tarantool#4268 Closed #202
1 parent b0ed8f0 commit 7181eb0

File tree

7 files changed

+227
-47
lines changed

7 files changed

+227
-47
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010
- Decimal type support (#203).
11+
- UUID type support (#202).
1112

1213
### Changed
1314
- Bump msgpack requirement to 1.0.4 (PR #223).

Diff for: tarantool/msgpack_ext/packer.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
from decimal import Decimal
2+
from uuid import UUID
23
from msgpack import ExtType
34

45
import tarantool.msgpack_ext.decimal as ext_decimal
6+
import tarantool.msgpack_ext.uuid as ext_uuid
7+
8+
encoders = [
9+
{'type': Decimal, 'ext': ext_decimal},
10+
{'type': UUID, 'ext': ext_uuid },
11+
]
512

613
def default(obj):
7-
if isinstance(obj, Decimal):
8-
return ExtType(ext_decimal.EXT_ID, ext_decimal.encode(obj))
14+
for encoder in encoders:
15+
if isinstance(obj, encoder['type']):
16+
return ExtType(encoder['ext'].EXT_ID, encoder['ext'].encode(obj))
917
raise TypeError("Unknown type: %r" % (obj,))

Diff for: tarantool/msgpack_ext/unpacker.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import tarantool.msgpack_ext.decimal as ext_decimal
2+
import tarantool.msgpack_ext.uuid as ext_uuid
3+
4+
decoders = {
5+
ext_decimal.EXT_ID: ext_decimal.decode,
6+
ext_uuid.EXT_ID : ext_uuid.decode ,
7+
}
28

39
def ext_hook(code, data):
4-
if code == ext_decimal.EXT_ID:
5-
return ext_decimal.decode(data)
10+
if code in decoders:
11+
return decoders[code](data)
612
raise NotImplementedError("Unknown msgpack type: %d" % (code,))

Diff for: tarantool/msgpack_ext/uuid.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from uuid import UUID
2+
3+
# https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-uuid-type
4+
#
5+
# The UUID MessagePack representation looks like this:
6+
# +--------+------------+-----------------+
7+
# | MP_EXT | MP_UUID | UuidValue |
8+
# | = d8 | = 2 | = 16-byte value |
9+
# +--------+------------+-----------------+
10+
11+
EXT_ID = 2
12+
13+
def encode(obj):
14+
return obj.bytes
15+
16+
def decode(data):
17+
return UUID(bytes=data)

Diff for: test/suites/lib/skip.py

+11
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,14 @@ def skip_or_run_decimal_test(func):
143143

144144
return skip_or_run_test_pcall_require(func, 'decimal',
145145
'does not support decimal type')
146+
147+
def skip_or_run_UUID_test(func):
148+
"""Decorator to skip or run UUID-related tests depending on
149+
the tarantool version.
150+
151+
Tarantool supports UUID type only since 2.4.1 version.
152+
See https://github.com/tarantool/tarantool/issues/4268
153+
"""
154+
155+
return skip_or_run_test_tarantool(func, '2.4.1',
156+
'does not support UUID type')

Diff for: test/suites/test_msgpack_ext.py renamed to test/suites/test_decimal.py

+44-43
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
from .lib.skip import skip_or_run_decimal_test
1717
from tarantool.error import MsgpackError, MsgpackWarning
1818

19-
class TestSuite_MsgpackExt(unittest.TestCase):
19+
class TestSuite_Decimal(unittest.TestCase):
2020
@classmethod
2121
def setUpClass(self):
22-
print(' MSGPACK EXT TYPES '.center(70, '='), file=sys.stderr)
22+
print(' DECIMAL EXT TYPE '.center(70, '='), file=sys.stderr)
2323
print('-' * 70, file=sys.stderr)
2424
self.srv = TarantoolServer()
2525
self.srv.script = 'test/suites/box.lua'
@@ -50,7 +50,7 @@ def setUp(self):
5050
self.adm("box.space['test']:truncate()")
5151

5252

53-
valid_decimal_cases = {
53+
valid_cases = {
5454
'simple_decimal_1': {
5555
'python': decimal.Decimal('0.7'),
5656
'msgpack': (b'\x01\x7c'),
@@ -219,47 +219,47 @@ def setUp(self):
219219
},
220220
}
221221

222-
def test_decimal_msgpack_decode(self):
223-
for name in self.valid_decimal_cases.keys():
222+
def test_msgpack_decode(self):
223+
for name in self.valid_cases.keys():
224224
with self.subTest(msg=name):
225-
decimal_case = self.valid_decimal_cases[name]
225+
case = self.valid_cases[name]
226226

227-
self.assertEqual(unpacker_ext_hook(1, decimal_case['msgpack']),
228-
decimal_case['python'])
227+
self.assertEqual(unpacker_ext_hook(1, case['msgpack']),
228+
case['python'])
229229

230230
@skip_or_run_decimal_test
231-
def test_decimal_tarantool_decode(self):
232-
for name in self.valid_decimal_cases.keys():
231+
def test_tarantool_decode(self):
232+
for name in self.valid_cases.keys():
233233
with self.subTest(msg=name):
234-
decimal_case = self.valid_decimal_cases[name]
234+
case = self.valid_cases[name]
235235

236-
self.adm(f"box.space['test']:replace{{'{name}', {decimal_case['tarantool']}}}")
236+
self.adm(f"box.space['test']:replace{{'{name}', {case['tarantool']}}}")
237237

238238
self.assertSequenceEqual(
239239
self.con.select('test', name),
240-
[[name, decimal_case['python']]])
240+
[[name, case['python']]])
241241

242-
def test_decimal_msgpack_encode(self):
243-
for name in self.valid_decimal_cases.keys():
242+
def test_msgpack_encode(self):
243+
for name in self.valid_cases.keys():
244244
with self.subTest(msg=name):
245-
decimal_case = self.valid_decimal_cases[name]
245+
case = self.valid_cases[name]
246246

247-
self.assertEqual(packer_default(decimal_case['python']),
248-
msgpack.ExtType(code=1, data=decimal_case['msgpack']))
247+
self.assertEqual(packer_default(case['python']),
248+
msgpack.ExtType(code=1, data=case['msgpack']))
249249

250250
@skip_or_run_decimal_test
251-
def test_decimal_tarantool_encode(self):
252-
for name in self.valid_decimal_cases.keys():
251+
def test_tarantool_encode(self):
252+
for name in self.valid_cases.keys():
253253
with self.subTest(msg=name):
254-
decimal_case = self.valid_decimal_cases[name]
254+
case = self.valid_cases[name]
255255

256-
self.con.insert('test', [name, decimal_case['python']])
256+
self.con.insert('test', [name, case['python']])
257257

258258
lua_eval = f"""
259259
local tuple = box.space['test']:get('{name}')
260260
assert(tuple ~= nil)
261261
262-
local dec = {decimal_case['tarantool']}
262+
local dec = {case['tarantool']}
263263
if tuple[2] == dec then
264264
return true
265265
else
@@ -271,7 +271,7 @@ def test_decimal_tarantool_encode(self):
271271
self.assertSequenceEqual(self.con.eval(lua_eval), [True])
272272

273273

274-
error_decimal_cases = {
274+
error_cases = {
275275
'decimal_limit_break_head_1': {
276276
'python': decimal.Decimal('999999999999999999999999999999999999999'),
277277
},
@@ -298,31 +298,31 @@ def test_decimal_tarantool_encode(self):
298298
},
299299
}
300300

301-
def test_decimal_msgpack_encode_error(self):
302-
for name in self.error_decimal_cases.keys():
301+
def test_msgpack_encode_error(self):
302+
for name in self.error_cases.keys():
303303
with self.subTest(msg=name):
304-
decimal_case = self.error_decimal_cases[name]
304+
case = self.error_cases[name]
305305

306306
msg = 'Decimal cannot be encoded: Tarantool decimal ' + \
307307
'supports a maximum of 38 digits.'
308308
self.assertRaisesRegex(
309309
MsgpackError, msg,
310-
lambda: packer_default(decimal_case['python']))
310+
lambda: packer_default(case['python']))
311311

312312
@skip_or_run_decimal_test
313-
def test_decimal_tarantool_encode_error(self):
314-
for name in self.error_decimal_cases.keys():
313+
def test_tarantool_encode_error(self):
314+
for name in self.error_cases.keys():
315315
with self.subTest(msg=name):
316-
decimal_case = self.error_decimal_cases[name]
316+
case = self.error_cases[name]
317317

318318
msg = 'Decimal cannot be encoded: Tarantool decimal ' + \
319319
'supports a maximum of 38 digits.'
320320
self.assertRaisesRegex(
321321
MsgpackError, msg,
322-
lambda: self.con.insert('test', [name, decimal_case['python']]))
322+
lambda: self.con.insert('test', [name, case['python']]))
323323

324324

325-
precision_loss_decimal_cases = {
325+
precision_loss_cases = {
326326
'decimal_limit_break_tail_1': {
327327
'python': decimal.Decimal('1.00000000000000000000000000000000000001'),
328328
'msgpack': (b'\x00\x1c'),
@@ -379,41 +379,41 @@ def test_decimal_tarantool_encode_error(self):
379379
},
380380
}
381381

382-
def test_decimal_msgpack_encode_with_precision_loss(self):
383-
for name in self.precision_loss_decimal_cases.keys():
382+
def test_msgpack_encode_with_precision_loss(self):
383+
for name in self.precision_loss_cases.keys():
384384
with self.subTest(msg=name):
385-
decimal_case = self.precision_loss_decimal_cases[name]
385+
case = self.precision_loss_cases[name]
386386

387387
msg = 'Decimal encoded with loss of precision: ' + \
388388
'Tarantool decimal supports a maximum of 38 digits.'
389389

390390
self.assertWarnsRegex(
391391
MsgpackWarning, msg,
392392
lambda: self.assertEqual(
393-
packer_default(decimal_case['python']),
394-
msgpack.ExtType(code=1, data=decimal_case['msgpack'])
393+
packer_default(case['python']),
394+
msgpack.ExtType(code=1, data=case['msgpack'])
395395
)
396396
)
397397

398398

399399
@skip_or_run_decimal_test
400-
def test_decimal_tarantool_encode_with_precision_loss(self):
401-
for name in self.precision_loss_decimal_cases.keys():
400+
def test_tarantool_encode_with_precision_loss(self):
401+
for name in self.precision_loss_cases.keys():
402402
with self.subTest(msg=name):
403-
decimal_case = self.precision_loss_decimal_cases[name]
403+
case = self.precision_loss_cases[name]
404404

405405
msg = 'Decimal encoded with loss of precision: ' + \
406406
'Tarantool decimal supports a maximum of 38 digits.'
407407

408408
self.assertWarnsRegex(
409409
MsgpackWarning, msg,
410-
lambda: self.con.insert('test', [name, decimal_case['python']]))
410+
lambda: self.con.insert('test', [name, case['python']]))
411411

412412
lua_eval = f"""
413413
local tuple = box.space['test']:get('{name}')
414414
assert(tuple ~= nil)
415415
416-
local dec = {decimal_case['tarantool']}
416+
local dec = {case['tarantool']}
417417
if tuple[2] == dec then
418418
return true
419419
else
@@ -424,6 +424,7 @@ def test_decimal_tarantool_encode_with_precision_loss(self):
424424

425425
self.assertSequenceEqual(self.con.eval(lua_eval), [True])
426426

427+
427428
@classmethod
428429
def tearDownClass(self):
429430
self.con.close()

0 commit comments

Comments
 (0)