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
2 changes: 1 addition & 1 deletion .github/workflows/reusable-wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-26.04-arm
timeout-minutes: 60
env:
WASMTIME_VERSION: 38.0.3
WASMTIME_VERSION: 48.0.2
CROSS_BUILD_WASI: cross-build/wasm32-wasip1
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down
9 changes: 9 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,15 @@ Generally you only use this feature if you receive a pointer from a C function,
and you *know* that the pointer actually points to an array instead of a single
item.

.. warning::

Because pointer objects support subscription, they implicitly support
:term:`iteration <iterator>`. Unless doing this in a controlled manner,
such as by manually calling :func:`next` on a :func:`pointer` iterator, this
will typically lead to infinite loops or crashes, because ctypes has no way
of knowing when to stop iteration. In other words, a ``pointer`` iterator
will infinitely yield arbitrary memory.

Behind the scenes, the :func:`pointer` function does more than simply create
pointer instances, it has to create pointer *types* first. This is done with the
:func:`POINTER` function, which accepts any :mod:`!ctypes` type, and returns a
Expand Down
16 changes: 8 additions & 8 deletions Lib/_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,15 +564,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
del err
bad_directive = bad_directive.replace('\\s', '')
if not bad_directive:
raise ValueError("stray %% in format '%s'" % format) from None
raise ValueError(f"stray % in format {format!r}") from None
bad_directive = bad_directive.replace('\\', '', 1)
raise ValueError("'%s' is a bad directive in format '%s'" %
(bad_directive, format)) from None
raise ValueError(f"{bad_directive!r} is a bad directive "
f"in format {format!r}") from None
_regex_cache[format] = format_regex
found = format_regex.match(data_string)
if not found:
raise ValueError("time data %r does not match format %r" %
(data_string, format))
raise ValueError(f"time data {data_string!r} does not match "
f"format {format!r}")
if len(data_string) != found.end():
rest = data_string[found.end():]
# Specific check for '%:z' directive
Expand All @@ -582,9 +582,9 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
and rest[0] != ":"
):
raise ValueError(
f"Missing colon in %:z before '{rest}', got '{data_string}'"
f"Missing colon in %:z before {rest!r}, got {data_string!r}"
)
raise ValueError("unconverted data remains: %s" % rest)
raise ValueError(f"unconverted data remains: {rest!r}")

iso_year = year = None
month = day = 1
Expand Down Expand Up @@ -700,7 +700,7 @@ def parse_int(s):
z = z[:3] + z[4:]
if len(z) > 5:
if z[5] != ':':
msg = f"Inconsistent use of : in {found_dict[group_key]}"
msg = f"Inconsistent use of : in {found_dict[group_key]!r}"
raise ValueError(msg)
z = z[:5] + z[6:]
hours = int(z[1:3])
Expand Down
41 changes: 30 additions & 11 deletions Lib/idlelib/configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -1146,8 +1146,8 @@ def create_page_keys(self):
selected keyset. The keybindings are loaded in load_keys_list()
and are pairs of (event, [keys]) where keys can be a list
of one or more key combinations to bind to the same event.
Mouse button 1 click invokes on_bindingslist_select(), which
allows button_new_keys to be clicked.
Mouse button 1 click or Up or Down key invokes
on_bindingslist_select(), which allows button_new_keys to be clicked.

So, an item is selected in listbindings, which activates
button_new_keys, and clicking button_new_keys calls function
Expand Down Expand Up @@ -1221,9 +1221,12 @@ def create_page_keys(self):
scroll_target_y = Scrollbar(frame_target)
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
self.bindingslist = Listbox(
frame_target, takefocus=FALSE, exportselection=FALSE)
frame_target, takefocus=True, exportselection=FALSE)
self.bindingslist.bind('<ButtonRelease-1>',
self.on_bindingslist_select)
self.bindingslist.bind('<KeyRelease-Up>', self.on_bindingslist_select)
self.bindingslist.bind('<KeyRelease-Down>',
self.on_bindingslist_select)
scroll_target_y['command'] = self.bindingslist.yview
scroll_target_x['command'] = self.bindingslist.xview
self.bindingslist['yscrollcommand'] = scroll_target_y.set
Expand Down Expand Up @@ -1427,7 +1430,14 @@ def save_as_new_key_set(self):
self.create_new_key_set(new_keys_name)

def on_bindingslist_select(self, event):
"Activate button to assign new keys to selected action."
"""Activate button to assign new keys to selected action.

Event can result from either mouse click or Up or Down key.
The keys move the selection, but not the anchor used by
get_new_keys and var_changed_keybinding.
"""
if event.type.name == 'KeyRelease':
self.bindingslist.selection_anchor(ACTIVE)
self.button_new_keys.state(('!disabled',))

def create_new_key_set(self, new_key_set_name):
Expand Down Expand Up @@ -1465,9 +1475,8 @@ def load_keys_list(self, keyset_name):

An action/key binding can be selected to change the key binding.
"""
reselect = False
list_index = 0
if self.bindingslist.curselection():
reselect = True
list_index = self.bindingslist.index(ANCHOR)
keyset = idleConf.GetKeySet(keyset_name)
# 'set' is dict mapping virtual event to list of key events.
Expand All @@ -1482,10 +1491,11 @@ def load_keys_list(self, keyset_name):
if bind_name in changes['keys'][keyset_name]:
key = changes['keys'][keyset_name][bind_name]
self.bindingslist.insert(END, bind_name+' - '+key)
if reselect:
self.bindingslist.see(list_index)
self.bindingslist.select_set(list_index)
self.bindingslist.select_anchor(list_index)
self.bindingslist.see(list_index)
self.bindingslist.select_set(list_index)
self.bindingslist.select_anchor(list_index)
self.bindingslist.activate(list_index)
self.button_new_keys.state(('!disabled',))

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

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

def help_source_selected(self, event):
"Handle event for selecting additional help."
"""Handle event for selecting additional help.

Event can result from either mouse click or Up or Down key.
The keys move the selection, but not the anchor used by
helplist_item_edit and helplist_item_remove.
"""
if event.type.name == 'KeyRelease':
self.helplist.selection_anchor(ACTIVE)
self.set_add_delete_state()

def set_add_delete_state(self):
Expand Down
41 changes: 37 additions & 4 deletions Lib/idlelib/idle_test/test_configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import unittest
from unittest import mock
from idlelib.idle_test.mock_idle import Func
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL,
EventType)
from types import SimpleNamespace
from idlelib import config
from idlelib.configdialog import idleConf, changes, tracers

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

# gh-75234: Up and Down keys move the active item, but not the
# anchor; the handler moves the anchor.
d.button_new_keys.state(('disabled',))
b.activate(0)
d.on_bindingslist_select(SimpleNamespace(type=EventType.KeyRelease))
self.assertEqual(b.get('anchor'), 'copy')
self.assertNotIn('disabled', d.button_new_keys.state())

def test_create_new_key_set_and_save_new_key_set(self):
eq = self.assertEqual
d = self.page
Expand Down Expand Up @@ -1110,11 +1120,14 @@ def test_load_keys_list(self):
'force-open-completions - <Control-Key-space>',
'spam - <Shift-Key-a>')

# No current selection.
# No current selection: select the first item.
d.button_new_keys.state(('disabled',))
d.load_keys_list('my keys')
eq(b.get(0, 'end'), expected)
eq(b.get('anchor'), '')
eq(b.curselection(), ())
eq(b.get('anchor'), 'copy - <Control-Key-c> <Control-Key-C>')
eq(b.curselection(), (0, ))
eq(b.index('active'), 0)
self.assertNotIn('disabled', d.button_new_keys.state())

# Check selection.
b.selection_set(1)
Expand Down Expand Up @@ -1584,6 +1597,26 @@ def test_helplist_item_remove(self):
eq(fr.user_helplist, [])
self.assertTrue(fr.upc.called == fr.set.called == 1)

def test_helplist_item_remove_keyboard_selection(self):
# gh-75234: Up and Down keys move the active item, but not the
# anchor; the handler moves the anchor.
eq = self.assertEqual
fr = self.frame
fr.helplist.delete(0, 'end')
fr.helplist.insert('end', 'name1', 'name2')
fr.helplist.selection_anchor(0)
fr.helplist.selection_set(1)
fr.helplist.activate(1)
fr.user_helplist.clear()
fr.user_helplist.extend([('name1', 'file1'), ('name2', 'file2')])
fr.set.called = fr.upc.called = 0

fr.help_source_selected(SimpleNamespace(type=EventType.KeyRelease))
eq(fr.helplist.get('anchor'), 'name2')
fr.helplist_item_remove()
eq(fr.helplist.get(0, 'end'), ('name1',))
eq(fr.user_helplist, [('name1', 'file1')])

def test_update_help_changes(self):
fr = self.frame
self.addCleanup(setattr, fr, 'update_help_changes', Func()) # Re-mask method.
Expand Down
11 changes: 9 additions & 2 deletions Lib/test/test_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ def test_ValueError(self):
directive = bad_format[1:].rstrip()
with (self.subTest(format=bad_format),
self.assertRaisesRegex(ValueError,
f"'{re.escape(directive)}' is a bad directive in format ")):
f"{re.escape(repr(directive))} is a bad directive "
f"in format ")):
_strptime._strptime_time("2005", bad_format)

msg_week_no_year_or_weekday = r"ISO week directive '%V' must be used with " \
Expand Down Expand Up @@ -303,6 +304,11 @@ def test_unconverteddata(self):
# Check ValueError is raised when there is unconverted data
self.assertRaises(ValueError, _strptime._strptime_time, "10 12", "%m")

# gh-141540: a trailing newline must be visible in the message
with self.assertRaisesRegex(ValueError,
r"unconverted data remains: '\\n'"):
_strptime._strptime_time("2001-02-03\n", "%Y-%m-%d")

def roundtrip(self, fmt, position, time_tuple=None):
"""Helper fxn in testing."""
if time_tuple is None:
Expand Down Expand Up @@ -451,7 +457,8 @@ def test_bad_offset(self):

with self.assertRaises(ValueError) as err:
_strptime._strptime("-01:3030", "%z")
self.assertEqual("Inconsistent use of : in -01:3030", str(err.exception))
self.assertEqual("Inconsistent use of : in '-01:3030'",
str(err.exception))
with self.assertRaises(ValueError) as err:
_strptime._strptime("-01:3030", "%:z")
self.assertEqual("Missing colon in %:z before '30', got '-01:3030'",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support WASI SDK 34.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix editing help sources and key bindings in the IDLE Settings dialog after
selecting them with the keyboard.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Quote the unconverted data and the format in the error messages of
:func:`time.strptime` and :meth:`datetime.datetime.strptime` so that
whitespace such as a trailing newline is visible.
4 changes: 4 additions & 0 deletions Platforms/WASI/config.site-wasm32-wasi
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ ac_cv_func_fchmod=no
ac_cv_func_fchmodat=no
ac_cv_func_statvfs=no
ac_cv_func_fstatvfs=no

# WASI SDK 34 declares some things that simply error out.
ac_cv_func_pipe2=no
ac_cv_func_pthread_getcpuclockid=no
2 changes: 1 addition & 1 deletion Platforms/WASI/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# This allows for blanket copying of the WASI build code between supported
# Python versions.
[targets]
wasi-sdk = 33
wasi-sdk = 34
host-triple = "wasm32-wasip1"
Loading