Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions Lib/plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,14 @@ def write_bytes(self, data):
def write_dict(self, d):
if d:
self.begin_element("dict")
items = d.items()
if self._skipkeys:
items = [(k, v) for k, v in items if isinstance(k, str)]
if self._sort_keys:
items = sorted(d.items())
else:
items = d.items()
items = sorted(items)

for key, value in items:
if not isinstance(key, str):
if self._skipkeys:
continue
raise TypeError("keys must be strings")
self.simple_element("key", key)
self.write_value(value)
Expand Down Expand Up @@ -719,13 +718,13 @@ def _flatten(self, value):
keys = []
values = []
items = value.items()
if self._skipkeys:
items = [(k, v) for k, v in items if isinstance(k, str)]
if self._sort_keys:
items = sorted(items)

for k, v in items:
if not isinstance(k, str):
if self._skipkeys:
continue
raise TypeError("keys must be strings")
keys.append(k)
values.append(v)
Expand Down Expand Up @@ -839,15 +838,15 @@ def _write_object(self, value):
elif isinstance(value, (dict, frozendict)):
keyRefs, valRefs = [], []

rootItems = value.items()
if self._skipkeys:
rootItems = [(k, v) for k, v in rootItems
if isinstance(k, str)]
if self._sort_keys:
rootItems = sorted(value.items())
else:
rootItems = value.items()
rootItems = sorted(rootItems)

for k, v in rootItems:
if not isinstance(k, str):
if self._skipkeys:
continue
raise TypeError("keys must be strings")
keyRefs.append(self._getrefnum(k))
valRefs.append(self._getrefnum(v))
Expand Down
36 changes: 25 additions & 11 deletions Lib/test/test_plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,20 +722,34 @@ def test_skipkeys(self):
'snake': 'aWord',
}

for fmt in ALL_FORMATS:
for sort_keys in (False, True):
with self.subTest(fmt=fmt, sort_keys=sort_keys):
data = plistlib.dumps(
pl, fmt=fmt, skipkeys=True, sort_keys=sort_keys)

pl2 = plistlib.loads(data)
self.assertEqual(pl2, {'snake': 'aWord'})

fp = BytesIO()
plistlib.dump(
pl, fp, fmt=fmt, skipkeys=True, sort_keys=sort_keys)
data = fp.getvalue()
pl2 = plistlib.loads(fp.getvalue())
self.assertEqual(pl2, {'snake': 'aWord'})

def test_skipkeys_with_sort_keys_mixed_types(self):
# gh-145856: skipkeys=True + sort_keys=True with mixed key types
# used to raise TypeError because the sort ran before the filter.
pl = {1: 'a', 'z': 'b', 'a': 'c'}

for fmt in ALL_FORMATS:
with self.subTest(fmt=fmt):
data = plistlib.dumps(
pl, fmt=fmt, skipkeys=True, sort_keys=False)

pl2 = plistlib.loads(data)
self.assertEqual(pl2, {'snake': 'aWord'})

fp = BytesIO()
plistlib.dump(
pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
data = fp.getvalue()
pl2 = plistlib.loads(fp.getvalue())
self.assertEqual(pl2, {'snake': 'aWord'})
pl, fmt=fmt, skipkeys=True, sort_keys=True)
pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
self.assertEqual(dict(pl2), {'z': 'b', 'a': 'c'})
self.assertEqual(list(pl2.keys()), ['a', 'z'])

def test_tuple_members(self):
pl = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :func:`plistlib.dumps` and :func:`plistlib.dump` so that ``skipkeys=True``
together with ``sort_keys=True`` correctly drops non-string keys when the
dictionary contains a mix of string and non-string keys. Previously the sort
ran before the filter and raised :exc:`TypeError`.
Loading