Skip to content

Commit 99fab82

Browse files
committed
WIP: Encode bytes as mp_bin on Python 3
This allows to write mp_bin, which is required for 'varbinary' field type: just write a value of 'bytes' type and it will be encoded as mp_bin instead of mp_str. mp_bin is already decoded into bytes, which is consistent with the new encode behaviour. XXX: Change the behaviour only under an option? If we'll go this way, we can also split mp_str / mp_bin across unicode / str for Python 2, but don't sure it is convenient. Fixes #105
1 parent bd37703 commit 99fab82

File tree

2 files changed

+91
-37
lines changed

2 files changed

+91
-37
lines changed

tarantool/request.py

+57-37
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Request types definitions
55
'''
66

7+
import sys
78
import msgpack
89
import hashlib
910

@@ -65,6 +66,25 @@ def __init__(self, conn):
6566
self._sync = None
6667
self._body = ''
6768

69+
# Python 2:
70+
#
71+
# - encode: str, unicode -> mp_str
72+
# - decode: mp_str, mp_bin -> str
73+
#
74+
# Python 3:
75+
#
76+
# - encode: str -> mp_str
77+
# - encode: bytes -> mp_bin
78+
# - decode: mp_str -> str
79+
# - decode: mp_bin -> bytes
80+
self.msgpack_dumps_kwargs = dict()
81+
if sys.version_info.major == 3:
82+
# XXX: Should we check msgpack version before use this option?
83+
self.msgpack_dumps_kwargs.update(use_bin_type=True)
84+
85+
def dumps(self, src):
86+
return msgpack.dumps(src, **self.msgpack_dumps_kwargs)
87+
6888
def __bytes__(self):
6989
return self.header(len(self._body)) + self._body
7090

@@ -82,11 +102,11 @@ def sync(self):
82102

83103
def header(self, length):
84104
self._sync = self.conn.generate_sync()
85-
header = msgpack.dumps({IPROTO_CODE: self.request_type,
86-
IPROTO_SYNC: self._sync,
87-
IPROTO_SCHEMA_ID: self.conn.schema_version})
105+
header = self.dumps({IPROTO_CODE: self.request_type,
106+
IPROTO_SYNC: self._sync,
107+
IPROTO_SCHEMA_ID: self.conn.schema_version})
88108

89-
return msgpack.dumps(length + len(header)) + header
109+
return self.dumps(length + len(header)) + header
90110

91111

92112
class RequestInsert(Request):
@@ -102,8 +122,8 @@ def __init__(self, conn, space_no, values):
102122
super(RequestInsert, self).__init__(conn)
103123
assert isinstance(values, (tuple, list))
104124

105-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
106-
IPROTO_TUPLE: values})
125+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
126+
IPROTO_TUPLE: values})
107127

108128
self._body = request_body
109129

@@ -131,19 +151,19 @@ def sha1(values):
131151
hash2 = sha1((hash1,))
132152
scramble = sha1((salt, hash2))
133153
scramble = strxor(hash1, scramble)
134-
request_body = msgpack.dumps({IPROTO_USER_NAME: user,
135-
IPROTO_TUPLE: ("chap-sha1", scramble)})
154+
request_body = self.dumps({IPROTO_USER_NAME: user,
155+
IPROTO_TUPLE: ("chap-sha1", scramble)})
136156
self._body = request_body
137157

138158
def header(self, length):
139159
self._sync = self.conn.generate_sync()
140160
# Set IPROTO_SCHEMA_ID: 0 to avoid SchemaReloadException
141161
# It is ok to use 0 in auth every time.
142-
header = msgpack.dumps({IPROTO_CODE: self.request_type,
143-
IPROTO_SYNC: self._sync,
144-
IPROTO_SCHEMA_ID: 0})
162+
header = self.dumps({IPROTO_CODE: self.request_type,
163+
IPROTO_SYNC: self._sync,
164+
IPROTO_SCHEMA_ID: 0})
145165

146-
return msgpack.dumps(length + len(header)) + header
166+
return self.dumps(length + len(header)) + header
147167

148168

149169
class RequestReplace(Request):
@@ -159,8 +179,8 @@ def __init__(self, conn, space_no, values):
159179
super(RequestReplace, self).__init__(conn)
160180
assert isinstance(values, (tuple, list))
161181

162-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
163-
IPROTO_TUPLE: values})
182+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
183+
IPROTO_TUPLE: values})
164184

165185
self._body = request_body
166186

@@ -177,9 +197,9 @@ def __init__(self, conn, space_no, index_no, key):
177197
'''
178198
super(RequestDelete, self).__init__(conn)
179199

180-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
181-
IPROTO_INDEX_ID: index_no,
182-
IPROTO_KEY: key})
200+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
201+
IPROTO_INDEX_ID: index_no,
202+
IPROTO_KEY: key})
183203

184204
self._body = request_body
185205

@@ -193,12 +213,12 @@ class RequestSelect(Request):
193213
# pylint: disable=W0231
194214
def __init__(self, conn, space_no, index_no, key, offset, limit, iterator):
195215
super(RequestSelect, self).__init__(conn)
196-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
197-
IPROTO_INDEX_ID: index_no,
198-
IPROTO_OFFSET: offset,
199-
IPROTO_LIMIT: limit,
200-
IPROTO_ITERATOR: iterator,
201-
IPROTO_KEY: key})
216+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
217+
IPROTO_INDEX_ID: index_no,
218+
IPROTO_OFFSET: offset,
219+
IPROTO_LIMIT: limit,
220+
IPROTO_ITERATOR: iterator,
221+
IPROTO_KEY: key})
202222

203223
self._body = request_body
204224

@@ -214,10 +234,10 @@ class RequestUpdate(Request):
214234
def __init__(self, conn, space_no, index_no, key, op_list):
215235
super(RequestUpdate, self).__init__(conn)
216236

217-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
218-
IPROTO_INDEX_ID: index_no,
219-
IPROTO_KEY: key,
220-
IPROTO_TUPLE: op_list})
237+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
238+
IPROTO_INDEX_ID: index_no,
239+
IPROTO_KEY: key,
240+
IPROTO_TUPLE: op_list})
221241

222242
self._body = request_body
223243

@@ -235,8 +255,8 @@ def __init__(self, conn, name, args, call_16):
235255
super(RequestCall, self).__init__(conn)
236256
assert isinstance(args, (list, tuple))
237257

238-
request_body = msgpack.dumps({IPROTO_FUNCTION_NAME: name,
239-
IPROTO_TUPLE: args})
258+
request_body = self.dumps({IPROTO_FUNCTION_NAME: name,
259+
IPROTO_TUPLE: args})
240260

241261
self._body = request_body
242262

@@ -280,10 +300,10 @@ class RequestUpsert(Request):
280300
def __init__(self, conn, space_no, index_no, tuple_value, op_list):
281301
super(RequestUpsert, self).__init__(conn)
282302

283-
request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no,
284-
IPROTO_INDEX_ID: index_no,
285-
IPROTO_TUPLE: tuple_value,
286-
IPROTO_OPS: op_list})
303+
request_body = self.dumps({IPROTO_SPACE_ID: space_no,
304+
IPROTO_INDEX_ID: index_no,
305+
IPROTO_TUPLE: tuple_value,
306+
IPROTO_OPS: op_list})
287307

288308
self._body = request_body
289309

@@ -297,7 +317,7 @@ class RequestJoin(Request):
297317
# pylint: disable=W0231
298318
def __init__(self, conn, server_uuid):
299319
super(RequestJoin, self).__init__(conn)
300-
request_body = msgpack.dumps({IPROTO_SERVER_UUID: server_uuid})
320+
request_body = self.dumps({IPROTO_SERVER_UUID: server_uuid})
301321
self._body = request_body
302322

303323

@@ -312,7 +332,7 @@ def __init__(self, conn, cluster_uuid, server_uuid, vclock):
312332
super(RequestSubscribe, self).__init__(conn)
313333
assert isinstance(vclock, dict)
314334

315-
request_body = msgpack.dumps({
335+
request_body = self.dumps({
316336
IPROTO_CLUSTER_UUID: cluster_uuid,
317337
IPROTO_SERVER_UUID: server_uuid,
318338
IPROTO_VCLOCK: vclock
@@ -329,6 +349,6 @@ class RequestOK(Request):
329349
# pylint: disable=W0231
330350
def __init__(self, conn, sync):
331351
super(RequestOK, self).__init__(conn)
332-
request_body = msgpack.dumps({IPROTO_CODE: self.request_type,
333-
IPROTO_SYNC: sync})
352+
request_body = self.dumps({IPROTO_CODE: self.request_type,
353+
IPROTO_SYNC: sync})
334354
self._body = request_body

unit/suites/test_dml.py

+34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
from .lib.tarantool_server import TarantoolServer
1010

11+
12+
def version_id(major, minor, patch):
13+
return (((major << 8) | minor) << 8) | patch
14+
15+
1116
class TestSuite_Request(unittest.TestCase):
1217
@classmethod
1318
def setUpClass(self):
@@ -299,6 +304,35 @@ def test_12_update_fields(self):
299304
[[2, 'help', 7]]
300305
)
301306

307+
def test_13_varbinary(self):
308+
if sys.version_info.major == 2:
309+
raise unittest.SkipTest(
310+
'bytes are encoded into mp_bin only for Python 3')
311+
312+
if self.con.version_id < version_id(2, 2, 1):
313+
raise unittest.SkipTest(
314+
"'varbinary' is supported since 2.2.0-492-g59de57d23")
315+
316+
self.adm("""
317+
box.schema.create_space('space_3', {
318+
format = {
319+
[1] = {name = 'id', type = 'unsigned'},
320+
[2] = {name = 'value', type = 'varbinary'},
321+
}
322+
})
323+
""".replace('\n', ' '))
324+
self.adm("""
325+
box.space['space_3']:create_index('primary', {
326+
parts = {'id'},
327+
})
328+
""".replace('\n', ' '))
329+
330+
t = [1, b'\xff']
331+
self.assertEqual(self.con.insert('space_3', t)[0], t)
332+
333+
res = self.con.select('space_3', 1)
334+
self.assertSequenceEqual(res, [[1, b'\xff']])
335+
302336
@classmethod
303337
def tearDownClass(self):
304338
self.con.close()

0 commit comments

Comments
 (0)