gh-141510: Support frozendict in plistlib (#145590)

Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Hugo van Kemenade
2026-03-31 15:45:23 +03:00
committed by GitHub
parent 67354b2925
commit 829e4d0b14
5 changed files with 32 additions and 10 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ and XML plist files.
The property list (``.plist``) file format is a simple serialization supporting
basic object types, like dictionaries, lists, numbers and strings. Usually the
top level object is a dictionary.
top level object is a dictionary or a frozen dictionary.
To write out and to parse a plist file, use the :func:`dump` and
:func:`load` functions.
+2 -1
View File
@@ -217,7 +217,8 @@ For example::
The following standard library modules have been updated to accept
:class:`!frozendict`: :mod:`copy`, :mod:`decimal`, :mod:`json`, :mod:`marshal`,
:mod:`pickle`, :mod:`pprint` and :mod:`xml.etree.ElementTree`.
:mod:`plistlib` (only for serialization), :mod:`pickle`, :mod:`pprint` and
:mod:`xml.etree.ElementTree`.
:func:`eval` and :func:`exec` accept :class:`!frozendict` for *globals*, and
:func:`type` and :meth:`str.maketrans` accept :class:`!frozendict` for *dict*.
+8 -8
View File
@@ -2,7 +2,7 @@ r"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
The property list (.plist) file format is a simple XML pickle supporting
basic object types, like dictionaries, lists, numbers and strings.
Usually the top level object is a dictionary.
Usually the top level object is a dictionary or a frozen dictionary.
To write out a plist file, use the dump(value, file)
function. 'value' is the top level object, 'file' is
@@ -357,7 +357,7 @@ class _PlistWriter(_DumbXMLWriter):
elif isinstance(value, float):
self.simple_element("real", repr(value))
elif isinstance(value, dict):
elif isinstance(value, (dict, frozendict)):
self.write_dict(value)
elif isinstance(value, (bytes, bytearray)):
@@ -715,7 +715,7 @@ class _BinaryPlistWriter (object):
self._objidtable[id(value)] = refnum
# And finally recurse into containers
if isinstance(value, dict):
if isinstance(value, (dict, frozendict)):
keys = []
values = []
items = value.items()
@@ -836,7 +836,7 @@ class _BinaryPlistWriter (object):
self._write_size(0xA0, s)
self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
elif isinstance(value, dict):
elif isinstance(value, (dict, frozendict)):
keyRefs, valRefs = [], []
if self._sort_keys:
@@ -869,18 +869,18 @@ def _is_fmt_binary(header):
# Generic bits
#
_FORMATS={
FMT_XML: dict(
_FORMATS=frozendict({
FMT_XML: frozendict(
detect=_is_fmt_xml,
parser=_PlistParser,
writer=_PlistWriter,
),
FMT_BINARY: dict(
FMT_BINARY: frozendict(
detect=_is_fmt_binary,
parser=_BinaryPlistParser,
writer=_BinaryPlistWriter,
)
}
})
def load(fp, *, fmt=None, dict_type=dict, aware_datetime=False):
+19
View File
@@ -792,6 +792,25 @@ class TestPlistlib(unittest.TestCase):
})
self.assertIsNot(pl2['first'], pl2['second'])
def test_frozendict(self):
pl = frozendict(
aString="Doodah",
anInt=728,
aDict=frozendict(
anotherString="hello",
aTrueValue=True,
),
aList=["A", "B", 12],
)
for fmt in ALL_FORMATS:
with self.subTest(fmt=fmt):
data = plistlib.dumps(pl, fmt=fmt)
pl2 = plistlib.loads(data)
self.assertEqual(pl2, dict(pl))
self.assertIsInstance(pl2, dict)
self.assertIsInstance(pl2['aDict'], dict)
def test_controlcharacters(self):
for i in range(128):
c = chr(i)
@@ -0,0 +1,2 @@
Support :class:`frozendict` in :mod:`plistlib`, for serialization only.
Patch by Hugo van Kemenade.