diff --git a/msgpack/_packer.pyx b/msgpack/_packer.pyx index ca6cf983..52f4f1f2 100644 --- a/msgpack/_packer.pyx +++ b/msgpack/_packer.pyx @@ -4,6 +4,7 @@ from cpython.datetime cimport ( PyDateTime_CheckExact, PyDelta_CheckExact, datetime_tzinfo, timedelta_days, timedelta_seconds, timedelta_microseconds, ) +from cpython.frozendict cimport PyAnyDict_Check, PyAnyDict_CheckExact cdef ExtType cdef Timestamp @@ -202,7 +203,7 @@ cdef class Packer: rawval = o msgpack_pack_raw(&self.pk, L) msgpack_pack_raw_body(&self.pk, rawval, L) - elif PyDict_CheckExact(o) if strict else PyDict_Check(o): + elif PyAnyDict_CheckExact(o) if strict else PyAnyDict_Check(o): L = len(o) if L > ITEM_LIMIT: raise ValueError("dict is too large") diff --git a/msgpack/fallback.py b/msgpack/fallback.py index e219786e..9cc0320e 100644 --- a/msgpack/fallback.py +++ b/msgpack/fallback.py @@ -37,6 +37,17 @@ def newlist_hint(size): return [] +# frozendict is a builtin type added in Python 3.15 (PEP 814). +if sys.version_info >= (3, 15): + import builtins + + frozendict = builtins.frozendict +else: + + class frozendict: + pass + + from .exceptions import BufferFull, ExtraData, FormatError, OutOfData, StackError from .ext import ExtType, Timestamp @@ -695,6 +706,7 @@ def _pack( list_types = list else: list_types = (list, tuple) + dict_types = (dict, frozendict) while True: if nest_limit < 0: raise ValueError("recursion limit exceeded") @@ -788,7 +800,7 @@ def _pack( for i in range(n): self._pack(obj[i], nest_limit - 1) return - if check(obj, dict): + if check(obj, dict_types): return self._pack_map_pairs(len(obj), obj.items(), nest_limit - 1) if self._datetime and check(obj, _DateTime) and obj.tzinfo is not None: diff --git a/pyproject.toml b/pyproject.toml index ebc6b50d..aee0d5ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,6 @@ lint.select = [ [dependency-groups] dev = [ - "cython>=3.2.5", + "cython>=3.3.0", "pytest>=9.0.3", ] diff --git a/requirements.txt b/requirements.txt index 7991e3f0..5e3122f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cython==3.2.5 +cython==3.3.0 setuptools==78.1.1 pytest build diff --git a/test/test_pack.py b/test/test_pack.py index f44bd557..9ca6e182 100644 --- a/test/test_pack.py +++ b/test/test_pack.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import struct +import sys from collections import OrderedDict from io import BytesIO @@ -162,6 +163,25 @@ def pair_hook(seq): assert unpackb(packb(od), object_pairs_hook=pair_hook, use_list=1) == seq +@pytest.mark.skipif(sys.version_info < (3, 15), reason="frozendict requires Python 3.15+") +def test_frozendict(): + import builtins + + fd = builtins.frozendict(a=1, b=[1, 2, 3]) + unpacked = unpackb(packb(fd), use_list=1) + assert isinstance(unpacked, dict) + assert unpacked == fd + + unpacked = unpackb(packb(fd), use_list=1, object_pairs_hook=builtins.frozendict) + assert isinstance(unpacked, builtins.frozendict) + assert unpacked == fd + + packer = Packer(strict_types=True) + unpacked = unpackb(packer.pack(fd), use_list=1) + assert isinstance(unpacked, dict) + assert unpacked == fd + + def test_pairlist(): pairlist = [(b"a", 1), (2, b"b"), (b"foo", b"bar")] packer = Packer()