Skip to content

Commit df7f4f9

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 71559ad commit df7f4f9

File tree

6 files changed

+129
-5
lines changed

6 files changed

+129
-5
lines changed

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).

tarantool/msgpack_ext_types/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_types.decimal as ext_decimal
6+
import tarantool.msgpack_ext_types.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,))
+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import tarantool.msgpack_ext_types.decimal as ext_decimal
2+
import tarantool.msgpack_ext_types.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 decoders[code]:
11+
return decoders[code](data)
612
raise NotImplementedError("Unknown msgpack type: %d" % (code,))

tarantool/msgpack_ext_types/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)

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')

test/suites/test_msgpack_ext_types.py

+82-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import sys
66
import unittest
7+
import uuid
78
import decimal
89
import msgpack
910
import warnings
@@ -13,7 +14,7 @@
1314
from tarantool.msgpack_ext_types.unpacker import ext_hook as unpacker_ext_hook
1415

1516
from .lib.tarantool_server import TarantoolServer
16-
from .lib.skip import skip_or_run_decimal_test
17+
from .lib.skip import skip_or_run_decimal_test, skip_or_run_UUID_test
1718
from tarantool.error import MsgpackError, MsgpackWarning
1819

1920
class TestSuite_MsgpackExtTypes(unittest.TestCase):
@@ -28,6 +29,7 @@ def setUpClass(self):
2829
self.adm = self.srv.admin
2930
self.adm(r"""
3031
_, decimal = pcall(require, 'decimal')
32+
_, uuid = pcall(require, 'uuid')
3133
3234
box.schema.space.create('test')
3335
box.space['test']:create_index('primary', {
@@ -418,6 +420,85 @@ def test_decimal_tarantool_encode_with_precision_loss(self):
418420

419421
self.assertSequenceEqual(self.con.eval(lua_eval), [True])
420422

423+
424+
UUID_cases = [
425+
{
426+
'python': uuid.UUID('ae28d4f6-076c-49dd-8227-7f9fae9592d0'),
427+
'msgpack': (b'\xae\x28\xd4\xf6\x07\x6c\x49\xdd\x82\x27\x7f\x9f\xae\x95\x92\xd0'),
428+
'tarantool': "uuid.fromstr('ae28d4f6-076c-49dd-8227-7f9fae9592d0')",
429+
},
430+
{
431+
'python': uuid.UUID('b3121301-9300-4038-a652-ead943fb9c39'),
432+
'msgpack': (b'\xb3\x12\x13\x01\x93\x00\x40\x38\xa6\x52\xea\xd9\x43\xfb\x9c\x39'),
433+
'tarantool': "uuid.fromstr('b3121301-9300-4038-a652-ead943fb9c39')",
434+
},
435+
{
436+
'python': uuid.UUID('dfa69f02-92e6-44a5-abb5-84b39292ff93'),
437+
'msgpack': (b'\xdf\xa6\x9f\x02\x92\xe6\x44\xa5\xab\xb5\x84\xb3\x92\x92\xff\x93'),
438+
'tarantool': "uuid.fromstr('dfa69f02-92e6-44a5-abb5-84b39292ff93')",
439+
},
440+
{
441+
'python': uuid.UUID('8b69a1ce-094a-4e21-a5dc-4cdae7cd8960'),
442+
'msgpack': (b'\x8b\x69\xa1\xce\x09\x4a\x4e\x21\xa5\xdc\x4c\xda\xe7\xcd\x89\x60'),
443+
'tarantool': "uuid.fromstr('8b69a1ce-094a-4e21-a5dc-4cdae7cd8960')",
444+
},
445+
{
446+
'python': uuid.UUID('25932334-1d42-4686-9299-ec1a7165227c'),
447+
'msgpack': (b'\x25\x93\x23\x34\x1d\x42\x46\x86\x92\x99\xec\x1a\x71\x65\x22\x7c'),
448+
'tarantool': "uuid.fromstr('25932334-1d42-4686-9299-ec1a7165227c')",
449+
},
450+
]
451+
452+
def test_UUID_msgpack_decode(self):
453+
for i in range(len(self.UUID_cases)):
454+
with self.subTest(msg=str(i)):
455+
UUID_case = self.UUID_cases[i]
456+
457+
self.assertEqual(unpacker_ext_hook(2, UUID_case['msgpack']),
458+
UUID_case['python'])
459+
460+
@skip_or_run_UUID_test
461+
def test_UUID_tarantool_decode(self):
462+
for i in range(len(self.UUID_cases)):
463+
with self.subTest(msg=str(i)):
464+
UUID_case = self.UUID_cases[i]
465+
466+
self.adm(f"box.space['test']:replace{{{i}, {UUID_case['tarantool']}}}")
467+
468+
self.assertSequenceEqual(self.con.select('test', i),
469+
[[i, UUID_case['python']]])
470+
471+
def test_UUID_msgpack_encode(self):
472+
for i in range(len(self.UUID_cases)):
473+
with self.subTest(msg=str(i)):
474+
UUID_case = self.UUID_cases[i]
475+
476+
self.assertEqual(packer_default(UUID_case['python']),
477+
msgpack.ExtType(code=2, data=UUID_case['msgpack']))
478+
479+
@skip_or_run_UUID_test
480+
def test_UUID_tarantool_encode(self):
481+
for i in range(len(self.UUID_cases)):
482+
with self.subTest(msg=str(i)):
483+
UUID_case = self.UUID_cases[i]
484+
485+
self.con.insert('test', [i, UUID_case['python']])
486+
487+
lua_eval = f"""
488+
local tuple = box.space['test']:get({i})
489+
assert(tuple ~= nil)
490+
491+
local id = {UUID_case['tarantool']}
492+
if tuple[2] == id then
493+
return true
494+
else
495+
return nil, ('%s is not equal to expected %s'):format(
496+
tostring(tuple[2]), tostring(id))
497+
end
498+
"""
499+
500+
self.assertSequenceEqual(self.con.eval(lua_eval), [True])
501+
421502
@classmethod
422503
def tearDownClass(self):
423504
self.con.close()

0 commit comments

Comments
 (0)