Skip to content

Commit deb733d

Browse files
msgpack: support error extension type
Tarantool supports error extension type since version 2.4.1 [1]. This patch introduced the support of Tarantool error extension type in msgpack decoders and encoders. Tarantool error extension type objects are decoded to `tarantool.BoxError` type. `tarantool.BoxError` may be encoded to Tarantool error extension type objects. Error extension type internals are the same as errors extra information: the only difference is that extra information is encoded as a separate error dictionary field and error extension type objects is encoded as MessagePack extension type objects. The only way to receive an error extension type object from Tarantool is to receive an explicitly built `box.error` object: either from `return box.error.new(...)` or a tuple with it. All errors raised within Tarantool (including those raised with `box.error(...)`) are encoded based on the same rules as simple errors due to backward compatibility. It is possible to create error extension type objects with Python code, but it not likely to be really useful since most of their fields is computed on error initialization on server side (even for custom error types): ``` tarantool.BoxError([ tarantool.BoxErrorStackUnit( type='ClientError', file='[string " local err = box.error.ne..."]', line=1, message='Unknown error', errno=0, errcode=0, ) ]) ``` 1. tarantool/tarantool#4398 Part of #232
1 parent 5ecccf5 commit deb733d

File tree

8 files changed

+351
-9
lines changed

8 files changed

+351
-9
lines changed

doc/dev-guide.rst

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ they are represented with in-built and custom types:
8383
+-----------------------------+----+-------------+----+-----------------------------+
8484
| :obj:`uuid.UUID` | -> | `UUID`_ | -> | :obj:`uuid.UUID` |
8585
+-----------------------------+----+-------------+----+-----------------------------+
86+
| :class:`tarantool.BoxError` | -> | `ERROR`_ | -> | :class:`tarantool.BoxError` |
87+
+-----------------------------+----+-------------+----+-----------------------------+
8688
| :class:`tarantool.Datetime` | -> | `DATETIME`_ | -> | :class:`tarantool.Datetime` |
8789
+-----------------------------+----+-------------+----+-----------------------------+
8890
| :class:`tarantool.Interval` | -> | `INTERVAL`_ | -> | :class:`tarantool.Interval` |
@@ -109,5 +111,6 @@ and iterate through it as with any other serializable object.
109111
.. _extension types: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/
110112
.. _DECIMAL: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-decimal-type
111113
.. _UUID: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-uuid-type
114+
.. _ERROR: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-error-type
112115
.. _DATETIME: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-datetime-type
113116
.. _INTERVAL: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-interval-type

tarantool/const.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,4 @@
129129
# Tarantool 2.10 protocol version is 3
130130
CONNECTOR_IPROTO_VERSION = 3
131131
# List of connector-supported features
132-
CONNECTOR_FEATURES = []
132+
CONNECTOR_FEATURES = [IPROTO_FEATURE_ERROR_EXTENSION,]

tarantool/msgpack_ext/error.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Tarantool `error`_ extension type support module.
3+
4+
Refer to :mod:`~tarantool.msgpack_ext.types.error`.
5+
6+
.. _error: https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-error-type
7+
"""
8+
9+
import msgpack
10+
11+
from tarantool.types import (
12+
encode_box_error,
13+
decode_box_error,
14+
)
15+
16+
EXT_ID = 3
17+
"""
18+
`error`_ type id.
19+
"""
20+
21+
def encode(obj):
22+
"""
23+
Encode an error object.
24+
25+
:param obj: Error to encode.
26+
:type obj: :class:`tarantool.BoxError`
27+
28+
:return: Encoded error.
29+
:rtype: :obj:`bytes`
30+
"""
31+
32+
err_map = encode_box_error(obj)
33+
return msgpack.packb(err_map)
34+
35+
def decode(data):
36+
"""
37+
Decode an error object.
38+
39+
:param obj: Error to decode.
40+
:type obj: :obj:`bytes`
41+
42+
:return: Decoded error.
43+
:rtype: :class:`tarantool.BoxError`
44+
"""
45+
46+
err_map = msgpack.unpackb(data, strict_map_key=False)
47+
return decode_box_error(err_map)

tarantool/types.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class BoxErrorStackUnit():
4747
fields: typing.Optional[dict] = None
4848
"""
4949
Additional fields depending on error type. For example, if
50-
:attr:`~tarantool.types.BoxErrorStackComponent.type`
50+
:attr:`~tarantool.BoxErrorStackComponent.type`
5151
is ``"AccessDeniedError"``, then it will include ``"object_type"``,
5252
``"object_name"``, ``"access_type"``.
5353
"""
@@ -62,7 +62,7 @@ class BoxError():
6262

6363
stack: list
6464
"""
65-
Array of :class:`~tarantool.types.BoxErrorStackUnit` objects.
65+
Array of :class:`~tarantool.BoxErrorStackUnit` objects.
6666
"""
6767

6868
MP_ERROR_STACK = 0x00
@@ -102,3 +102,33 @@ def decode_box_error(err_map):
102102
))
103103

104104
return BoxError(stack)
105+
106+
def encode_box_error(err):
107+
"""
108+
Encode Python `box.error`_ representation to MessagePack map.
109+
110+
:param err: Error to encode
111+
:type err: :obj:`tarantool.BoxError`
112+
113+
:rtype: :obj:`dict`
114+
115+
:raises: :exc:`KeyError`
116+
"""
117+
118+
stack = []
119+
for item in err.stack:
120+
dict_item = {
121+
MP_ERROR_TYPE: item.type,
122+
MP_ERROR_FILE: item.file,
123+
MP_ERROR_LINE: item.line,
124+
MP_ERROR_MESSAGE: item.message,
125+
MP_ERROR_ERRNO: item.errno,
126+
MP_ERROR_ERRCODE: item.errcode,
127+
}
128+
129+
if item.fields is not None: # omitted if empty
130+
dict_item[MP_ERROR_FIELDS] = item.fields
131+
132+
stack.append(dict_item)
133+
134+
return {MP_ERROR_STACK: stack}

test/suites/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
from .test_uuid import TestSuite_UUID
2020
from .test_datetime import TestSuite_Datetime
2121
from .test_interval import TestSuite_Interval
22+
from .test_error_ext import TestSuite_ErrorExt
2223

2324
test_cases = (TestSuite_Schema_UnicodeConnection,
2425
TestSuite_Schema_BinaryConnection,
2526
TestSuite_Request, TestSuite_Protocol, TestSuite_Reconnect,
2627
TestSuite_Mesh, TestSuite_Execute, TestSuite_DBAPI,
2728
TestSuite_Encoding, TestSuite_Pool, TestSuite_Ssl,
2829
TestSuite_Decimal, TestSuite_UUID, TestSuite_Datetime,
29-
TestSuite_Interval)
30+
TestSuite_Interval, TestSuite_ErrorExt,)
3031

3132
def load_tests(loader, tests, pattern):
3233
suite = unittest.TestSuite()

test/suites/lib/skip.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ def skip_or_run_datetime_test(func):
155155
return skip_or_run_test_pcall_require(func, 'datetime',
156156
'does not support datetime type')
157157

158-
def skip_or_run_error_extra_info_test(func):
159-
"""Decorator to skip or run tests related to extra error info
160-
provided over iproto depending on the tarantool version.
158+
def skip_or_run_error_ext_type_test(func):
159+
"""Decorator to skip or run tests related to error extension
160+
type depending on the tarantool version.
161161
162-
Tarantool provides extra error info only since 2.4.1 version.
162+
Tarantool supports error extension type only since 2.4.1 version.
163163
See https://github.com/tarantool/tarantool/issues/4398
164164
"""
165165

0 commit comments

Comments
 (0)