Skip to content

Commit 4ad011f

Browse files
authored
Merge branch 'main' into subscope
2 parents 39b65fc + a321390 commit 4ad011f

32 files changed

Lines changed: 1511 additions & 151 deletions

‎.github/workflows/reusable-wasi.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-26.04-arm
1616
timeout-minutes: 60
1717
env:
18-
WASMTIME_VERSION: 38.0.3
18+
WASMTIME_VERSION: 48.0.2
1919
CROSS_BUILD_WASI: cross-build/wasm32-wasip1
2020
steps:
2121
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

‎.pre-commit-config.yaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ repos:
143143
entry: Space found in path, move to Misc/NEWS.d/next/Core_and_Builtins/
144144
files: Misc/NEWS.d/next/Core and Builtins/20.*.rst
145145

146+
- repo: local
147+
hooks:
148+
- id: check-capi-macros
149+
name: Check C API macros start with Py
150+
language: python
151+
entry: python Tools/build/check_capi_macros.py
152+
pass_filenames: false
153+
files: ^(Include/(cpython/)?[^/]+\.h|pyconfig\.h\.in|Tools/build/check_capi_macros)
154+
146155
- repo: meta
147156
hooks:
148157
- id: check-hooks-apply

‎Doc/library/ctypes.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,15 @@ Generally you only use this feature if you receive a pointer from a C function,
997997
and you *know* that the pointer actually points to an array instead of a single
998998
item.
999999

1000+
.. warning::
1001+
1002+
Because pointer objects support subscription, they implicitly support
1003+
:term:`iteration <iterator>`. Unless doing this in a controlled manner,
1004+
such as by manually calling :func:`next` on a :func:`pointer` iterator, this
1005+
will typically lead to infinite loops or crashes, because ctypes has no way
1006+
of knowing when to stop iteration. In other words, a ``pointer`` iterator
1007+
will infinitely yield arbitrary memory.
1008+
10001009
Behind the scenes, the :func:`pointer` function does more than simply create
10011010
pointer instances, it has to create pointer *types* first. This is done with the
10021011
:func:`POINTER` function, which accepts any :mod:`!ctypes` type, and returns a

‎Doc/library/ipaddress.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ write code that handles both IP versions correctly. Address objects are
261261

262262
.. attribute:: ipv6_mapped
263263

264-
:class:`IPv4Address` object representing the IPv4-mapped IPv6 address. See :RFC:`4291`.
264+
:class:`IPv6Address` object representing the IPv4-mapped IPv6 address. See :RFC:`4291`.
265265

266266
.. versionadded:: 3.13
267267

‎Include/internal/pycore_object.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,8 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);
957957

958958
// Export for 'math' shared extension
959959
PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
960-
PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self);
960+
PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method,
961+
_PyStackRef *self);
961962

962963
// Calls the method named `attr` on `self`, but does not set an exception if
963964
// the attribute does not exist.

‎Include/internal/pycore_unicodeobject.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ extern int _PyUnicodeWriter_FormatV(
244244

245245
/* --- iconv Codec -------------------------------------------------------- */
246246

247-
#ifdef HAVE_ICONV
247+
#ifdef _Py_HAVE_ICONV
248248
extern PyObject* _PyUnicode_DecodeIconv(
249249
const char *encoding, /* iconv encoding name */
250250
const char *string, /* encoded string */

‎Lib/_strptime.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -564,15 +564,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
564564
del err
565565
bad_directive = bad_directive.replace('\\s', '')
566566
if not bad_directive:
567-
raise ValueError("stray %% in format '%s'" % format) from None
567+
raise ValueError(f"stray % in format {format!r}") from None
568568
bad_directive = bad_directive.replace('\\', '', 1)
569-
raise ValueError("'%s' is a bad directive in format '%s'" %
570-
(bad_directive, format)) from None
569+
raise ValueError(f"{bad_directive!r} is a bad directive "
570+
f"in format {format!r}") from None
571571
_regex_cache[format] = format_regex
572572
found = format_regex.match(data_string)
573573
if not found:
574-
raise ValueError("time data %r does not match format %r" %
575-
(data_string, format))
574+
raise ValueError(f"time data {data_string!r} does not match "
575+
f"format {format!r}")
576576
if len(data_string) != found.end():
577577
rest = data_string[found.end():]
578578
# Specific check for '%:z' directive
@@ -582,9 +582,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
582582
and rest[0] != ":"
583583
):
584584
raise ValueError(
585-
f"Missing colon in %:z before '{rest}', got '{data_string}'"
585+
f"Missing colon in %:z before {rest!r}, got {data_string!r}"
586586
)
587-
raise ValueError("unconverted data remains: %s" % rest)
587+
raise ValueError(f"unconverted data remains: {rest!r}")
588588

589589
iso_year = year = None
590590
month = day = 1
@@ -700,7 +700,7 @@ def parse_int(s):
700700
z = z[:3] + z[4:]
701701
if len(z) > 5:
702702
if z[5] != ':':
703-
msg = f"Inconsistent use of : in {found_dict[group_key]}"
703+
msg = f"Inconsistent use of : in {found_dict[group_key]!r}"
704704
raise ValueError(msg)
705705
z = z[:5] + z[6:]
706706
hours = int(z[1:3])

‎Lib/idlelib/configdialog.py‎

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,8 +1146,8 @@ def create_page_keys(self):
11461146
selected keyset. The keybindings are loaded in load_keys_list()
11471147
and are pairs of (event, [keys]) where keys can be a list
11481148
of one or more key combinations to bind to the same event.
1149-
Mouse button 1 click invokes on_bindingslist_select(), which
1150-
allows button_new_keys to be clicked.
1149+
Mouse button 1 click or Up or Down key invokes
1150+
on_bindingslist_select(), which allows button_new_keys to be clicked.
11511151
11521152
So, an item is selected in listbindings, which activates
11531153
button_new_keys, and clicking button_new_keys calls function
@@ -1221,9 +1221,12 @@ def create_page_keys(self):
12211221
scroll_target_y = Scrollbar(frame_target)
12221222
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
12231223
self.bindingslist = Listbox(
1224-
frame_target, takefocus=FALSE, exportselection=FALSE)
1224+
frame_target, takefocus=True, exportselection=FALSE)
12251225
self.bindingslist.bind('<ButtonRelease-1>',
12261226
self.on_bindingslist_select)
1227+
self.bindingslist.bind('<KeyRelease-Up>', self.on_bindingslist_select)
1228+
self.bindingslist.bind('<KeyRelease-Down>',
1229+
self.on_bindingslist_select)
12271230
scroll_target_y['command'] = self.bindingslist.yview
12281231
scroll_target_x['command'] = self.bindingslist.xview
12291232
self.bindingslist['yscrollcommand'] = scroll_target_y.set
@@ -1427,7 +1430,14 @@ def save_as_new_key_set(self):
14271430
self.create_new_key_set(new_keys_name)
14281431

14291432
def on_bindingslist_select(self, event):
1430-
"Activate button to assign new keys to selected action."
1433+
"""Activate button to assign new keys to selected action.
1434+
1435+
Event can result from either mouse click or Up or Down key.
1436+
The keys move the selection, but not the anchor used by
1437+
get_new_keys and var_changed_keybinding.
1438+
"""
1439+
if event.type.name == 'KeyRelease':
1440+
self.bindingslist.selection_anchor(ACTIVE)
14311441
self.button_new_keys.state(('!disabled',))
14321442

14331443
def create_new_key_set(self, new_key_set_name):
@@ -1465,9 +1475,8 @@ def load_keys_list(self, keyset_name):
14651475
14661476
An action/key binding can be selected to change the key binding.
14671477
"""
1468-
reselect = False
1478+
list_index = 0
14691479
if self.bindingslist.curselection():
1470-
reselect = True
14711480
list_index = self.bindingslist.index(ANCHOR)
14721481
keyset = idleConf.GetKeySet(keyset_name)
14731482
# 'set' is dict mapping virtual event to list of key events.
@@ -1482,10 +1491,11 @@ def load_keys_list(self, keyset_name):
14821491
if bind_name in changes['keys'][keyset_name]:
14831492
key = changes['keys'][keyset_name][bind_name]
14841493
self.bindingslist.insert(END, bind_name+' - '+key)
1485-
if reselect:
1486-
self.bindingslist.see(list_index)
1487-
self.bindingslist.select_set(list_index)
1488-
self.bindingslist.select_anchor(list_index)
1494+
self.bindingslist.see(list_index)
1495+
self.bindingslist.select_set(list_index)
1496+
self.bindingslist.select_anchor(list_index)
1497+
self.bindingslist.activate(list_index)
1498+
self.button_new_keys.state(('!disabled',))
14891499

14901500
@staticmethod
14911501
def save_new_key_set(keyset_name, keyset):
@@ -2124,6 +2134,8 @@ def create_frame_help(self):
21242134
scroll_helplist['command'] = self.helplist.yview
21252135
self.helplist['yscrollcommand'] = scroll_helplist.set
21262136
self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
2137+
self.helplist.bind('<KeyRelease-Up>', self.help_source_selected)
2138+
self.helplist.bind('<KeyRelease-Down>', self.help_source_selected)
21272139

21282140
frame_buttons = Frame(self)
21292141
self.button_helplist_edit = Button(
@@ -2146,7 +2158,14 @@ def create_frame_help(self):
21462158
self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
21472159

21482160
def help_source_selected(self, event):
2149-
"Handle event for selecting additional help."
2161+
"""Handle event for selecting additional help.
2162+
2163+
Event can result from either mouse click or Up or Down key.
2164+
The keys move the selection, but not the anchor used by
2165+
helplist_item_edit and helplist_item_remove.
2166+
"""
2167+
if event.type.name == 'KeyRelease':
2168+
self.helplist.selection_anchor(ACTIVE)
21502169
self.set_add_delete_state()
21512170

21522171
def set_add_delete_state(self):

‎Lib/idlelib/idle_test/test_configdialog.py‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import unittest
1010
from unittest import mock
1111
from idlelib.idle_test.mock_idle import Func
12-
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
12+
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL,
13+
EventType)
14+
from types import SimpleNamespace
1315
from idlelib import config
1416
from idlelib.configdialog import idleConf, changes, tracers
1517

@@ -1060,6 +1062,14 @@ def test_on_bindingslist_select(self):
10601062
self.assertEqual(b.get('anchor'), 'find')
10611063
self.assertNotIn('disabled', d.button_new_keys.state())
10621064

1065+
# gh-75234: Up and Down keys move the active item, but not the
1066+
# anchor; the handler moves the anchor.
1067+
d.button_new_keys.state(('disabled',))
1068+
b.activate(0)
1069+
d.on_bindingslist_select(SimpleNamespace(type=EventType.KeyRelease))
1070+
self.assertEqual(b.get('anchor'), 'copy')
1071+
self.assertNotIn('disabled', d.button_new_keys.state())
1072+
10631073
def test_create_new_key_set_and_save_new_key_set(self):
10641074
eq = self.assertEqual
10651075
d = self.page
@@ -1110,11 +1120,14 @@ def test_load_keys_list(self):
11101120
'force-open-completions - <Control-Key-space>',
11111121
'spam - <Shift-Key-a>')
11121122

1113-
# No current selection.
1123+
# No current selection: select the first item.
1124+
d.button_new_keys.state(('disabled',))
11141125
d.load_keys_list('my keys')
11151126
eq(b.get(0, 'end'), expected)
1116-
eq(b.get('anchor'), '')
1117-
eq(b.curselection(), ())
1127+
eq(b.get('anchor'), 'copy - <Control-Key-c> <Control-Key-C>')
1128+
eq(b.curselection(), (0, ))
1129+
eq(b.index('active'), 0)
1130+
self.assertNotIn('disabled', d.button_new_keys.state())
11181131

11191132
# Check selection.
11201133
b.selection_set(1)
@@ -1584,6 +1597,26 @@ def test_helplist_item_remove(self):
15841597
eq(fr.user_helplist, [])
15851598
self.assertTrue(fr.upc.called == fr.set.called == 1)
15861599

1600+
def test_helplist_item_remove_keyboard_selection(self):
1601+
# gh-75234: Up and Down keys move the active item, but not the
1602+
# anchor; the handler moves the anchor.
1603+
eq = self.assertEqual
1604+
fr = self.frame
1605+
fr.helplist.delete(0, 'end')
1606+
fr.helplist.insert('end', 'name1', 'name2')
1607+
fr.helplist.selection_anchor(0)
1608+
fr.helplist.selection_set(1)
1609+
fr.helplist.activate(1)
1610+
fr.user_helplist.clear()
1611+
fr.user_helplist.extend([('name1', 'file1'), ('name2', 'file2')])
1612+
fr.set.called = fr.upc.called = 0
1613+
1614+
fr.help_source_selected(SimpleNamespace(type=EventType.KeyRelease))
1615+
eq(fr.helplist.get('anchor'), 'name2')
1616+
fr.helplist_item_remove()
1617+
eq(fr.helplist.get(0, 'end'), ('name1',))
1618+
eq(fr.user_helplist, [('name1', 'file1')])
1619+
15871620
def test_update_help_changes(self):
15881621
fr = self.frame
15891622
self.addCleanup(setattr, fr, 'update_help_changes', Func()) # Re-mask method.

‎Lib/test/test_pyrepl/test_pyrepl.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,8 @@ def test_no_newline(self):
24312431
env = os.environ.copy()
24322432
env.pop("PYTHON_BASIC_REPL", "")
24332433
env["PYTHON_BASIC_REPL"] = "1"
2434+
# Ensure user's .inputrc doesn't interfere with basic REPL output
2435+
env["INPUTRC"] = "/dev/null"
24342436

24352437
commands = "print('Something pretty long', end='')\nexit()\n"
24362438
expected_output_sequence = "Something pretty long>>> exit()"

0 commit comments

Comments
 (0)