From 659e262a288ca93dd2fb8cc228e6c4a3f4a56e67 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Fri, 18 Sep 2026 16:20:55 +0900 Subject: [PATCH 1/4] gh-157688: Test `EXT_SUFFIX` platform triplet on RISC-V (#157690) --- Lib/test/test_sysconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index b40d1a23e13df9f..126e314db149165 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -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: From 7f7ff4c99d1f53424be8c323c50f3623c3488ba1 Mon Sep 17 00:00:00 2001 From: ankhikarmakar <100954262+ankhikarmakar@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:26:30 +0530 Subject: [PATCH 2/4] gh-145856: Fix plistlib.dump() with skipkeys=True and sort_keys=True (GH-150109) A dict with non-string keys raised TypeError, because the keys were sorted before skipping non-string keys. Co-authored-by: VanshAgarwal24036 Co-authored-by: Serhiy Storchaka Co-authored-by: Claude Opus 5 (1M context) --- Lib/plistlib.py | 36 +++++++++++-------- Lib/test/test_plistlib.py | 36 +++++++++++++------ ...-05-20-14-00-00.gh-issue-145856.Pl5kZx.rst | 4 +++ 3 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-20-14-00-00.gh-issue-145856.Pl5kZx.rst diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 93f3ef5e38af843..822fbcf17b16d08 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -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: @@ -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: @@ -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: @@ -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: diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index b9c261310bb5670..fd6036c033ffb85 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -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 = { diff --git a/Misc/NEWS.d/next/Library/2026-05-20-14-00-00.gh-issue-145856.Pl5kZx.rst b/Misc/NEWS.d/next/Library/2026-05-20-14-00-00.gh-issue-145856.Pl5kZx.rst new file mode 100644 index 000000000000000..9f8cc32fd81c88c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-20-14-00-00.gh-issue-145856.Pl5kZx.rst @@ -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`. From 0b18efdf12c56b7b1f2080aa26c419b0014bde77 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Sep 2026 11:08:44 +0300 Subject: [PATCH 3/4] gh-56596: Make IDLE key bindings work with Caps Lock on (#157709) Tk does not fold the case of letter keysyms, and Caps Lock changes it, so bind each key sequence with the other case of its letters too. --------- Co-authored-by: Terry Jan Reedy --- Lib/idlelib/editor.py | 10 ++++++++++ Lib/idlelib/idle_test/test_editor.py | 20 +++++++++++++++++++ ...09-17-23-00-00.gh-issue-56596.capslock.rst | 2 ++ 3 files changed, 32 insertions(+) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 8e15319b5baab20..1d5e181f42f7645 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -34,6 +34,10 @@ TK_TABWIDTH_DEFAULT = 8 darwin = sys.platform == 'darwin' +# A letter keysym in a key sequence, such as "s" in "". +_letter_key_re = re.compile(r'(?<=-Key-)[a-zA-Z](?=>)') + + class EditorWindow: is_shell = False # PyShell overrides. from idlelib.percolator import Percolator @@ -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. diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index e32981091b72a6e..2aaeb5037901b8f 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -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({'<>': ('', ''), + '<>': ('',), + '<>': ('', '')}) + self.assertEqual(set(e.text.event_info('<>')), + {'', '', + ''}) + # Existing variants are not added again. + self.assertEqual(e.text.event_info('<>'), + ('', '')) + self.assertEqual(set(e.text.event_info('<>')), + {'', + ''}) + 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) diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst new file mode 100644 index 000000000000000..70878e4f8012638 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst @@ -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. From e66424a4e6ffb7125cc093cb6c267df14c06f1ef Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:05:31 +0300 Subject: [PATCH 4/4] gh-115119: Fix licence page after removing bundled copy of libmpdec (#157686) Co-authored-by: Stan Ulbrych --- Doc/license.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/license.rst b/Doc/license.rst index 47c4fa9d582de3a..41e5ce4e3b668d8 100644 --- a/Doc/license.rst +++ b/Doc/license.rst @@ -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