Skip to content

Commit 1526316

Browse files
committed
Merge branch '0.2-maint'
Conflicts: msgpack/_msgpack.pyx
2 parents d13f10c + 477d3b1 commit 1526316

File tree

6 files changed

+87
-71
lines changed

6 files changed

+87
-71
lines changed

ChangeLog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Changes
1313

1414
Changes
1515
-------
16+
* Warn when use_list is not specified. It's default value will be changed in 0.3.
1617

1718
Bugs fixed
1819
-----------

msgpack/_msgpack.pyx

Lines changed: 51 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ cdef extern from "unpack.h":
201201
ctypedef struct msgpack_user:
202202
bint use_list
203203
PyObject* object_hook
204+
bint has_pairs_hook # call object_hook with k-v pairs
204205
PyObject* list_hook
205206
char *encoding
206207
char *unicode_errors
@@ -217,9 +218,50 @@ cdef extern from "unpack.h":
217218
void template_init(template_context* ctx)
218219
object template_data(template_context* ctx)
219220

221+
cdef inline init_ctx(template_context *ctx, object object_hook, object object_pairs_hook, object list_hook, bint use_list, encoding, unicode_errors):
222+
template_init(ctx)
223+
ctx.user.use_list = use_list
224+
ctx.user.object_hook = ctx.user.list_hook = <PyObject*>NULL
225+
226+
if object_hook is not None and object_pairs_hook is not None:
227+
raise ValueError("object_pairs_hook and object_hook are mutually exclusive.")
228+
229+
if object_hook is not None:
230+
if not PyCallable_Check(object_hook):
231+
raise TypeError("object_hook must be a callable.")
232+
ctx.user.object_hook = <PyObject*>object_hook
233+
234+
if object_pairs_hook is None:
235+
ctx.user.has_pairs_hook = False
236+
else:
237+
if not PyCallable_Check(object_pairs_hook):
238+
raise TypeError("object_pairs_hook must be a callable.")
239+
ctx.user.object_hook = <PyObject*>object_pairs_hook
240+
ctx.user.has_pairs_hook = True
241+
242+
if list_hook is not None:
243+
if not PyCallable_Check(list_hook):
244+
raise TypeError("list_hook must be a callable.")
245+
ctx.user.list_hook = <PyObject*>list_hook
246+
247+
if encoding is None:
248+
ctx.user.encoding = NULL
249+
ctx.user.unicode_errors = NULL
250+
else:
251+
if isinstance(encoding, unicode):
252+
_bencoding = encoding.encode('ascii')
253+
else:
254+
_bencoding = encoding
255+
ctx.user.encoding = PyBytes_AsString(_bencoding)
256+
if isinstance(unicode_errors, unicode):
257+
_berrors = unicode_errors.encode('ascii')
258+
else:
259+
_berrors = unicode_errors
260+
ctx.user.unicode_errors = PyBytes_AsString(_berrors)
220261

221262
def unpackb(object packed, object object_hook=None, object list_hook=None,
222263
bint use_list=1, encoding=None, unicode_errors="strict",
264+
object_pairs_hook=None,
223265
):
224266
"""Unpack packed_bytes to object. Returns an unpacked object.
225267
@@ -234,34 +276,7 @@ def unpackb(object packed, object object_hook=None, object list_hook=None,
234276

235277
PyObject_AsReadBuffer(packed, <const_void_ptr*>&buf, &buf_len)
236278

237-
if encoding is None:
238-
enc = NULL
239-
err = NULL
240-
else:
241-
if isinstance(encoding, unicode):
242-
bencoding = encoding.encode('ascii')
243-
else:
244-
bencoding = encoding
245-
if isinstance(unicode_errors, unicode):
246-
berrors = unicode_errors.encode('ascii')
247-
else:
248-
berrors = unicode_errors
249-
enc = PyBytes_AsString(bencoding)
250-
err = PyBytes_AsString(berrors)
251-
252-
template_init(&ctx)
253-
ctx.user.use_list = use_list
254-
ctx.user.object_hook = ctx.user.list_hook = NULL
255-
ctx.user.encoding = <const_char_ptr>enc
256-
ctx.user.unicode_errors = <const_char_ptr>err
257-
if object_hook is not None:
258-
if not PyCallable_Check(object_hook):
259-
raise TypeError("object_hook must be a callable.")
260-
ctx.user.object_hook = <PyObject*>object_hook
261-
if list_hook is not None:
262-
if not PyCallable_Check(list_hook):
263-
raise TypeError("list_hook must be a callable.")
264-
ctx.user.list_hook = <PyObject*>list_hook
279+
init_ctx(&ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors)
265280
ret = template_execute(&ctx, buf, buf_len, &off, 1)
266281
if ret == 1:
267282
obj = template_data(&ctx)
@@ -274,13 +289,14 @@ def unpackb(object packed, object object_hook=None, object list_hook=None,
274289

275290
def unpack(object stream, object object_hook=None, object list_hook=None,
276291
bint use_list=1, encoding=None, unicode_errors="strict",
292+
object_pairs_hook=None,
277293
):
278294
"""Unpack an object from `stream`.
279295
280296
Raises `ValueError` when `stream` has extra bytes.
281297
"""
282298
return unpackb(stream.read(), use_list=use_list,
283-
object_hook=object_hook, list_hook=list_hook,
299+
object_hook=object_hook, object_pairs_hook=object_pairs_hook, list_hook=list_hook,
284300
encoding=encoding, unicode_errors=unicode_errors,
285301
)
286302

@@ -300,7 +316,10 @@ cdef class Unpacker(object):
300316
Otherwise, it is deserialized to Python tuple.
301317
302318
`object_hook` is same to simplejson. If it is not None, it should be callable
303-
and Unpacker calls it when deserializing key-value.
319+
and Unpacker calls it with a dict argument after deserializing a map.
320+
321+
`object_pairs_hook` is same to simplejson. If it is not None, it should be callable
322+
and Unpacker calls it with a list of key-value pairs after deserializing a map.
304323
305324
`encoding` is encoding used for decoding msgpack bytes. If it is None (default),
306325
msgpack bytes is deserialized to Python bytes.
@@ -350,9 +369,8 @@ cdef class Unpacker(object):
350369
self.buf = NULL
351370

352371
def __init__(self, file_like=None, Py_ssize_t read_size=0, bint use_list=1,
353-
object object_hook=None, object list_hook=None,
372+
object object_hook=None, object object_pairs_hook=None, object list_hook=None,
354373
encoding=None, unicode_errors='strict', int max_buffer_size=0,
355-
object object_pairs_hook=None,
356374
):
357375
self.file_like = file_like
358376
if file_like:
@@ -373,31 +391,7 @@ cdef class Unpacker(object):
373391
self.buf_size = read_size
374392
self.buf_head = 0
375393
self.buf_tail = 0
376-
template_init(&self.ctx)
377-
self.ctx.user.use_list = use_list
378-
self.ctx.user.object_hook = self.ctx.user.list_hook = <PyObject*>NULL
379-
if object_hook is not None:
380-
if not PyCallable_Check(object_hook):
381-
raise TypeError("object_hook must be a callable.")
382-
self.ctx.user.object_hook = <PyObject*>object_hook
383-
if list_hook is not None:
384-
if not PyCallable_Check(list_hook):
385-
raise TypeError("list_hook must be a callable.")
386-
self.ctx.user.list_hook = <PyObject*>list_hook
387-
if encoding is None:
388-
self.ctx.user.encoding = NULL
389-
self.ctx.user.unicode_errors = NULL
390-
else:
391-
if isinstance(encoding, unicode):
392-
self._bencoding = encoding.encode('ascii')
393-
else:
394-
self._bencoding = encoding
395-
self.ctx.user.encoding = PyBytes_AsString(self._bencoding)
396-
if isinstance(unicode_errors, unicode):
397-
self._berrors = unicode_errors.encode('ascii')
398-
else:
399-
self._berrors = unicode_errors
400-
self.ctx.user.unicode_errors = PyBytes_AsString(self._berrors)
394+
init_ctx(&self.ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors)
401395

402396
def feed(self, object next_bytes):
403397
cdef char* buf

msgpack/unpack.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
typedef struct unpack_user {
2323
int use_list;
2424
PyObject *object_hook;
25+
bool has_pairs_hook;
2526
PyObject *list_hook;
2627
const char *encoding;
2728
const char *unicode_errors;
@@ -160,9 +161,7 @@ static inline int template_callback_array_item(unpack_user* u, unsigned int curr
160161
static inline int template_callback_array_end(unpack_user* u, msgpack_unpack_object* c)
161162
{
162163
if (u->list_hook) {
163-
PyObject *arglist = Py_BuildValue("(O)", *c);
164-
PyObject *new_c = PyEval_CallObject(u->list_hook, arglist);
165-
Py_DECREF(arglist);
164+
PyObject *new_c = PyEval_CallFunction(u->list_hook, "(O)", *c);
166165
Py_DECREF(*c);
167166
*c = new_c;
168167
}
@@ -171,16 +170,31 @@ static inline int template_callback_array_end(unpack_user* u, msgpack_unpack_obj
171170

172171
static inline int template_callback_map(unpack_user* u, unsigned int n, msgpack_unpack_object* o)
173172
{
174-
PyObject *p = PyDict_New();
173+
PyObject *p;
174+
if (u->has_pairs_hook) {
175+
p = PyList_New(n); // Or use tuple?
176+
}
177+
else {
178+
p = PyDict_New();
179+
}
175180
if (!p)
176181
return -1;
177182
*o = p;
178183
return 0;
179184
}
180185

181-
static inline int template_callback_map_item(unpack_user* u, msgpack_unpack_object* c, msgpack_unpack_object k, msgpack_unpack_object v)
186+
static inline int template_callback_map_item(unpack_user* u, unsigned int current, msgpack_unpack_object* c, msgpack_unpack_object k, msgpack_unpack_object v)
182187
{
183-
if (PyDict_SetItem(*c, k, v) == 0) {
188+
if (u->has_pairs_hook) {
189+
msgpack_unpack_object item = PyTuple_Pack(2, k, v);
190+
if (!item)
191+
return -1;
192+
Py_DECREF(k);
193+
Py_DECREF(v);
194+
PyList_SET_ITEM(*c, current, item);
195+
return 0;
196+
}
197+
else if (PyDict_SetItem(*c, k, v) == 0) {
184198
Py_DECREF(k);
185199
Py_DECREF(v);
186200
return 0;
@@ -191,9 +205,7 @@ static inline int template_callback_map_item(unpack_user* u, msgpack_unpack_obje
191205
static inline int template_callback_map_end(unpack_user* u, msgpack_unpack_object* c)
192206
{
193207
if (u->object_hook) {
194-
PyObject *arglist = Py_BuildValue("(O)", *c);
195-
PyObject *new_c = PyEval_CallObject(u->object_hook, arglist);
196-
Py_DECREF(arglist);
208+
PyObject *new_c = PyEval_CallFunction(u->object_hook, "(O)", *c);
197209
Py_DECREF(*c);
198210
*c = new_c;
199211
}

msgpack/unpack_template.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ msgpack_unpack_func(int, _execute)(msgpack_unpack_struct(_context)* ctx, const c
357357
c->ct = CT_MAP_VALUE;
358358
goto _header_again;
359359
case CT_MAP_VALUE:
360-
if(construct_cb(_map_item)(user, &c->obj, c->map_key, obj) < 0) { goto _failed; }
360+
if(construct_cb(_map_item)(user, c->count, &c->obj, c->map_key, obj) < 0) { goto _failed; }
361361
if(++c->count == c->size) {
362362
obj = c->obj;
363363
construct_cb(_map_end)(user, &obj);

test/test_obj.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ def test_decode_hook():
2626
unpacked = unpackb(packed, object_hook=_decode_complex, use_list=1)
2727
eq_(unpacked[1], 1+2j)
2828

29+
def test_decode_pairs_hook():
30+
packed = packb([3, {1: 2, 3: 4}])
31+
prod_sum = 1 * 2 + 3 * 4
32+
unpacked = unpackb(packed, object_pairs_hook=lambda l: sum(k * v for k, v in l), use_list=1)
33+
eq_(unpacked[1], prod_sum)
34+
35+
@raises(ValueError)
36+
def test_only_one_obj_hook():
37+
unpackb(b'', object_hook=lambda x: x, object_pairs_hook=lambda x: x)
38+
2939
@raises(ValueError)
3040
def test_bad_hook():
3141
packed = packb([3, 1+2j], default=lambda o: o)

test/test_pack.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,9 @@ def test_odict():
110110
seq = [(b'one', 1), (b'two', 2), (b'three', 3), (b'four', 4)]
111111
od = odict(seq)
112112
assert_equal(unpackb(packb(od), use_list=1), dict(seq))
113-
# After object_pairs_hook is implemented.
114-
#def pair_hook(seq):
115-
# return seq
116-
#assert_equal(unpackb(packb(od), object_pairs_hook=pair_hook), seq)
113+
def pair_hook(seq):
114+
return seq
115+
assert_equal(unpackb(packb(od), object_pairs_hook=pair_hook, use_list=1), seq)
117116

118117

119118
if __name__ == '__main__':

0 commit comments

Comments
 (0)