@@ -40,20 +40,22 @@ cdef extern from "unpack.h":
4040 Py_ssize_t max_map_len
4141 Py_ssize_t max_ext_len
4242
43+ ctypedef struct unpack_context
44+ ctypedef int (* execute_fn)(unpack_context* ctx, const char * data,
45+ Py_ssize_t len , Py_ssize_t* off) except - 1
46+
4347 ctypedef struct unpack_context:
4448 msgpack_user user
45- PyObject* obj
46- Py_ssize_t count
49+ execute_fn execute
4750
48- ctypedef int (* execute_fn)(unpack_context* ctx, const char * data,
49- Py_ssize_t len , Py_ssize_t* off) except - 1
5051 execute_fn unpack_construct
5152 execute_fn unpack_skip
5253 execute_fn read_array_header
5354 execute_fn read_map_header
5455
5556 void unpack_init(unpack_context* ctx)
56- object unpack_data(unpack_context* ctx)
57+ # Transfers the result's owned reference to Cython and clears its slot.
58+ object unpack_take_result(unpack_context* ctx)
5759 void unpack_clear(unpack_context* ctx)
5860
5961cdef inline init_ctx(unpack_context * ctx,
@@ -191,16 +193,16 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None,
191193 max_str_len, max_bin_len, max_array_len, max_map_len, max_ext_len)
192194 ret = unpack_construct(& ctx, buf, buf_len, & off)
193195 if ret == 1 :
194- obj = unpack_data (& ctx)
196+ obj = unpack_take_result (& ctx)
195197 if off < buf_len:
196198 # buf may point into a temporary contiguous copy owned by view,
197199 # so the extra data must be copied out before releasing view.
198200 raise ExtraData(obj, PyBytes_FromStringAndSize(buf+ off, buf_len- off))
199201 return obj
200202 finally :
203+ unpack_clear(& ctx)
201204 PyBuffer_Release(& view);
202205
203- unpack_clear(& ctx)
204206 if ret == 0 :
205207 raise ValueError (" Unpack failed: incomplete input" )
206208 elif ret == - 2 :
@@ -214,6 +216,11 @@ def unpackb(object packed, *, object object_hook=None, object list_hook=None,
214216cdef class Unpacker:
215217 """ Streaming unpacker.
216218
219+ If an operation needs more data, resume it with the same method after
220+ feeding more bytes. ``unpack()`` and iteration may be used interchangeably.
221+ Switching between unpacking, skipping, header readers, or ``read_bytes()``
222+ while an object is incomplete raises ``ValueError`` in the C extension.
223+
217224 Arguments:
218225
219226 :param file_like:
@@ -339,6 +346,9 @@ cdef class Unpacker:
339346 Py_ssize_t max_ext_len = - 1 ):
340347 cdef const char * cerr= NULL
341348
349+ if self ._unpacking:
350+ raise RuntimeError (" Unpacker.__init__() cannot be called while unpacking is in progress" )
351+
342352 unpack_clear(& self .ctx)
343353 unpack_init(& self .ctx)
344354 if self .buf != NULL :
@@ -473,18 +483,29 @@ cdef class Unpacker:
473483 cdef object obj
474484 cdef Py_ssize_t prev_head
475485
486+ if self ._unpacking:
487+ raise RuntimeError (" Unpacker cannot be called recursively while unpacking is in progress" )
488+ if self .ctx.execute != NULL and self .ctx.execute != execute:
489+ raise ValueError (" Cannot switch unpacking methods while an object is incomplete" )
490+
476491 self ._unpacking = True
477492 try :
478493 while 1 :
479494 prev_head = self .buf_head
480495 if prev_head < self .buf_tail:
481- ret = execute(& self .ctx, self .buf, self .buf_tail, & self .buf_head)
482- self .stream_offset += self .buf_head - prev_head
496+ self .ctx.execute = execute
497+ try :
498+ ret = execute(& self .ctx, self .buf, self .buf_tail, & self .buf_head)
499+ except :
500+ unpack_clear(& self .ctx)
501+ raise
502+ finally :
503+ self .stream_offset += self .buf_head - prev_head
483504 else :
484505 ret = 0
485506
486507 if ret == 1 :
487- obj = unpack_data (& self .ctx)
508+ obj = unpack_take_result (& self .ctx)
488509 unpack_init(& self .ctx)
489510 return obj
490511 if ret == 0 :
@@ -510,6 +531,10 @@ cdef class Unpacker:
510531 def read_bytes (self , Py_ssize_t nbytes ):
511532 """ Read a specified number of raw bytes from the stream"""
512533 cdef Py_ssize_t nread
534+ if self ._unpacking:
535+ raise RuntimeError (" Unpacker.read_bytes() cannot be called while unpacking is in progress" )
536+ if self .ctx.execute != NULL :
537+ raise ValueError (" Cannot switch unpacking methods while an object is incomplete" )
513538 nread = min (self .buf_tail - self .buf_head, nbytes)
514539 ret = PyBytes_FromStringAndSize(self .buf + self .buf_head, nread)
515540 self .buf_head += nread
0 commit comments