From 378bd3e50352a1adca932a9744ed0ae16cdc32ac Mon Sep 17 00:00:00 2001 From: 0z5a Date: Sun, 15 Nov 2026 13:39:52 +0100 Subject: [PATCH] Accept falsy file-like objects in the C Unpacker Unpacker.__init__ in the C extension used `if file_like:` before saving `file_like.read`. A stream that defines __bool__ or __len__ and is falsy (for example werkzeug's FileStorage without a filename) was stored as the input but its read method was not, so the first read failed with "TypeError: 'NoneType' object is not callable". The pure Python Unpacker checks `file_like is None` and works. Check `file_like is not None` instead and add a regression test. --- msgpack/_unpacker.pyx | 2 +- test/test_sequnpack.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index 3c81808f..44f83f09 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -360,7 +360,7 @@ cdef class Unpacker: self.ext_hook = ext_hook self.file_like = file_like - if file_like: + if file_like is not None: self.file_like_read = file_like.read if not PyCallable_Check(self.file_like_read): raise TypeError("`file_like.read` must be a callable.") diff --git a/test/test_sequnpack.py b/test/test_sequnpack.py index 904be42f..4588126b 100644 --- a/test/test_sequnpack.py +++ b/test/test_sequnpack.py @@ -87,6 +87,21 @@ def test_maxbuffersize_file(): print(unpacker.unpack()) +def test_falsy_file_like(): + # A file-like object may define __bool__ or __len__ and be falsy + # (e.g. a file wrapper without a name). Only `.read()` is required. + class FalsyBytesIO(io.BytesIO): + def __bool__(self): + return False + + stream = FalsyBytesIO(packb([1, 2]) + packb({"a": 3})) + assert list(Unpacker(stream)) == [[1, 2], {"a": 3}] + + unpacker = Unpacker(FalsyBytesIO(packb(1) + b"xy")) + assert unpacker.unpack() == 1 + assert unpacker.read_bytes(2) == b"xy" + + def test_readbytes(): unpacker = Unpacker(read_size=3) unpacker.feed(b"foobar")