Skip to content

Commit fa15708

Browse files
committed
Add object_hook option to unpack and default option to pack.
(see simplejson for how to use).
1 parent 367f15c commit fa15708

4 files changed

Lines changed: 92 additions & 10 deletions

File tree

msgpack/_msgpack.pyx

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ cdef extern from "Python.h":
2020
cdef bint PyFloat_Check(object o)
2121
cdef bint PyBytes_Check(object o)
2222
cdef bint PyUnicode_Check(object o)
23+
cdef bint PyCallable_Check(object o)
24+
cdef void Py_INCREF(object o)
25+
cdef void Py_DECREF(object o)
2326

2427
cdef extern from "stdlib.h":
2528
void* malloc(size_t)
@@ -60,13 +63,20 @@ cdef class Packer(object):
6063
astream.write(packer.pack(b))
6164
"""
6265
cdef msgpack_packer pk
66+
cdef object default
6367

6468
def __cinit__(self):
6569
cdef int buf_size = 1024*1024
6670
self.pk.buf = <char*> malloc(buf_size);
6771
self.pk.buf_size = buf_size
6872
self.pk.length = 0
6973

74+
def __init__(self, default=None):
75+
if default is not None:
76+
if not PyCallable_Check(default):
77+
raise TypeError("default must be a callable.")
78+
self.default = default
79+
7080
def __dealloc__(self):
7181
free(self.pk.buf);
7282

@@ -126,9 +136,18 @@ cdef class Packer(object):
126136
for v in o:
127137
ret = self._pack(v)
128138
if ret != 0: break
139+
elif self.default is not None:
140+
o = self.default(o)
141+
d = o
142+
ret = msgpack_pack_map(&self.pk, len(d))
143+
if ret == 0:
144+
for k,v in d.items():
145+
ret = self._pack(k)
146+
if ret != 0: break
147+
ret = self._pack(v)
148+
if ret != 0: break
129149
else:
130-
# TODO: Serialize with defalt() like simplejson.
131-
raise TypeError, "can't serialize %r" % (o,)
150+
raise TypeError("can't serialize %r" % (o,))
132151
return ret
133152

134153
def pack(self, object obj):
@@ -141,21 +160,22 @@ cdef class Packer(object):
141160
return buf
142161

143162

144-
def pack(object o, object stream):
163+
def pack(object o, object stream, default=None):
145164
"""pack an object `o` and write it to stream)."""
146-
packer = Packer()
165+
packer = Packer(default)
147166
stream.write(packer.pack(o))
148167

149-
def packb(object o):
168+
def packb(object o, default=None):
150169
"""pack o and return packed bytes."""
151-
packer = Packer()
170+
packer = Packer(default=default)
152171
return packer.pack(o)
153172

154173
packs = packb
155174

156175
cdef extern from "unpack.h":
157176
ctypedef struct msgpack_user:
158177
int use_list
178+
PyObject* object_hook
159179

160180
ctypedef struct template_context:
161181
msgpack_user user
@@ -170,26 +190,35 @@ cdef extern from "unpack.h":
170190
object template_data(template_context* ctx)
171191

172192

173-
def unpackb(bytes packed_bytes):
193+
def unpackb(bytes packed_bytes, object object_hook=None):
174194
"""Unpack packed_bytes to object. Returns an unpacked object."""
175195
cdef const_char_ptr p = packed_bytes
176196
cdef template_context ctx
177197
cdef size_t off = 0
178198
cdef int ret
179199
template_init(&ctx)
180200
ctx.user.use_list = 0
201+
ctx.user.object_hook = NULL
202+
if object_hook is not None:
203+
if not PyCallable_Check(object_hook):
204+
raise TypeError("object_hook must be a callable.")
205+
Py_INCREF(object_hook)
206+
ctx.user.object_hook = <PyObject*>object_hook
181207
ret = template_execute(&ctx, p, len(packed_bytes), &off)
208+
if object_hook is not None:
209+
pass
210+
#Py_DECREF(object_hook)
182211
if ret == 1:
183212
return template_data(&ctx)
184213
else:
185214
return None
186215

187216
unpacks = unpackb
188217

189-
def unpack(object stream):
218+
def unpack(object stream, object object_hook=None):
190219
"""unpack an object from stream."""
191220
packed = stream.read()
192-
return unpackb(packed)
221+
return unpackb(packed, object_hook=object_hook)
193222

194223
cdef class UnpackIterator(object):
195224
cdef object unpacker
@@ -234,6 +263,7 @@ cdef class Unpacker(object):
234263
cdef int read_size
235264
cdef object waiting_bytes
236265
cdef bint use_list
266+
cdef object object_hook
237267

238268
def __cinit__(self):
239269
self.buf = NULL
@@ -242,7 +272,8 @@ cdef class Unpacker(object):
242272
if self.buf:
243273
free(self.buf);
244274

245-
def __init__(self, file_like=None, int read_size=0, bint use_list=0):
275+
def __init__(self, file_like=None, int read_size=0, bint use_list=0,
276+
object object_hook=None):
246277
if read_size == 0:
247278
read_size = 1024*1024
248279
self.use_list = use_list
@@ -255,6 +286,11 @@ cdef class Unpacker(object):
255286
self.buf_tail = 0
256287
template_init(&self.ctx)
257288
self.ctx.user.use_list = use_list
289+
self.ctx.user.object_hook = <PyObject*>NULL
290+
if object_hook is not None:
291+
if not PyCallable_Check(object_hook):
292+
raise TypeError("object_hook must be a callable.")
293+
self.ctx.user.object_hook = <PyObject*>object_hook
258294

259295
def feed(self, bytes next_bytes):
260296
self.waiting_bytes.append(next_bytes)

msgpack/unpack.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
typedef struct unpack_user {
2323
int use_list;
24+
PyObject *object_hook;
2425
} unpack_user;
2526

2627

@@ -172,6 +173,19 @@ static inline int template_callback_map_item(unpack_user* u, msgpack_unpack_obje
172173
return -1;
173174
}
174175

176+
//static inline int template_callback_map_end(unpack_user* u, msgpack_unpack_object* c)
177+
int template_callback_map_end(unpack_user* u, msgpack_unpack_object* c)
178+
{
179+
if (u->object_hook) {
180+
PyObject *arglist = Py_BuildValue("(O)", *c);
181+
Py_INCREF(*c);
182+
*c = PyEval_CallObject(u->object_hook, arglist);
183+
Py_DECREF(arglist);
184+
return 0;
185+
}
186+
return -1;
187+
}
188+
175189
static inline int template_callback_raw(unpack_user* u, const char* b, const char* p, unsigned int l, msgpack_unpack_object* o)
176190
{
177191
PyObject *py;

msgpack/unpack_template.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ msgpack_unpack_func(int, _execute)(msgpack_unpack_struct(_context)* ctx, const c
317317
case CT_MAP_VALUE:
318318
if(msgpack_unpack_callback(_map_item)(user, &c->obj, c->map_key, obj) < 0) { goto _failed; }
319319
if(--c->count == 0) {
320+
msgpack_unpack_callback(_map_end)(user, &c->obj);
320321
obj = c->obj;
321322
--top;
322323
/*printf("stack pop %d\n", top);*/

test/test_obj.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
from nose import main
5+
from nose.tools import *
6+
7+
from msgpack import packs, unpacks
8+
9+
def _decode_complex(obj):
10+
if '__complex__' in obj:
11+
return complex(obj['real'], obj['imag'])
12+
return obj
13+
14+
def _encode_complex(obj):
15+
if isinstance(obj, complex):
16+
return {'__complex__': True, 'real': 1, 'imag': 2}
17+
return obj
18+
19+
def test_encode_hook():
20+
packed = packs([3, 1+2j], default=_encode_complex)
21+
unpacked = unpacks(packed)
22+
eq_(unpacked[1], {'__complex__': True, 'real': 1, 'imag': 2})
23+
24+
def test_decode_hook():
25+
packed = packs([3, {'__complex__': True, 'real': 1, 'imag': 2}])
26+
unpacked = unpacks(packed, object_hook=_decode_complex)
27+
eq_(unpacked[1], 1+2j)
28+
29+
if __name__ == '__main__':
30+
#main()
31+
test_decode_hook()

0 commit comments

Comments
 (0)