summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml14
-rw-r--r--ChangeLog.rst3
-rw-r--r--README.rst39
-rw-r--r--msgpack/_msgpack.pyx95
-rw-r--r--msgpack/unpack.h5
-rw-r--r--msgpack/unpack_template.h4
-rw-r--r--setup.py3
-rw-r--r--test/test_obj.py21
-rw-r--r--test/test_unpack_raw.py34
9 files changed, 176 insertions, 42 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..11835cb
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: python
+python:
+ - 2.6
+ - 2.7
+ - 3.2
+
+install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -q cython
+ - cython --cplus msgpack/_msgpack.pyx
+ - pip install six --use-mirrors
+ - python setup.py install
+
+script: "nosetests -w test"
diff --git a/ChangeLog.rst b/ChangeLog.rst
index 6a4d27b..43ee723 100644
--- a/ChangeLog.rst
+++ b/ChangeLog.rst
@@ -5,7 +5,8 @@
Changes
-------
* Add ``.skip()`` method to ``Unpacker`` (thanks to jnothman)
-
+* Add capturing feature. You can pass the writable object to
+ ``Unpacker.unpack()`` as a second parameter.
0.2.3
=======
diff --git a/README.rst b/README.rst
index 1da6ece..8cebc01 100644
--- a/README.rst
+++ b/README.rst
@@ -6,6 +6,9 @@ MessagePack Python Binding
:version: 0.2.0
:date: 2012-06-27
+.. image:: https://secure.travis-ci.org/msgpack/msgpack-python.png
+ :target: https://travis-ci.org/#!/msgpack/msgpack-python
+
HOW TO USE
-----------
@@ -54,13 +57,45 @@ stream.
unpacker = msgpack.Unpacker()
while True:
- data = buf.read(4)
+ data = buf.read(16)
if not data:
break
- unpacker.seed(buf.read(16))
+ unpacker.feed(data)
+
for unpacked in unpacker:
print unpacked
+packing/unpacking of custom data type
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Also possible to pack/unpack user's data types. Here is an example for
+``datetime.datetime``.
+
+::
+
+ import datetime
+
+ import msgpack
+
+ useful_dict = {
+ "id": 1,
+ "created": datetime.datetime.now(),
+ }
+
+ def decode_datetime(obj):
+ if b'__datetime__' in obj:
+ obj = datetime.datetime.strptime(obj["as_str"], "%Y%m%dT%H:%M:%S.%f")
+ return obj
+
+ def encode_datetime(obj):
+ if isinstance(obj, datetime.datetime):
+ return {'__datetime__': True, 'as_str': obj.strftime("%Y%m%dT%H:%M:%S.%f")}
+ return obj
+
+
+ packed_dict = msgpack.packb(useful_dict, default=encode_datetime)
+ this_dict_again = msgpack.unpackb(packed_dict, object_hook=decode_datetime)
+
INSTALL
---------
diff --git a/msgpack/_msgpack.pyx b/msgpack/_msgpack.pyx
index 728e4f3..38940f0 100644
--- a/msgpack/_msgpack.pyx
+++ b/msgpack/_msgpack.pyx
@@ -1,8 +1,6 @@
# coding: utf-8
#cython: embedsignature=True
-import warnings
-
from cpython cimport *
cdef extern from "Python.h":
ctypedef char* const_char_ptr "const char*"
@@ -233,7 +231,9 @@ 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 object_pairs_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, char* encoding, char* unicode_errors):
template_init(ctx)
ctx.user.use_list = use_list
ctx.user.object_hook = ctx.user.list_hook = <PyObject*>NULL
@@ -259,20 +259,8 @@ cdef inline init_ctx(template_context *ctx, object object_hook, object object_pa
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
- else:
- if isinstance(encoding, unicode):
- _bencoding = encoding.encode('ascii')
- else:
- _bencoding = encoding
- ctx.user.encoding = PyBytes_AsString(_bencoding)
- if isinstance(unicode_errors, unicode):
- _berrors = unicode_errors.encode('ascii')
- else:
- _berrors = unicode_errors
- ctx.user.unicode_errors = PyBytes_AsString(_berrors)
+ ctx.user.encoding = encoding
+ ctx.user.unicode_errors = unicode_errors
def unpackb(object packed, object object_hook=None, object list_hook=None,
bint use_list=1, encoding=None, unicode_errors="strict",
@@ -288,10 +276,22 @@ def unpackb(object packed, object object_hook=None, object list_hook=None,
cdef char* buf
cdef Py_ssize_t buf_len
+ cdef char* cenc = NULL
+ cdef char* cerr = NULL
PyObject_AsReadBuffer(packed, <const_void_ptr*>&buf, &buf_len)
- init_ctx(&ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors)
+ if encoding is not None:
+ if isinstance(encoding, unicode):
+ encoding = encoding.encode('ascii')
+ cenc = PyBytes_AsString(encoding)
+
+ if unicode_errors is not None:
+ if isinstance(unicode_errors, unicode):
+ unicode_errors = unicode_errors.encode('ascii')
+ cerr = PyBytes_AsString(unicode_errors)
+
+ init_ctx(&ctx, object_hook, object_pairs_hook, list_hook, use_list, cenc, cerr)
ret = template_construct(&ctx, buf, buf_len, &off)
if ret == 1:
obj = template_data(&ctx)
@@ -370,10 +370,7 @@ cdef class Unpacker(object):
cdef object file_like_read
cdef Py_ssize_t read_size
cdef object object_hook
- cdef object _bencoding
- cdef object _berrors
- cdef char *encoding
- cdef char *unicode_errors
+ cdef object encoding, unicode_errors
cdef size_t max_buffer_size
def __cinit__(self):
@@ -387,6 +384,8 @@ cdef class Unpacker(object):
object object_hook=None, object object_pairs_hook=None, object list_hook=None,
encoding=None, unicode_errors='strict', int max_buffer_size=0,
):
+ cdef char *cenc=NULL, *cerr=NULL
+
self.file_like = file_like
if file_like:
self.file_like_read = file_like.read
@@ -406,7 +405,20 @@ cdef class Unpacker(object):
self.buf_size = read_size
self.buf_head = 0
self.buf_tail = 0
- init_ctx(&self.ctx, object_hook, object_pairs_hook, list_hook, use_list, encoding, unicode_errors)
+
+ if encoding is not None:
+ if isinstance(encoding, unicode):
+ encoding = encoding.encode('ascii')
+ self.encoding = encoding
+ cenc = PyBytes_AsString(encoding)
+
+ if unicode_errors is not None:
+ if isinstance(unicode_errors, unicode):
+ unicode_errors = unicode_errors.encode('ascii')
+ self.unicode_errors = unicode_errors
+ cerr = PyBytes_AsString(unicode_errors)
+
+ init_ctx(&self.ctx, object_hook, object_pairs_hook, list_hook, use_list, cenc, cerr)
def feed(self, object next_bytes):
cdef char* buf
@@ -467,11 +479,16 @@ cdef class Unpacker(object):
else:
self.file_like = None
- cdef object _unpack(self, execute_fn execute):
+ cdef object _unpack(self, execute_fn execute, object write_bytes):
cdef int ret
cdef object obj
+ cdef size_t prev_head
while 1:
+ prev_head = self.buf_head
ret = execute(&self.ctx, self.buf, self.buf_tail, &self.buf_head)
+ if write_bytes is not None:
+ write_bytes(PyBytes_FromStringAndSize(self.buf + prev_head, self.buf_head - prev_head))
+
if ret == 1:
obj = template_data(&self.ctx)
template_init(&self.ctx)
@@ -494,27 +511,35 @@ cdef class Unpacker(object):
ret += self.file_like.read(nbytes - len(ret))
return ret
- def unpack(self):
- """unpack one object"""
- return self._unpack(template_construct)
+ def unpack(self, object write_bytes=None):
+ """
+ unpack one object
+
+ If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
+ """
+ return self._unpack(template_construct, write_bytes)
+
+ def skip(self, object write_bytes=None):
+ """
+ read and ignore one object, returning None
- def skip(self):
- """read and ignore one object, returning None"""
- return self._unpack(template_skip)
+ If write_bytes is not None, it will be called with parts of the raw message as it is unpacked.
+ """
+ return self._unpack(template_skip, write_bytes)
- def read_array_header(self):
+ def read_array_header(self, object write_bytes=None):
"""assuming the next object is an array, return its size n, such that the next n unpack() calls will iterate over its contents."""
- return self._unpack(read_array_header)
+ return self._unpack(read_array_header, write_bytes)
- def read_map_header(self):
+ def read_map_header(self, object write_bytes=None):
"""assuming the next object is a map, return its size n, such that the next n * 2 unpack() calls will iterate over its key-value pairs."""
- return self._unpack(read_map_header)
+ return self._unpack(read_map_header, write_bytes)
def __iter__(self):
return self
def __next__(self):
- return self._unpack(template_construct)
+ return self._unpack(template_construct, None)
# for debug.
#def _buf(self):
diff --git a/msgpack/unpack.h b/msgpack/unpack.h
index 5ec7dbc..3dc88e5 100644
--- a/msgpack/unpack.h
+++ b/msgpack/unpack.h
@@ -163,6 +163,8 @@ static inline int template_callback_array_end(unpack_user* u, msgpack_unpack_obj
{
if (u->list_hook) {
PyObject *new_c = PyEval_CallFunction(u->list_hook, "(O)", *c);
+ if (!new_c)
+ return -1;
Py_DECREF(*c);
*c = new_c;
}
@@ -207,6 +209,9 @@ static inline int template_callback_map_end(unpack_user* u, msgpack_unpack_objec
{
if (u->object_hook) {
PyObject *new_c = PyEval_CallFunction(u->object_hook, "(O)", *c);
+ if (!new_c)
+ return -1;
+
Py_DECREF(*c);
*c = new_c;
}
diff --git a/msgpack/unpack_template.h b/msgpack/unpack_template.h
index 7d07601..8a57f0d 100644
--- a/msgpack/unpack_template.h
+++ b/msgpack/unpack_template.h
@@ -347,7 +347,7 @@ _push:
if(construct_cb(_array_item)(user, c->count, &c->obj, obj) < 0) { goto _failed; }
if(++c->count == c->size) {
obj = c->obj;
- construct_cb(_array_end)(user, &obj);
+ if (construct_cb(_array_end)(user, &obj) < 0) { goto _failed; }
--top;
/*printf("stack pop %d\n", top);*/
goto _push;
@@ -361,7 +361,7 @@ _push:
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);
+ if (construct_cb(_map_end)(user, &obj) < 0) { goto _failed; }
--top;
/*printf("stack pop %d\n", top);*/
goto _push;
diff --git a/setup.py b/setup.py
index 5be92bf..b9af8c3 100644
--- a/setup.py
+++ b/setup.py
@@ -34,8 +34,7 @@ Install Cython >= 0.16 or install msgpack from PyPI.
os.stat(src).st_mtime < os.stat(pyx).st_mtime and
have_cython):
cythonize(pyx)
- else:
- return src
+ return src
class BuildExt(build_ext):
diff --git a/test/test_obj.py b/test/test_obj.py
index 881e627..1d9024b 100644
--- a/test/test_obj.py
+++ b/test/test_obj.py
@@ -49,5 +49,26 @@ def test_array_hook():
unpacked = unpackb(packed, list_hook=_arr_to_str, use_list=1)
eq_(unpacked, '123')
+
+class DecodeError(Exception):
+ pass
+
+def bad_complex_decoder(o):
+ raise DecodeError("Ooops!")
+
+
+@raises(DecodeError)
+def test_an_exception_in_objecthook1():
+ packed = packb({1: {'__complex__': True, 'real': 1, 'imag': 2}})
+ unpackb(packed, object_hook=bad_complex_decoder)
+
+
+@raises(DecodeError)
+def test_an_exception_in_objecthook2():
+ packed = packb({1: [{'__complex__': True, 'real': 1, 'imag': 2}]})
+ unpackb(packed, list_hook=bad_complex_decoder, use_list=1)
+
+
+
if __name__ == '__main__':
main()
diff --git a/test/test_unpack_raw.py b/test/test_unpack_raw.py
new file mode 100644
index 0000000..15d9c93
--- /dev/null
+++ b/test/test_unpack_raw.py
@@ -0,0 +1,34 @@
+"""Tests for cases where the user seeks to obtain packed msgpack objects"""
+
+from nose import main
+from nose.tools import *
+import six
+from msgpack import Unpacker, packb
+
+
+def test_write_bytes():
+ unpacker = Unpacker()
+ unpacker.feed(b'abc')
+ f = six.BytesIO()
+ assert_equal(unpacker.unpack(f.write), ord('a'))
+ assert_equal(f.getvalue(), b'a')
+ f = six.BytesIO()
+ assert unpacker.skip(f.write) is None
+ assert_equal(f.getvalue(), b'b')
+ f = six.BytesIO()
+ assert unpacker.skip() is None
+ assert_equal(f.getvalue(), b'')
+
+
+def test_write_bytes_multi_buffer():
+ long_val = (5) * 100
+ expected = packb(long_val)
+ unpacker = Unpacker(six.BytesIO(expected), read_size=3, max_buffer_size=3)
+
+ f = six.BytesIO()
+ unpacked = unpacker.unpack(f.write)
+ assert_equal(unpacked, long_val)
+ assert_equal(f.getvalue(), expected)
+
+if __name__ == '__main__':
+ main()