Skip to content

Commit 989af6c

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 989af6c

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/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,))

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

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)

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.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.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_MsgpackExt(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', {
@@ -424,6 +426,85 @@ def test_decimal_tarantool_encode_with_precision_loss(self):
424426

425427
self.assertSequenceEqual(self.con.eval(lua_eval), [True])
426428

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

0 commit comments

Comments
 (0)