bpo-36409: Remove old plistlib API deprecated in 3.4 (GH-15615)

* Remove implementation for old plistlib API deprecated in 3.4
This commit is contained in:
Jon Janzen 2019-09-05 03:11:35 -05:00 committed by Ronald Oussoren
parent 9b51570ffd
commit ce81a925ef
5 changed files with 31 additions and 324 deletions

View File

@ -30,7 +30,7 @@ To work with plist data in bytes objects, use :func:`dumps`
and :func:`loads`. and :func:`loads`.
Values can be strings, integers, floats, booleans, tuples, lists, dictionaries Values can be strings, integers, floats, booleans, tuples, lists, dictionaries
(but only with string keys), :class:`Data`, :class:`bytes`, :class:`bytesarray` (but only with string keys), :class:`bytes`, :class:`bytearray`
or :class:`datetime.datetime` objects. or :class:`datetime.datetime` objects.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
@ -40,6 +40,9 @@ or :class:`datetime.datetime` objects.
Support added for reading and writing :class:`UID` tokens in binary plists as used Support added for reading and writing :class:`UID` tokens in binary plists as used
by NSKeyedArchiver and NSKeyedUnarchiver. by NSKeyedArchiver and NSKeyedUnarchiver.
.. versionchanged:: 3.9
Old API removed.
.. seealso:: .. seealso::
`PList manual page <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/>`_ `PList manual page <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/>`_
@ -48,7 +51,7 @@ or :class:`datetime.datetime` objects.
This module defines the following functions: This module defines the following functions:
.. function:: load(fp, \*, fmt=None, use_builtin_types=True, dict_type=dict) .. function:: load(fp, \*, fmt=None, dict_type=dict)
Read a plist file. *fp* should be a readable and binary file object. Read a plist file. *fp* should be a readable and binary file object.
Return the unpacked root object (which usually is a Return the unpacked root object (which usually is a
@ -62,10 +65,6 @@ This module defines the following functions:
* :data:`FMT_BINARY`: Binary plist format * :data:`FMT_BINARY`: Binary plist format
If *use_builtin_types* is true (the default) binary data will be returned
as instances of :class:`bytes`, otherwise it is returned as instances of
:class:`Data`.
The *dict_type* is the type used for dictionaries that are read from the The *dict_type* is the type used for dictionaries that are read from the
plist file. plist file.
@ -80,7 +79,7 @@ This module defines the following functions:
.. versionadded:: 3.4 .. versionadded:: 3.4
.. function:: loads(data, \*, fmt=None, use_builtin_types=True, dict_type=dict) .. function:: loads(data, \*, fmt=None, dict_type=dict)
Load a plist from a bytes object. See :func:`load` for an explanation of Load a plist from a bytes object. See :func:`load` for an explanation of
the keyword arguments. the keyword arguments.
@ -124,75 +123,6 @@ This module defines the following functions:
.. versionadded:: 3.4 .. versionadded:: 3.4
The following functions are deprecated:
.. function:: readPlist(pathOrFile)
Read a plist file. *pathOrFile* may be either a file name or a (readable
and binary) file object. Returns the unpacked root object (which usually
is a dictionary).
This function calls :func:`load` to do the actual work, see the documentation
of :func:`that function <load>` for an explanation of the keyword arguments.
.. deprecated:: 3.4 Use :func:`load` instead.
.. versionchanged:: 3.7
Dict values in the result are now normal dicts. You no longer can use
attribute access to access items of these dictionaries.
.. function:: writePlist(rootObject, pathOrFile)
Write *rootObject* to an XML plist file. *pathOrFile* may be either a file name
or a (writable and binary) file object
.. deprecated:: 3.4 Use :func:`dump` instead.
.. function:: readPlistFromBytes(data)
Read a plist data from a bytes object. Return the root object.
See :func:`load` for a description of the keyword arguments.
.. deprecated:: 3.4 Use :func:`loads` instead.
.. versionchanged:: 3.7
Dict values in the result are now normal dicts. You no longer can use
attribute access to access items of these dictionaries.
.. function:: writePlistToBytes(rootObject)
Return *rootObject* as an XML plist-formatted bytes object.
.. deprecated:: 3.4 Use :func:`dumps` instead.
The following classes are available:
.. class:: Data(data)
Return a "data" wrapper object around the bytes object *data*. This is used
in functions converting from/to plists to represent the ``<data>`` type
available in plists.
It has one attribute, :attr:`data`, that can be used to retrieve the Python
bytes object stored in it.
.. deprecated:: 3.4 Use a :class:`bytes` object instead.
.. class:: UID(data)
Wraps an :class:`int`. This is used when reading or writing NSKeyedArchiver
encoded data, which contains UID (see PList manual).
It has one attribute, :attr:`data` which can be used to retrieve the int value
of the UID. :attr:`data` must be in the range `0 <= data <= 2**64`.
.. versionadded:: 3.8
The following constants are available: The following constants are available:

View File

@ -213,6 +213,12 @@ Removed
instead. The ``xml.etree.cElementTree`` module has been removed. instead. The ``xml.etree.cElementTree`` module has been removed.
(Contributed by Serhiy Storchaka in :issue:`36543`.) (Contributed by Serhiy Storchaka in :issue:`36543`.)
* The old :mod:`plistlib` API has been removed, it was deprecated since Python
3.4. Use the :func:`load`, :func:`loads`, :func:`dump`, and :func:`dumps`
functions. Additionally, the ``use_builtin_types`` parameter was removed,
standard :class:`bytes` objects are always used.
(Contributed by Jon Janzen in :issue:`36409`.)
Porting to Python 3.9 Porting to Python 3.9
===================== =====================

View File

@ -46,9 +46,7 @@ Parse Plist example:
print(pl["aKey"]) print(pl["aKey"])
""" """
__all__ = [ __all__ = [
"readPlist", "writePlist", "readPlistFromBytes", "writePlistToBytes", "InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"
"Data", "InvalidFileException", "FMT_XML", "FMT_BINARY",
"load", "dump", "loads", "dumps", "UID"
] ]
import binascii import binascii
@ -69,112 +67,6 @@ PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
globals().update(PlistFormat.__members__) globals().update(PlistFormat.__members__)
#
#
# Deprecated functionality
#
#
@contextlib.contextmanager
def _maybe_open(pathOrFile, mode):
if isinstance(pathOrFile, str):
with open(pathOrFile, mode) as fp:
yield fp
else:
yield pathOrFile
def readPlist(pathOrFile):
"""
Read a .plist from a path or file. pathOrFile should either
be a file name, or a readable binary file object.
This function is deprecated, use load instead.
"""
warn("The readPlist function is deprecated, use load() instead",
DeprecationWarning, 2)
with _maybe_open(pathOrFile, 'rb') as fp:
return load(fp, fmt=None, use_builtin_types=False)
def writePlist(value, pathOrFile):
"""
Write 'value' to a .plist file. 'pathOrFile' may either be a
file name or a (writable) file object.
This function is deprecated, use dump instead.
"""
warn("The writePlist function is deprecated, use dump() instead",
DeprecationWarning, 2)
with _maybe_open(pathOrFile, 'wb') as fp:
dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False)
def readPlistFromBytes(data):
"""
Read a plist data from a bytes object. Return the root object.
This function is deprecated, use loads instead.
"""
warn("The readPlistFromBytes function is deprecated, use loads() instead",
DeprecationWarning, 2)
return load(BytesIO(data), fmt=None, use_builtin_types=False)
def writePlistToBytes(value):
"""
Return 'value' as a plist-formatted bytes object.
This function is deprecated, use dumps instead.
"""
warn("The writePlistToBytes function is deprecated, use dumps() instead",
DeprecationWarning, 2)
f = BytesIO()
dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
return f.getvalue()
class Data:
"""
Wrapper for binary data.
This class is deprecated, use a bytes object instead.
"""
def __init__(self, data):
if not isinstance(data, bytes):
raise TypeError("data must be as bytes")
self.data = data
@classmethod
def fromBase64(cls, data):
# base64.decodebytes just calls binascii.a2b_base64;
# it seems overkill to use both base64 and binascii.
return cls(_decode_base64(data))
def asBase64(self, maxlinelength=76):
return _encode_base64(self.data, maxlinelength)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.data == other.data
elif isinstance(other, bytes):
return self.data == other
else:
return NotImplemented
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.data))
#
#
# End of deprecated functionality
#
#
class UID: class UID:
def __init__(self, data): def __init__(self, data):
if not isinstance(data, int): if not isinstance(data, int):
@ -202,7 +94,6 @@ class UID:
def __hash__(self): def __hash__(self):
return hash(self.data) return hash(self.data)
# #
# XML support # XML support
# #
@ -273,11 +164,10 @@ def _escape(text):
return text return text
class _PlistParser: class _PlistParser:
def __init__(self, use_builtin_types, dict_type): def __init__(self, dict_type):
self.stack = [] self.stack = []
self.current_key = None self.current_key = None
self.root = None self.root = None
self._use_builtin_types = use_builtin_types
self._dict_type = dict_type self._dict_type = dict_type
def parse(self, fileobj): def parse(self, fileobj):
@ -366,11 +256,7 @@ class _PlistParser:
self.add_object(self.get_data()) self.add_object(self.get_data())
def end_data(self): def end_data(self):
if self._use_builtin_types: self.add_object(_decode_base64(self.get_data()))
self.add_object(_decode_base64(self.get_data()))
else:
self.add_object(Data.fromBase64(self.get_data()))
def end_date(self): def end_date(self):
self.add_object(_date_from_string(self.get_data())) self.add_object(_date_from_string(self.get_data()))
@ -452,9 +338,6 @@ class _PlistWriter(_DumbXMLWriter):
elif isinstance(value, dict): elif isinstance(value, dict):
self.write_dict(value) self.write_dict(value)
elif isinstance(value, Data):
self.write_data(value)
elif isinstance(value, (bytes, bytearray)): elif isinstance(value, (bytes, bytearray)):
self.write_bytes(value) self.write_bytes(value)
@ -467,9 +350,6 @@ class _PlistWriter(_DumbXMLWriter):
else: else:
raise TypeError("unsupported type: %s" % type(value)) raise TypeError("unsupported type: %s" % type(value))
def write_data(self, data):
self.write_bytes(data.data)
def write_bytes(self, data): def write_bytes(self, data):
self.begin_element("data") self.begin_element("data")
self._indent_level -= 1 self._indent_level -= 1
@ -563,8 +443,7 @@ class _BinaryPlistParser:
see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
""" """
def __init__(self, use_builtin_types, dict_type): def __init__(self, dict_type):
self._use_builtin_types = use_builtin_types
self._dict_type = dict_type self._dict_type = dict_type
def parse(self, fp): def parse(self, fp):
@ -664,10 +543,7 @@ class _BinaryPlistParser:
elif tokenH == 0x40: # data elif tokenH == 0x40: # data
s = self._get_size(tokenL) s = self._get_size(tokenL)
if self._use_builtin_types: result = self._fp.read(s)
result = self._fp.read(s)
else:
result = Data(self._fp.read(s))
elif tokenH == 0x50: # ascii string elif tokenH == 0x50: # ascii string
s = self._get_size(tokenL) s = self._get_size(tokenL)
@ -783,10 +659,6 @@ class _BinaryPlistWriter (object):
if (type(value), value) in self._objtable: if (type(value), value) in self._objtable:
return return
elif isinstance(value, Data):
if (type(value.data), value.data) in self._objtable:
return
elif id(value) in self._objidtable: elif id(value) in self._objidtable:
return return
@ -795,8 +667,6 @@ class _BinaryPlistWriter (object):
self._objlist.append(value) self._objlist.append(value)
if isinstance(value, _scalars): if isinstance(value, _scalars):
self._objtable[(type(value), value)] = refnum self._objtable[(type(value), value)] = refnum
elif isinstance(value, Data):
self._objtable[(type(value.data), value.data)] = refnum
else: else:
self._objidtable[id(value)] = refnum self._objidtable[id(value)] = refnum
@ -826,8 +696,6 @@ class _BinaryPlistWriter (object):
def _getrefnum(self, value): def _getrefnum(self, value):
if isinstance(value, _scalars): if isinstance(value, _scalars):
return self._objtable[(type(value), value)] return self._objtable[(type(value), value)]
elif isinstance(value, Data):
return self._objtable[(type(value.data), value.data)]
else: else:
return self._objidtable[id(value)] return self._objidtable[id(value)]
@ -885,10 +753,6 @@ class _BinaryPlistWriter (object):
f = (value - datetime.datetime(2001, 1, 1)).total_seconds() f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
self._fp.write(struct.pack('>Bd', 0x33, f)) self._fp.write(struct.pack('>Bd', 0x33, f))
elif isinstance(value, Data):
self._write_size(0x40, len(value.data))
self._fp.write(value.data)
elif isinstance(value, (bytes, bytearray)): elif isinstance(value, (bytes, bytearray)):
self._write_size(0x40, len(value)) self._write_size(0x40, len(value))
self._fp.write(value) self._fp.write(value)
@ -970,7 +834,7 @@ _FORMATS={
} }
def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict): def load(fp, *, fmt=None, dict_type=dict):
"""Read a .plist file. 'fp' should be a readable and binary file object. """Read a .plist file. 'fp' should be a readable and binary file object.
Return the unpacked root object (which usually is a dictionary). Return the unpacked root object (which usually is a dictionary).
""" """
@ -988,17 +852,16 @@ def load(fp, *, fmt=None, use_builtin_types=True, dict_type=dict):
else: else:
P = _FORMATS[fmt]['parser'] P = _FORMATS[fmt]['parser']
p = P(use_builtin_types=use_builtin_types, dict_type=dict_type) p = P(dict_type=dict_type)
return p.parse(fp) return p.parse(fp)
def loads(value, *, fmt=None, use_builtin_types=True, dict_type=dict): def loads(value, *, fmt=None, dict_type=dict):
"""Read a .plist file from a bytes object. """Read a .plist file from a bytes object.
Return the unpacked root object (which usually is a dictionary). Return the unpacked root object (which usually is a dictionary).
""" """
fp = BytesIO(value) fp = BytesIO(value)
return load( return load(fp, fmt=fmt, dict_type=dict_type)
fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type)
def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False): def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False):

View File

@ -263,14 +263,12 @@ class TestPlistlib(unittest.TestCase):
self.assertEqual(copy.deepcopy(UID(1)), UID(1)) self.assertEqual(copy.deepcopy(UID(1)), UID(1))
def test_appleformatting(self): def test_appleformatting(self):
for use_builtin_types in (True, False): for fmt in ALL_FORMATS:
for fmt in ALL_FORMATS: with self.subTest(fmt=fmt):
with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types): pl = plistlib.loads(TESTDATA[fmt])
pl = plistlib.loads(TESTDATA[fmt], data = plistlib.dumps(pl, fmt=fmt)
use_builtin_types=use_builtin_types) self.assertEqual(data, TESTDATA[fmt],
data = plistlib.dumps(pl, fmt=fmt) "generated data was not identical to Apple's output")
self.assertEqual(data, TESTDATA[fmt],
"generated data was not identical to Apple's output")
def test_appleformattingfromliteral(self): def test_appleformattingfromliteral(self):
@ -524,8 +522,7 @@ class TestBinaryPlistlib(unittest.TestCase):
# Test effectiveness of saving duplicated objects # Test effectiveness of saving duplicated objects
for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
datetime.datetime(2004, 10, 26, 10, 33, 33), datetime.datetime(2004, 10, 26, 10, 33, 33),
plistlib.Data(b'abcde'), bytearray(b'abcde'), bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}):
[12, 345], (12, 345), {'12': 345}):
with self.subTest(x=x): with self.subTest(x=x):
data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY) data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY)
self.assertLess(len(data), 1100, repr(data)) self.assertLess(len(data), 1100, repr(data))
@ -533,8 +530,7 @@ class TestBinaryPlistlib(unittest.TestCase):
def test_identity(self): def test_identity(self):
for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
datetime.datetime(2004, 10, 26, 10, 33, 33), datetime.datetime(2004, 10, 26, 10, 33, 33),
plistlib.Data(b'abcde'), bytearray(b'abcde'), bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}):
[12, 345], (12, 345), {'12': 345}):
with self.subTest(x=x): with self.subTest(x=x):
data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY) data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY)
a, b = plistlib.loads(data) a, b = plistlib.loads(data)
@ -621,95 +617,6 @@ class TestBinaryPlistlib(unittest.TestCase):
plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
class TestPlistlibDeprecated(unittest.TestCase):
def test_io_deprecated(self):
pl_in = {
'key': 42,
'sub': {
'key': 9,
'alt': 'value',
'data': b'buffer',
}
}
pl_out = {
'key': 42,
'sub': {
'key': 9,
'alt': 'value',
'data': plistlib.Data(b'buffer'),
}
}
self.addCleanup(support.unlink, support.TESTFN)
with self.assertWarns(DeprecationWarning):
plistlib.writePlist(pl_in, support.TESTFN)
with self.assertWarns(DeprecationWarning):
pl2 = plistlib.readPlist(support.TESTFN)
self.assertEqual(pl_out, pl2)
os.unlink(support.TESTFN)
with open(support.TESTFN, 'wb') as fp:
with self.assertWarns(DeprecationWarning):
plistlib.writePlist(pl_in, fp)
with open(support.TESTFN, 'rb') as fp:
with self.assertWarns(DeprecationWarning):
pl2 = plistlib.readPlist(fp)
self.assertEqual(pl_out, pl2)
def test_bytes_deprecated(self):
pl = {
'key': 42,
'sub': {
'key': 9,
'alt': 'value',
'data': b'buffer',
}
}
with self.assertWarns(DeprecationWarning):
data = plistlib.writePlistToBytes(pl)
with self.assertWarns(DeprecationWarning):
pl2 = plistlib.readPlistFromBytes(data)
self.assertIsInstance(pl2, dict)
self.assertEqual(pl2, dict(
key=42,
sub=dict(
key=9,
alt='value',
data=plistlib.Data(b'buffer'),
)
))
with self.assertWarns(DeprecationWarning):
data2 = plistlib.writePlistToBytes(pl2)
self.assertEqual(data, data2)
def test_dataobject_deprecated(self):
in_data = { 'key': plistlib.Data(b'hello') }
out_data = { 'key': b'hello' }
buf = plistlib.dumps(in_data)
cur = plistlib.loads(buf)
self.assertEqual(cur, out_data)
self.assertEqual(cur, in_data)
cur = plistlib.loads(buf, use_builtin_types=False)
self.assertEqual(cur, out_data)
self.assertEqual(cur, in_data)
with self.assertWarns(DeprecationWarning):
cur = plistlib.readPlistFromBytes(buf)
self.assertEqual(cur, out_data)
self.assertEqual(cur, in_data)
class TestKeyedArchive(unittest.TestCase): class TestKeyedArchive(unittest.TestCase):
def test_keyed_archive_data(self): def test_keyed_archive_data(self):
# This is the structure of a NSKeyedArchive packed plist # This is the structure of a NSKeyedArchive packed plist
@ -749,7 +656,7 @@ class MiscTestCase(unittest.TestCase):
def test_main(): def test_main():
support.run_unittest(TestPlistlib, TestPlistlibDeprecated, TestKeyedArchive, MiscTestCase) support.run_unittest(TestPlistlib, TestKeyedArchive, MiscTestCase)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1 @@
Remove the old plistlib API deprecated in Python 3.4