mirror of
				https://github.com/msgpack/msgpack-python.git
				synced 2025-10-31 09:30:53 +00:00 
			
		
		
		
	Implement object_pairs_hook
This commit is contained in:
		
							parent
							
								
									b06ed8eb75
								
							
						
					
					
						commit
						77942514db
					
				
					 5 changed files with 61 additions and 22 deletions
				
			
		|  | @ -197,6 +197,7 @@ cdef extern from "unpack.h": | |||
|     ctypedef struct msgpack_user: | ||||
|         int use_list | ||||
|         PyObject* object_hook | ||||
|         bint has_pairs_hook # call object_hook with k-v pairs | ||||
|         PyObject* list_hook | ||||
|         char *encoding | ||||
|         char *unicode_errors | ||||
|  | @ -213,18 +214,32 @@ cdef extern from "unpack.h": | |||
|     void template_init(template_context* ctx) | ||||
|     object template_data(template_context* ctx) | ||||
| 
 | ||||
| cdef inline init_ctx(template_context *ctx, object object_hook, object list_hook, bint use_list, encoding, unicode_errors): | ||||
| cdef inline init_ctx(template_context *ctx, object object_hook, object object_pairs_hook, object list_hook, bint use_list, encoding, unicode_errors): | ||||
|     template_init(ctx) | ||||
|     ctx.user.use_list = use_list | ||||
|     ctx.user.object_hook = ctx.user.list_hook = <PyObject*>NULL | ||||
| 
 | ||||
|     if object_hook is not None and object_pairs_hook is not None: | ||||
|         raise ValueError("object_pairs_hook and object_hook are mutually exclusive.") | ||||
| 
 | ||||
|     if object_hook is not None: | ||||
|         if not PyCallable_Check(object_hook): | ||||
|             raise TypeError("object_hook must be a callable.") | ||||
|         ctx.user.object_hook = <PyObject*>object_hook | ||||
| 
 | ||||
|     if object_pairs_hook is None: | ||||
|         ctx.user.has_pairs_hook = False | ||||
|     else: | ||||
|         if not PyCallable_Check(object_pairs_hook): | ||||
|             raise TypeError("object_pairs_hook must be a callable.") | ||||
|         ctx.user.object_hook = <PyObject*>object_pairs_hook | ||||
|         ctx.user.has_pairs_hook = True | ||||
| 
 | ||||
|     if list_hook is not None: | ||||
|         if not PyCallable_Check(list_hook): | ||||
|             raise TypeError("list_hook must be a callable.") | ||||
|         ctx.user.list_hook = <PyObject*>list_hook | ||||
| 
 | ||||
|     if encoding is None: | ||||
|         ctx.user.encoding = NULL | ||||
|         ctx.user.unicode_errors = NULL | ||||
|  | @ -240,7 +255,7 @@ cdef inline init_ctx(template_context *ctx, object object_hook, object list_hook | |||
|             _berrors = unicode_errors | ||||
|         ctx.user.unicode_errors = PyBytes_AsString(_berrors) | ||||
| 
 | ||||
| def unpackb(object packed, object object_hook=None, object list_hook=None, | ||||
| def unpackb(object packed, object object_hook=None, object object_pairs_hook=None, object list_hook=None, | ||||
|             bint use_list=0, encoding=None, unicode_errors="strict", | ||||
|             ): | ||||
|     """Unpack packed_bytes to object. Returns an unpacked object. | ||||
|  | @ -255,7 +270,7 @@ def unpackb(object packed, object object_hook=None, object list_hook=None, | |||
|     cdef Py_ssize_t buf_len | ||||
|     PyObject_AsReadBuffer(packed, <const_void_ptr*>&buf, &buf_len) | ||||
| 
 | ||||
|     init_ctx(&ctx, object_hook, list_hook, use_list, encoding, unicode_errors) | ||||
|     init_ctx(&ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors) | ||||
|     ret = template_execute(&ctx, buf, buf_len, &off, 1) | ||||
|     if ret == 1: | ||||
|         obj = template_data(&ctx) | ||||
|  | @ -266,7 +281,7 @@ def unpackb(object packed, object object_hook=None, object list_hook=None, | |||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| def unpack(object stream, object object_hook=None, object list_hook=None, | ||||
| def unpack(object stream, object object_hook=None, object object_pairs_hook=None, object list_hook=None, | ||||
|            bint use_list=0, encoding=None, unicode_errors="strict", | ||||
|            ): | ||||
|     """Unpack an object from `stream`. | ||||
|  | @ -274,7 +289,7 @@ def unpack(object stream, object object_hook=None, object list_hook=None, | |||
|     Raises `ValueError` when `stream` has extra bytes. | ||||
|     """ | ||||
|     return unpackb(stream.read(), use_list=use_list, | ||||
|                    object_hook=object_hook, list_hook=list_hook, | ||||
|                    object_hook=object_hook, object_pairs_hook=object_pairs_hook, list_hook=list_hook, | ||||
|                    encoding=encoding, unicode_errors=unicode_errors, | ||||
|                    ) | ||||
| 
 | ||||
|  | @ -294,7 +309,10 @@ cdef class Unpacker(object): | |||
|     Otherwise, it is deserialized to Python tuple. (default: False) | ||||
| 
 | ||||
|     `object_hook` is same to simplejson. If it is not None, it should be callable | ||||
|     and Unpacker calls it when deserializing key-value. | ||||
|     and Unpacker calls it with a dict argument after deserializing a map. | ||||
| 
 | ||||
|     `object_pairs_hook` is same to simplejson. If it is not None, it should be callable | ||||
|     and Unpacker calls it with a list of key-value pairs after deserializing a map. | ||||
| 
 | ||||
|     `encoding` is encoding used for decoding msgpack bytes. If it is None (default), | ||||
|     msgpack bytes is deserialized to Python bytes. | ||||
|  | @ -345,7 +363,7 @@ cdef class Unpacker(object): | |||
|         self.buf = NULL | ||||
| 
 | ||||
|     def __init__(self, file_like=None, Py_ssize_t read_size=0, bint use_list=0, | ||||
|                  object object_hook=None, object list_hook=None, | ||||
|                  object object_hook=None, object object_pairs_hook=None, object list_hook=None, | ||||
|                  encoding=None, unicode_errors='strict', int max_buffer_size=0, | ||||
|                  ): | ||||
|         self.use_list = use_list | ||||
|  | @ -368,7 +386,7 @@ cdef class Unpacker(object): | |||
|         self.buf_size = read_size | ||||
|         self.buf_head = 0 | ||||
|         self.buf_tail = 0 | ||||
|         init_ctx(&self.ctx, object_hook, list_hook, use_list, encoding, unicode_errors) | ||||
|         init_ctx(&self.ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors) | ||||
| 
 | ||||
|     def feed(self, object next_bytes): | ||||
|         cdef char* buf | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ | |||
| typedef struct unpack_user { | ||||
|     int use_list; | ||||
|     PyObject *object_hook; | ||||
|     bool has_pairs_hook; | ||||
|     PyObject *list_hook; | ||||
|     const char *encoding; | ||||
|     const char *unicode_errors; | ||||
|  | @ -160,9 +161,7 @@ static inline int template_callback_array_item(unpack_user* u, unsigned int curr | |||
| static inline int template_callback_array_end(unpack_user* u, msgpack_unpack_object* c) | ||||
| { | ||||
|     if (u->list_hook) { | ||||
|         PyObject *arglist = Py_BuildValue("(O)", *c); | ||||
|         PyObject *new_c = PyEval_CallObject(u->list_hook, arglist); | ||||
|         Py_DECREF(arglist); | ||||
|         PyObject *new_c = PyEval_CallFunction(u->list_hook, "(O)", *c); | ||||
|         Py_DECREF(*c); | ||||
|         *c = new_c; | ||||
|     } | ||||
|  | @ -171,16 +170,31 @@ static inline int template_callback_array_end(unpack_user* u, msgpack_unpack_obj | |||
| 
 | ||||
| static inline int template_callback_map(unpack_user* u, unsigned int n, msgpack_unpack_object* o) | ||||
| { | ||||
|     PyObject *p = PyDict_New(); | ||||
|     PyObject *p; | ||||
|     if (u->has_pairs_hook) { | ||||
|         p = PyList_New(n); // Or use tuple?
 | ||||
|     } | ||||
|     else { | ||||
|         p = PyDict_New(); | ||||
|     } | ||||
|     if (!p) | ||||
|         return -1; | ||||
|     *o = p; | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static inline int template_callback_map_item(unpack_user* u, msgpack_unpack_object* c, msgpack_unpack_object k, msgpack_unpack_object v) | ||||
| 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) | ||||
| { | ||||
|     if (PyDict_SetItem(*c, k, v) == 0) { | ||||
|     if (u->has_pairs_hook) { | ||||
|         msgpack_unpack_object item = PyTuple_Pack(2, k, v); | ||||
|         if (!item) | ||||
|             return -1; | ||||
|         Py_DECREF(k); | ||||
|         Py_DECREF(v); | ||||
|         PyList_SET_ITEM(*c, current, item); | ||||
|         return 0; | ||||
|     } | ||||
|     else if (PyDict_SetItem(*c, k, v) == 0) { | ||||
|         Py_DECREF(k); | ||||
|         Py_DECREF(v); | ||||
|         return 0; | ||||
|  | @ -191,9 +205,7 @@ static inline int template_callback_map_item(unpack_user* u, msgpack_unpack_obje | |||
| static inline int template_callback_map_end(unpack_user* u, msgpack_unpack_object* c) | ||||
| { | ||||
|     if (u->object_hook) { | ||||
|         PyObject *arglist = Py_BuildValue("(O)", *c); | ||||
|         PyObject *new_c = PyEval_CallObject(u->object_hook, arglist); | ||||
|         Py_DECREF(arglist); | ||||
|         PyObject *new_c = PyEval_CallFunction(u->object_hook, "(O)", *c); | ||||
|         Py_DECREF(*c); | ||||
|         *c = new_c; | ||||
|     } | ||||
|  |  | |||
|  | @ -357,7 +357,7 @@ _push: | |||
| 		c->ct = CT_MAP_VALUE; | ||||
| 		goto _header_again; | ||||
| 	case CT_MAP_VALUE: | ||||
| 		if(construct_cb(_map_item)(user, &c->obj, c->map_key, obj) < 0) { goto _failed; } | ||||
| 		if(construct_cb(_map_item)(user, c->count, &c->obj, c->map_key, obj) < 0) { goto _failed; } | ||||
| 		if(++c->count == c->size) { | ||||
| 			obj = c->obj; | ||||
| 			construct_cb(_map_end)(user, &obj); | ||||
|  |  | |||
|  | @ -26,6 +26,16 @@ def test_decode_hook(): | |||
|     unpacked = unpackb(packed, object_hook=_decode_complex) | ||||
|     eq_(unpacked[1], 1+2j) | ||||
| 
 | ||||
| def test_decode_pairs_hook(): | ||||
|     packed = packb([3, {1: 2, 3: 4}]) | ||||
|     prod_sum = 1 * 2 + 3 * 4 | ||||
|     unpacked = unpackb(packed, object_pairs_hook=lambda l: sum(k * v for k, v in l)) | ||||
|     eq_(unpacked[1], prod_sum) | ||||
| 
 | ||||
| @raises(ValueError) | ||||
| def test_only_one_obj_hook(): | ||||
|     unpackb('', object_hook=lambda x: x, object_pairs_hook=lambda x: x) | ||||
| 
 | ||||
| @raises(ValueError) | ||||
| def test_bad_hook(): | ||||
|     packed = packb([3, 1+2j], default=lambda o: o) | ||||
|  |  | |||
|  | @ -111,10 +111,9 @@ def test_odict(): | |||
|     seq = [(b'one', 1), (b'two', 2), (b'three', 3), (b'four', 4)] | ||||
|     od = odict(seq) | ||||
|     assert_equal(unpackb(packb(od)), dict(seq)) | ||||
|     # After object_pairs_hook is implemented. | ||||
|     #def pair_hook(seq): | ||||
|     #    return seq | ||||
|     #assert_equal(unpackb(packb(od), object_pairs_hook=pair_hook), seq) | ||||
|     def pair_hook(seq): | ||||
|         return seq | ||||
|     assert_equal(unpackb(packb(od), object_pairs_hook=pair_hook), seq) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Joel Nothman
						Joel Nothman