From bdc34d273f3ef71cd0aad9808b1d0cca7505d195 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:03:29 +0200 Subject: [PATCH] Support frozendict in plistlib --- Doc/library/plistlib.rst | 6 +++--- Lib/plistlib.py | 20 ++++++++++---------- Lib/test/test_plistlib.py | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Doc/library/plistlib.rst b/Doc/library/plistlib.rst index e5fc19f495226e..dd1ba11da5d301 100644 --- a/Doc/library/plistlib.rst +++ b/Doc/library/plistlib.rst @@ -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 frozen dictionary. To write out and to parse a plist file, use the :func:`dump` and :func:`load` functions. @@ -183,12 +183,12 @@ Generating a plist:: import datetime import plistlib - pl = dict( + pl = frozendict( aString = "Doodah", aList = ["A", "B", 12, 32.1, [1, 2, 3]], aFloat = 0.1, anInt = 728, - aDict = dict( + aDict = frozendict( anotherString = "", aThirdString = "M\xe4ssig, Ma\xdf", aTrueValue = True, diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 3c6a6b7bdc44d2..88b1e8e0507587 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -2,7 +2,7 @@ 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 frozen dictionary. To write out a plist file, use the dump(value, file) function. 'value' is the top level object, 'file' is @@ -24,12 +24,12 @@ import datetime import plistlib - pl = dict( + pl = frozendict( aString = "Doodah", aList = ["A", "B", 12, 32.1, [1, 2, 3]], aFloat = 0.1, anInt = 728, - aDict = dict( + aDict = frozendict( anotherString = "", aThirdString = "M\xe4ssig, Ma\xdf", aTrueValue = True, @@ -357,7 +357,7 @@ def write_value(self, value): 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 @@ def _flatten(self, value): 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 @@ def _write_object(self, value): 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): diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index d9216be4d95658..b9c261310bb567 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -792,6 +792,25 @@ def test_dict_members(self): }) 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)