diff --git a/.travis.yml b/.travis.yml index 784cb107..268506fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,15 @@ cache: false env: matrix: - TARGET=test + PYTHON_MSGPACK=msgpack-python==0.4.0 + - TARGET=test + PYTHON_MSGPACK=msgpack==0.5.0 + - TARGET=test + PYTHON_MSGPACK=msgpack==0.6.0 + - TARGET=test + PYTHON_MSGPACK=msgpack==0.6.2 + - TARGET=test + PYTHON_MSGPACK=msgpack==1.0.0 - OS=el DIST=6 - OS=el DIST=7 - OS=fedora DIST=28 diff --git a/requirements.txt b/requirements.txt index 5a1556c1..14261dfd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -msgpack-python>=0.4.0 +msgpack>=0.4.0 diff --git a/tarantool/connection.py b/tarantool/connection.py index 6f330896..325a664f 100644 --- a/tarantool/connection.py +++ b/tarantool/connection.py @@ -52,6 +52,7 @@ NetworkError, DatabaseError, InterfaceError, + ConfigurationError, SchemaError, NetworkWarning, SchemaReloadException, @@ -79,6 +80,7 @@ class Connection(object): Error = tarantool.error DatabaseError = DatabaseError InterfaceError = InterfaceError + ConfigurationError = ConfigurationError SchemaError = SchemaError NetworkError = NetworkError @@ -101,6 +103,11 @@ def __init__(self, host, port, creates network connection. if False than you have to call connect() manualy. ''' + + if msgpack.version >= (1, 0, 0) and encoding not in (None, 'utf-8'): + raise ConfigurationError("Only None and 'utf-8' encoding option " + + "values are supported with msgpack>=1.0.0") + if os.name == 'nt': libc = ctypes.WinDLL( ctypes.util.find_library('Ws2_32'), use_last_error=True diff --git a/tarantool/request.py b/tarantool/request.py index 2e45c8d9..e4c1acc7 100644 --- a/tarantool/request.py +++ b/tarantool/request.py @@ -65,6 +65,34 @@ def __init__(self, conn): self._sync = None self._body = '' + packer_kwargs = dict() + + # use_bin_type=True is default since msgpack-1.0.0. + # + # The option controls whether to pack binary (non-unicode) + # string values as mp_bin or as mp_str. + # + # The default behaviour of the connector is to pack both + # bytes and Unicode strings as mp_str. + # + # msgpack-0.5.0 (and only this version) warns when the + # option is unset: + # + # | FutureWarning: use_bin_type option is not specified. + # | Default value of the option will be changed in future + # | version. + # + # The option is supported since msgpack-0.4.0, so we can + # just always set it for all msgpack versions to get rid + # of the warning on msgpack-0.5.0 and to keep our + # behaviour on msgpack-1.0.0. + packer_kwargs['use_bin_type'] = False + + self.packer = msgpack.Packer(**packer_kwargs) + + def _dumps(self, src): + return self.packer.pack(src) + def __bytes__(self): return self.header(len(self._body)) + self._body @@ -82,11 +110,11 @@ def sync(self): def header(self, length): self._sync = self.conn.generate_sync() - header = msgpack.dumps({IPROTO_CODE: self.request_type, - IPROTO_SYNC: self._sync, - IPROTO_SCHEMA_ID: self.conn.schema_version}) + header = self._dumps({IPROTO_CODE: self.request_type, + IPROTO_SYNC: self._sync, + IPROTO_SCHEMA_ID: self.conn.schema_version}) - return msgpack.dumps(length + len(header)) + header + return self._dumps(length + len(header)) + header class RequestInsert(Request): @@ -102,8 +130,8 @@ def __init__(self, conn, space_no, values): super(RequestInsert, self).__init__(conn) assert isinstance(values, (tuple, list)) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_TUPLE: values}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_TUPLE: values}) self._body = request_body @@ -131,19 +159,19 @@ def sha1(values): hash2 = sha1((hash1,)) scramble = sha1((salt, hash2)) scramble = strxor(hash1, scramble) - request_body = msgpack.dumps({IPROTO_USER_NAME: user, - IPROTO_TUPLE: ("chap-sha1", scramble)}) + request_body = self._dumps({IPROTO_USER_NAME: user, + IPROTO_TUPLE: ("chap-sha1", scramble)}) self._body = request_body def header(self, length): self._sync = self.conn.generate_sync() # Set IPROTO_SCHEMA_ID: 0 to avoid SchemaReloadException # It is ok to use 0 in auth every time. - header = msgpack.dumps({IPROTO_CODE: self.request_type, - IPROTO_SYNC: self._sync, - IPROTO_SCHEMA_ID: 0}) + header = self._dumps({IPROTO_CODE: self.request_type, + IPROTO_SYNC: self._sync, + IPROTO_SCHEMA_ID: 0}) - return msgpack.dumps(length + len(header)) + header + return self._dumps(length + len(header)) + header class RequestReplace(Request): @@ -159,8 +187,8 @@ def __init__(self, conn, space_no, values): super(RequestReplace, self).__init__(conn) assert isinstance(values, (tuple, list)) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_TUPLE: values}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_TUPLE: values}) self._body = request_body @@ -177,9 +205,9 @@ def __init__(self, conn, space_no, index_no, key): ''' super(RequestDelete, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_INDEX_ID: index_no, - IPROTO_KEY: key}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_INDEX_ID: index_no, + IPROTO_KEY: key}) self._body = request_body @@ -193,12 +221,12 @@ class RequestSelect(Request): # pylint: disable=W0231 def __init__(self, conn, space_no, index_no, key, offset, limit, iterator): super(RequestSelect, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_INDEX_ID: index_no, - IPROTO_OFFSET: offset, - IPROTO_LIMIT: limit, - IPROTO_ITERATOR: iterator, - IPROTO_KEY: key}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_INDEX_ID: index_no, + IPROTO_OFFSET: offset, + IPROTO_LIMIT: limit, + IPROTO_ITERATOR: iterator, + IPROTO_KEY: key}) self._body = request_body @@ -214,10 +242,10 @@ class RequestUpdate(Request): def __init__(self, conn, space_no, index_no, key, op_list): super(RequestUpdate, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_INDEX_ID: index_no, - IPROTO_KEY: key, - IPROTO_TUPLE: op_list}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_INDEX_ID: index_no, + IPROTO_KEY: key, + IPROTO_TUPLE: op_list}) self._body = request_body @@ -235,8 +263,8 @@ def __init__(self, conn, name, args, call_16): super(RequestCall, self).__init__(conn) assert isinstance(args, (list, tuple)) - request_body = msgpack.dumps({IPROTO_FUNCTION_NAME: name, - IPROTO_TUPLE: args}) + request_body = self._dumps({IPROTO_FUNCTION_NAME: name, + IPROTO_TUPLE: args}) self._body = request_body @@ -252,8 +280,8 @@ def __init__(self, conn, name, args): super(RequestEval, self).__init__(conn) assert isinstance(args, (list, tuple)) - request_body = msgpack.dumps({IPROTO_EXPR: name, - IPROTO_TUPLE: args}) + request_body = self._dumps({IPROTO_EXPR: name, + IPROTO_TUPLE: args}) self._body = request_body @@ -280,10 +308,10 @@ class RequestUpsert(Request): def __init__(self, conn, space_no, index_no, tuple_value, op_list): super(RequestUpsert, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_SPACE_ID: space_no, - IPROTO_INDEX_ID: index_no, - IPROTO_TUPLE: tuple_value, - IPROTO_OPS: op_list}) + request_body = self._dumps({IPROTO_SPACE_ID: space_no, + IPROTO_INDEX_ID: index_no, + IPROTO_TUPLE: tuple_value, + IPROTO_OPS: op_list}) self._body = request_body @@ -297,7 +325,7 @@ class RequestJoin(Request): # pylint: disable=W0231 def __init__(self, conn, server_uuid): super(RequestJoin, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_SERVER_UUID: server_uuid}) + request_body = self._dumps({IPROTO_SERVER_UUID: server_uuid}) self._body = request_body @@ -312,7 +340,7 @@ def __init__(self, conn, cluster_uuid, server_uuid, vclock): super(RequestSubscribe, self).__init__(conn) assert isinstance(vclock, dict) - request_body = msgpack.dumps({ + request_body = self._dumps({ IPROTO_CLUSTER_UUID: cluster_uuid, IPROTO_SERVER_UUID: server_uuid, IPROTO_VCLOCK: vclock @@ -329,6 +357,6 @@ class RequestOK(Request): # pylint: disable=W0231 def __init__(self, conn, sync): super(RequestOK, self).__init__(conn) - request_body = msgpack.dumps({IPROTO_CODE: self.request_type, - IPROTO_SYNC: sync}) + request_body = self._dumps({IPROTO_CODE: self.request_type, + IPROTO_SYNC: sync}) self._body = request_body diff --git a/tarantool/response.py b/tarantool/response.py index 9516cd39..d8b479c1 100644 --- a/tarantool/response.py +++ b/tarantool/response.py @@ -50,15 +50,38 @@ def __init__(self, conn, response): # created in the __new__(). # super(Response, self).__init__() + unpacker_kwargs = dict() + + # Decode msgpack arrays into Python lists (not tuples). + unpacker_kwargs['use_list'] = True + + # Use raw=False instead of encoding='utf-8'. if msgpack.version >= (0, 5, 2) and conn.encoding == 'utf-8': # Get rid of the following warning. # > PendingDeprecationWarning: encoding is deprecated, # > Use raw=False instead. - unpacker = msgpack.Unpacker(use_list=True, raw=False) + unpacker_kwargs['raw'] = False elif conn.encoding is not None: - unpacker = msgpack.Unpacker(use_list=True, encoding=conn.encoding) - else: - unpacker = msgpack.Unpacker(use_list=True) + unpacker_kwargs['encoding'] = conn.encoding + + # raw=False is default since msgpack-1.0.0. + # + # The option decodes mp_str to bytes, not a Unicode + # string (when True). + if msgpack.version >= (1, 0, 0) and conn.encoding is None: + unpacker_kwargs['raw'] = True + + # encoding option is not supported since msgpack-1.0.0, + # but it is handled in the Connection constructor. + assert(msgpack.version < (1, 0, 0) or conn.encoding in (None, 'utf-8')) + + # strict_map_key=True is default since msgpack-1.0.0. + # + # The option forbids non-string keys in a map (when True). + if msgpack.version >= (1, 0, 0): + unpacker_kwargs['strict_map_key'] = False + + unpacker = msgpack.Unpacker(**unpacker_kwargs) unpacker.feed(response) header = unpacker.unpack() diff --git a/test.sh b/test.sh index a8c1b577..ce701592 100755 --- a/test.sh +++ b/test.sh @@ -9,8 +9,13 @@ echo "deb http://download.tarantool.org/tarantool/2x/ubuntu/ ${release} main" | sudo apt-get update > /dev/null sudo apt-get -q -y install tarantool +# Install module requirements. +# +# Keep it in sync with requirements.txt. +pip install "${PYTHON_MSGPACK:-msgpack==1.0.0}" +python -c 'import msgpack; print(msgpack.version)' + # Install testing dependencies. -pip install -r requirements.txt pip install pyyaml # Run tests.