From bbf214df23be3ee5daead119e8a2506d810d7d1f Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 6 Jan 2024 17:26:59 +0800 Subject: [PATCH] gh-113537: support loads str in plistlib.loads (#113582) Add support for loading XML plists from a string value instead of a only bytes value. --- Doc/library/plistlib.rst | 8 +++++--- Lib/plistlib.py | 5 +++++ Lib/test/test_plistlib.py | 13 +++++++++++++ .../2023-12-30-20-30-05.gh-issue-113537.v1W5_X.rst | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-30-20-30-05.gh-issue-113537.v1W5_X.rst diff --git a/Doc/library/plistlib.rst b/Doc/library/plistlib.rst index 10f1a48fc70a72..7416ca2650bab4 100644 --- a/Doc/library/plistlib.rst +++ b/Doc/library/plistlib.rst @@ -27,7 +27,7 @@ top level object is a dictionary. To write out and to parse a plist file, use the :func:`dump` and :func:`load` functions. -To work with plist data in bytes objects, use :func:`dumps` +To work with plist data in bytes or string objects, use :func:`dumps` and :func:`loads`. Values can be strings, integers, floats, booleans, tuples, lists, dictionaries @@ -89,11 +89,13 @@ This module defines the following functions: .. function:: loads(data, *, fmt=None, dict_type=dict, aware_datetime=False) - Load a plist from a bytes object. See :func:`load` for an explanation of - the keyword arguments. + Load a plist from a bytes or string object. See :func:`load` for an + explanation of the keyword arguments. .. versionadded:: 3.4 + .. versionchanged:: 3.13 + *data* can be a string when *fmt* equals :data:`FMT_XML`. .. function:: dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False, aware_datetime=False) diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 0fc1b5cbfa8c49..188a0b399b587b 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -906,6 +906,11 @@ def loads(value, *, fmt=None, dict_type=dict, aware_datetime=False): """Read a .plist file from a bytes object. Return the unpacked root object (which usually is a dictionary). """ + if isinstance(value, str): + if fmt == FMT_BINARY: + raise TypeError("value must be bytes-like object when fmt is " + "FMT_BINARY") + value = value.encode() fp = BytesIO(value) return load(fp, fmt=fmt, dict_type=dict_type, aware_datetime=aware_datetime) diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index 1d2e14a30c4e13..f47982907def21 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -510,6 +510,19 @@ def test_bytes(self): data2 = plistlib.dumps(pl2) self.assertEqual(data, data2) + def test_loads_str_with_xml_fmt(self): + pl = self._create() + b = plistlib.dumps(pl) + s = b.decode() + self.assertIsInstance(s, str) + pl2 = plistlib.loads(s) + self.assertEqual(pl, pl2) + + def test_loads_str_with_binary_fmt(self): + msg = "value must be bytes-like object when fmt is FMT_BINARY" + with self.assertRaisesRegex(TypeError, msg): + plistlib.loads('test', fmt=plistlib.FMT_BINARY) + def test_indentation_array(self): data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]] self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) diff --git a/Misc/NEWS.d/next/Library/2023-12-30-20-30-05.gh-issue-113537.v1W5_X.rst b/Misc/NEWS.d/next/Library/2023-12-30-20-30-05.gh-issue-113537.v1W5_X.rst new file mode 100644 index 00000000000000..a6150815b285a9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-30-20-30-05.gh-issue-113537.v1W5_X.rst @@ -0,0 +1 @@ +Support loads ``str`` in :func:`plistlib.loads`.