Skip to content

Commit 7a63920

Browse files
Support packing Python 3.15 frozentdict (PEP 814) (#738)
Support packing a Python 3.15 `frozendict`. This PR doesn't support _unpacking_ into a `frozendict` other than with the generic `object_pairs_hook=frozendict`. --------- Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 9f4909c commit 7a63920

5 files changed

Lines changed: 37 additions & 4 deletions

File tree

msgpack/_packer.pyx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from cpython.datetime cimport (
44
PyDateTime_CheckExact, PyDelta_CheckExact,
55
datetime_tzinfo, timedelta_days, timedelta_seconds, timedelta_microseconds,
66
)
7+
from cpython.frozendict cimport PyAnyDict_Check, PyAnyDict_CheckExact
78

89
cdef ExtType
910
cdef Timestamp
@@ -202,7 +203,7 @@ cdef class Packer:
202203
rawval = o
203204
msgpack_pack_raw(&self.pk, L)
204205
msgpack_pack_raw_body(&self.pk, rawval, L)
205-
elif PyDict_CheckExact(o) if strict else PyDict_Check(o):
206+
elif PyAnyDict_CheckExact(o) if strict else PyAnyDict_Check(o):
206207
L = len(o)
207208
if L > ITEM_LIMIT:
208209
raise ValueError("dict is too large")

msgpack/fallback.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ def newlist_hint(size):
3737
return []
3838

3939

40+
# frozendict is a builtin type added in Python 3.15 (PEP 814).
41+
if sys.version_info >= (3, 15):
42+
import builtins
43+
44+
frozendict = builtins.frozendict
45+
else:
46+
47+
class frozendict:
48+
pass
49+
50+
4051
from .exceptions import BufferFull, ExtraData, FormatError, OutOfData, StackError
4152
from .ext import ExtType, Timestamp
4253

@@ -695,6 +706,7 @@ def _pack(
695706
list_types = list
696707
else:
697708
list_types = (list, tuple)
709+
dict_types = (dict, frozendict)
698710
while True:
699711
if nest_limit < 0:
700712
raise ValueError("recursion limit exceeded")
@@ -788,7 +800,7 @@ def _pack(
788800
for i in range(n):
789801
self._pack(obj[i], nest_limit - 1)
790802
return
791-
if check(obj, dict):
803+
if check(obj, dict_types):
792804
return self._pack_map_pairs(len(obj), obj.items(), nest_limit - 1)
793805

794806
if self._datetime and check(obj, _DateTime) and obj.tzinfo is not None:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ lint.select = [
4747

4848
[dependency-groups]
4949
dev = [
50-
"cython>=3.2.5",
50+
"cython>=3.3.0",
5151
"pytest>=9.0.3",
5252
]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cython==3.2.5
1+
cython==3.3.0
22
setuptools==78.1.1
33
pytest
44
build

test/test_pack.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22

33
import struct
4+
import sys
45
from collections import OrderedDict
56
from io import BytesIO
67

@@ -162,6 +163,25 @@ def pair_hook(seq):
162163
assert unpackb(packb(od), object_pairs_hook=pair_hook, use_list=1) == seq
163164

164165

166+
@pytest.mark.skipif(sys.version_info < (3, 15), reason="frozendict requires Python 3.15+")
167+
def test_frozendict():
168+
import builtins
169+
170+
fd = builtins.frozendict(a=1, b=[1, 2, 3])
171+
unpacked = unpackb(packb(fd), use_list=1)
172+
assert isinstance(unpacked, dict)
173+
assert unpacked == fd
174+
175+
unpacked = unpackb(packb(fd), use_list=1, object_pairs_hook=builtins.frozendict)
176+
assert isinstance(unpacked, builtins.frozendict)
177+
assert unpacked == fd
178+
179+
packer = Packer(strict_types=True)
180+
unpacked = unpackb(packer.pack(fd), use_list=1)
181+
assert isinstance(unpacked, dict)
182+
assert unpacked == fd
183+
184+
165185
def test_pairlist():
166186
pairlist = [(b"a", 1), (2, b"b"), (b"foo", b"bar")]
167187
packer = Packer()

0 commit comments

Comments
 (0)