Skip to content
Merged
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
7 changes: 4 additions & 3 deletions Doc/license.rst
Original file line number Diff line number Diff line change
Expand Up @@ -960,10 +960,11 @@ libmpdec
--------

The :mod:`!_decimal` C extension underlying the :mod:`decimal` module
is built using an included copy of the libmpdec
library unless the build is configured ``--with-system-libmpdec``::
uses the libmpdec library if made available by the operating system.
Additionally, the Windows and macOS installers for Python include a copy
of the libmpdec library, so we include a copy of the libmpdec license here::

Copyright (c) 2008-2020 Stefan Krah. All rights reserved.
Copyright (c) 2008-2024 Stefan Krah. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
Expand Down
10 changes: 10 additions & 0 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
TK_TABWIDTH_DEFAULT = 8
darwin = sys.platform == 'darwin'

# A letter keysym in a key sequence, such as "s" in "<Control-Key-s>".
_letter_key_re = re.compile(r'(?<=-Key-)[a-zA-Z](?=>)')


class EditorWindow:
is_shell = False # PyShell overrides.
from idlelib.percolator import Percolator
Expand Down Expand Up @@ -1186,6 +1190,12 @@ def apply_bindings(self, keydefs=None):
for event, keylist in keydefs.items():
if keylist:
text.event_add(event, *keylist)
# Caps Lock changes the case of letter keysyms, so bind
# the sequences with the other case too (gh-56596).
for keys in keylist:
other = _letter_key_re.sub(lambda m: m[0].swapcase(), keys)
if other not in keylist:
text.event_add(event, other)

def fill_menus(self, menudefs=None, keydefs=None):
"""Fill in dropdown menus used by this window.
Expand Down
20 changes: 20 additions & 0 deletions Lib/idlelib/idle_test/test_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ def test_init(self):
self.assertEqual(e.root, self.root)
e._close()

def test_apply_bindings_caps_lock(self):
# gh-56596: Caps Lock changes the case of letter keysyms, so the
# sequences are bound with both cases.
e = Editor(root=self.root)
try:
e.apply_bindings({'<<spam>>': ('<Control-Key-s>', '<Key-F1>'),
'<<eggs>>': ('<Control-Key-x><Alt-Shift-Key-S>',),
'<<ham>>': ('<Control-Key-h>', '<Control-Key-H>')})
self.assertEqual(set(e.text.event_info('<<spam>>')),
{'<Control-KeyPress-s>', '<Control-KeyPress-S>',
'<KeyPress-F1>'})
# Existing variants are not added again.
self.assertEqual(e.text.event_info('<<ham>>'),
('<Control-KeyPress-h>', '<Control-KeyPress-H>'))
self.assertEqual(set(e.text.event_info('<<eggs>>')),
{'<Control-Key-x><Shift-Alt-Key-S>',
'<Control-Key-X><Shift-Alt-Key-s>'})
finally:
e._close()

def test_set_width_zero_char_width(self):
# A zero-width '0' must not raise ZeroDivisionError (gh-90304).
e = Editor(root=self.root)
Expand Down
36 changes: 22 additions & 14 deletions Lib/plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,25 @@ def _date_to_string(d, aware_datetime):
d.hour, d.minute, d.second
)

def _dict_items(d, sort_keys, skipkeys):
"""Return the (key, value) pairs of a dict, sorted if needed.

Sorting fails for keys of different types, so non-string keys are
removed or reported before sorting.
"""
items = d.items()
if sort_keys:
if skipkeys:
items = [item for item in items if isinstance(item[0], str)]
items.sort()
else:
for key in d:
if not isinstance(key, str):
raise TypeError("keys must be strings")
items = sorted(items)
return items


def _escape(text):
m = _controlCharPat.search(text)
if m is not None:
Expand Down Expand Up @@ -388,11 +407,7 @@ def write_bytes(self, data):
def write_dict(self, d):
if d:
self.begin_element("dict")
if self._sort_keys:
items = sorted(d.items())
else:
items = d.items()

items = _dict_items(d, self._sort_keys, self._skipkeys)
for key, value in items:
if not isinstance(key, str):
if self._skipkeys:
Expand Down Expand Up @@ -718,10 +733,7 @@ def _flatten(self, value):
if isinstance(value, (dict, frozendict)):
keys = []
values = []
items = value.items()
if self._sort_keys:
items = sorted(items)

items = _dict_items(value, self._sort_keys, self._skipkeys)
for k, v in items:
if not isinstance(k, str):
if self._skipkeys:
Expand Down Expand Up @@ -839,11 +851,7 @@ def _write_object(self, value):
elif isinstance(value, (dict, frozendict)):
keyRefs, valRefs = [], []

if self._sort_keys:
rootItems = sorted(value.items())
else:
rootItems = value.items()

rootItems = _dict_items(value, self._sort_keys, self._skipkeys)
for k, v in rootItems:
if not isinstance(k, str):
if self._skipkeys:
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
2 changes: 1 addition & 1 deletion Lib/test/test_sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ def test_linux_ext_suffix(self):
ctypes = import_module('ctypes')
machine = platform.machine()
suffix = sysconfig.get_config_var('EXT_SUFFIX')
if re.match('(aarch64|arm|mips|ppc|powerpc|s390|sparc)', machine):
if re.match('(aarch64|arm|mips|ppc|powerpc|riscv|s390|sparc)', machine):
self.assertTrue('linux' in suffix, suffix)
if re.match('(i[3-6]86|x86_64)$', machine):
if ctypes.sizeof(ctypes.c_char_p()) == 4:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make IDLE keyboard shortcuts with ascii letters work when Caps Lock is on even when only one
of the upper and lowercase versions are defined.
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