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,188 changes: 1,062 additions & 1,126 deletions Doc/library/curses.rst

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Doc/library/tkinter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1056,11 +1056,11 @@ Base and mixin classes
:class:`int`.
Raise :exc:`ValueError` if *s* is not a valid integer.

.. method:: getvar(name='PY_VAR')
.. method:: getvar(name)

Return the value of the Tcl global variable named *name*.

.. method:: setvar(name='PY_VAR', value='1')
.. method:: setvar(name, value)

Set the Tcl global variable named *name* to *value*.

Expand Down Expand Up @@ -1524,10 +1524,10 @@ Base and mixin classes
This updates the display of windows, for example after geometry changes,
but does not process events caused by the user.

.. method:: waitvar(name='PY_VAR')
.. method:: waitvar(name)
:no-typesetting:

.. method:: wait_variable(name='PY_VAR')
.. method:: wait_variable(name)

Wait until the Tcl variable *name* is modified, continuing to process
events in the meantime so that the application stays responsive.
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,14 @@ Porting to Python 3.16
This section lists previously described changes and other bugfixes
that may require changes to your code.

* In :mod:`tkinter`, the *name* parameter of the
:meth:`~tkinter.Misc.wait_variable`, :meth:`~tkinter.Misc.setvar` and
:meth:`~tkinter.Misc.getvar` methods and the *value* parameter of
:meth:`!setvar` are now required. Calling these methods without
them, which formerly defaulted to ``'PY_VAR'`` and ``'1'``, now raises
:exc:`TypeError`.
(Contributed by Serhiy Storchaka in :gh:`152587`.)


Build changes
=============
Expand Down
6 changes: 5 additions & 1 deletion Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ typedef struct {
char *entries[1];
} _PyCodeArray;

#define _PyCode_DEF_UNIQUE_ID() \
Py_ssize_t _co_unique_id; /* ID used for per-thread refcounting */

#define _PyCode_DEF_THREAD_LOCAL_BYTECODE() \
_PyCodeArray *co_tlbc;
#else
#define _PyCode_DEF_UNIQUE_ID()
#define _PyCode_DEF_THREAD_LOCAL_BYTECODE()
#endif

Expand Down Expand Up @@ -101,7 +105,7 @@ typedef struct {
_PyCoCached *_co_cached; /* cached co_* attributes */ \
uintptr_t _co_instrumentation_version; /* current instrumentation version */ \
struct _PyCoMonitoringData *_co_monitoring; /* Monitoring data */ \
Py_ssize_t _co_unique_id; /* ID used for per-thread refcounting */ \
_PyCode_DEF_UNIQUE_ID() \
int _co_firsttraceable; /* index of first traceable instruction */ \
/* Scratch space for extra data relating to the code object. \
Type is a void* to keep the format private in codeobject.c to force \
Expand Down
3 changes: 2 additions & 1 deletion Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2534,7 +2534,8 @@ def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,
tarinfo, unfiltered = self._get_extract_tarinfo(
member, filter_function, path)
if tarinfo is not None:
self._extract_one(tarinfo, path, set_attrs, numeric_owner)
self._extract_one(tarinfo, path, set_attrs, numeric_owner,
filter_function=filter_function)

def _get_extract_tarinfo(self, member, filter_function, path):
"""Get (filtered, unfiltered) TarInfos from *member*
Expand Down
23 changes: 14 additions & 9 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1563,7 +1563,8 @@ def test_env_queries(self):
self.assertIsInstance(curses.has_ic(), bool)
self.assertIsInstance(curses.has_il(), bool)
self.assertIsInstance(curses.termattrs(), int)
self.assertIsInstance(curses.term_attrs(), int)
if hasattr(curses, 'term_attrs'):
self.assertIsInstance(curses.term_attrs(), int)

c = curses.killchar()
self.assertIsInstance(c, bytes)
Expand Down Expand Up @@ -2365,15 +2366,19 @@ def test_textbox_combining(self):
self.assertEqual(box.gather(), text + ' ')

def test_textbox_edit_wide(self):
# edit() reads characters through get_wch(). Each is used only if
# encodable in the current locale.
# edit() reads characters through get_wch(). Each character is pushed
# with unget_wch(), which on a narrow build requires it to encode to a
# single byte, so a non-ASCII case needs a wide build or an 8-bit locale.
for ch in ['A', 'é', '¤', '€', 'д']:
if self._encodable(ch):
with self.subTest(ch=ch):
box, win = self._make_textbox(1, 10)
for c in reversed(['a', ch, chr(curses.ascii.BEL)]):
curses.unget_wch(c)
self.assertEqual(box.edit(), 'a' + ch + ' ')
if not self._encodable(ch):
continue
if not WIDE_BUILD and len(ch.encode(self.stdscr.encoding)) != 1:
continue
with self.subTest(ch=ch):
box, win = self._make_textbox(1, 10)
for c in reversed(['a', ch, chr(curses.ascii.BEL)]):
curses.unget_wch(c)
self.assertEqual(box.edit(), 'a' + ch + ' ')

def test_textbox_movement(self):
box, win = self._make_textbox(3, 10)
Expand Down
8 changes: 5 additions & 3 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,7 @@ def module_search_paths(self, prefix=None, exec_prefix=None):
if prefix is None:
prefix = config['config']['prefix']
if exec_prefix is None:
exec_prefix = config['config']['prefix']
exec_prefix = config['config']['exec_prefix']
if MS_WINDOWS:
return config['config']['module_search_paths']
else:
Expand Down Expand Up @@ -1614,8 +1614,10 @@ def test_init_is_python_build_with_home(self):
expected_paths[1 if MS_WINDOWS else 2] = os.path.normpath(
os.path.join(exedir, f'{f.read()}\n$'.splitlines()[0]))
if not MS_WINDOWS:
# PREFIX (default) is set when running in build directory
prefix = exec_prefix = sys.prefix
# PREFIX and EXEC_PREFIX (defaults) are set when running in the
# build directory and may differ with --exec-prefix (gh-151096).
prefix = sys.prefix
exec_prefix = sys.exec_prefix
# stdlib calculation (/Lib) is not yet supported
expected_paths[0] = self.module_search_paths(prefix=prefix)[0]
config.update(prefix=prefix, base_prefix=prefix,
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,21 @@ def test_replace_id(self):
text = 'abc def'
self.assertIs(text.replace(pattern, pattern), text)

@support.nomemtest
def test_replace_oom(self):
# https://github.com/python/cpython/issues/152228
s1 = "轘" * 4
s2 = "&"
s3 = "&"
assertion = self.assertRaises(MemoryError)
_testcapi.set_nomemory(0, 0)
try:
# No allocations made in the test itself:
with assertion:
s1.replace(s2, s3) # this line used to crash before
finally:
_testcapi.remove_mem_hooks()

def test_repeat_id_preserving(self):
a = '123abc1@'
b = '456zyx-+'
Expand Down
92 changes: 92 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4539,6 +4539,98 @@ def test_chmod_outside_dir(self):
st_mode = cc.outerdir.stat().st_mode
self.assertNotEqual(st_mode & 0o777, 0o777)

@symlink_test
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
def test_chown_links_on_extract(self, link_type):
with ArchiveMaker() as arc:
arc.add("test.txt",
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
arc.add("link",
type=link_type,
linkname='test.txt',
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')

with (
os_helper.temp_dir() as tmpdir,
arc.open() as tar,
unittest.mock.patch("os.chown") as mock_chown,
unittest.mock.patch("os.lchown") as mock_lchown,
unittest.mock.patch("os.geteuid") as mock_geteuid,
):
# Set UID to 0 so chown() is attempted.
mock_geteuid.return_value = 0
tar.extract("link", path=tmpdir, filter='data')
extract_path = os.path.join(tmpdir, "link")

if link_type == tarfile.SYMTYPE:
mock_chown.assert_not_called()
mock_lchown.assert_called_once_with(extract_path, -1, -1)
else:
mock_chown.assert_has_calls([
unittest.mock.call(extract_path, -1, -1),
unittest.mock.call(extract_path, -1, -1)
])
mock_lchown.assert_not_called()

@symlink_test
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
def test_chown_links_on_extractall(self, link_type):
with ArchiveMaker() as arc:
arc.add("test.txt",
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
arc.add("link",
type=link_type,
linkname='test.txt',
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')

with (
os_helper.temp_dir() as tmpdir,
arc.open() as tar,
unittest.mock.patch("os.chown") as mock_chown,
unittest.mock.patch("os.lchown") as mock_lchown,
unittest.mock.patch("os.geteuid") as mock_geteuid,
):
# Set UID to 0 so chown() is attempted.
mock_geteuid.return_value = 0
tar.extractall(path=tmpdir, filter='data')
extract_link_path = os.path.join(tmpdir, "link")
extract_file_path = os.path.join(tmpdir, "test.txt")

if link_type == tarfile.SYMTYPE:
mock_chown.assert_called_once_with(extract_file_path, -1, -1)
mock_lchown.assert_called_once_with(extract_link_path, -1, -1)
else:
mock_chown.assert_has_calls([
unittest.mock.call(extract_file_path, -1, -1),
unittest.mock.call(extract_link_path, -1, -1)
])
mock_lchown.assert_not_called()

def test_extract_filters_target(self):
# Test that when extract() falls back to extracting (rather than
# linking) a hardlink target, it filters the target.
with ArchiveMaker() as arc:
arc.add("target")
arc.add("link", hardlink_to="target")
def testing_filter(member, path):
if member.name == 'target':
# target: set read-only
return member.replace(mode=stat.S_IRUSR)
# link: don't overwrite the mode
return member.replace(mode=None)
tempdir = pathlib.Path(TEMPDIR) / 'extract'
with os_helper.temp_dir(tempdir), arc.open() as tar:
tar.extract("link", path=tempdir, filter=testing_filter)
path = tempdir / 'link'
if os_helper.can_chmod():
self.assertFalse(path.stat().st_mode & stat.S_IWUSR)

def test_link_fallback_normalizes(self):
# Make sure hardlink fallbacks work for non-normalized paths for all
# filters
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ def test_getdouble(self):
def test_getvar(self):
self.root.setvar('test_var', 'hello')
self.assertEqual(self.root.getvar('test_var'), 'hello')
# The name and value are required (gh-152587).
self.assertRaises(TypeError, self.root.getvar)
self.assertRaises(TypeError, self.root.setvar)
self.assertRaises(TypeError, self.root.setvar, 'test_var')

def test_register(self):
result = []
Expand Down Expand Up @@ -598,6 +602,8 @@ def test_wait_variable(self):
self.root.after(1, var.set, 'done')
self.root.wait_variable(var) # Returns once the variable is set.
self.assertEqual(var.get(), 'done')
# The name is required (gh-152587).
self.assertRaises(TypeError, self.root.wait_variable)

def test_wait_window(self):
top = tkinter.Toplevel(self.root)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_tkinter/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1994,7 +1994,7 @@ def test_selection_event(self):
lb = self.create(selectmode='browse', exportselection=False)
lb.insert(0, *('el%d' % i for i in range(5)))
lb.pack()
lb.update()
self.require_mapped(lb)
events = []
lb.bind('<<ListboxSelect>>', lambda e: events.append(lb.curselection()))
lb.focus_force()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_ttk/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1977,7 +1977,7 @@ def test_virtual_events(self):
self.tv.insert(parent, 'end')
item2 = self.tv.insert('', 'end')
self.tv.pack()
self.tv.update()
self.require_mapped(self.tv)
selects, opens, closes = [], [], []
self.tv.bind('<<TreeviewSelect>>',
lambda e: selects.append(self.tv.selection()))
Expand Down
6 changes: 3 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ def tk_inactive(self, reset=False, *, displayof=0):
else:
return self.tk.getint(self.tk.call(args))

def wait_variable(self, name='PY_VAR'):
def wait_variable(self, name):
"""Wait until the variable is modified.

A parameter of type IntVar, StringVar, DoubleVar or
Expand All @@ -843,11 +843,11 @@ def wait_visibility(self, window=None):
window = self
self.tk.call('tkwait', 'visibility', window._w)

def setvar(self, name='PY_VAR', value='1'):
def setvar(self, name, value):
"""Set Tcl variable NAME to VALUE."""
self.tk.setvar(name, value)

def getvar(self, name='PY_VAR'):
def getvar(self, name):
"""Return value of Tcl variable NAME."""
return self.tk.getvar(name)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The :mod:`curses` module now detects its optional functions with
:program:`configure` capability probes instead of assuming ncurses, so it
builds against narrow (non-wide) ncurses and other curses implementations such
as NetBSD curses, exposing exactly the functions they provide.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix an assertion failure when python is built in a debug mode
that happened in :meth:`str.replace` under a limited memory situation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :meth:`curses.window.in_wch`, :meth:`curses.window.in_wchstr` and
:meth:`curses.window.getbkgrnd` returning garbage text when :mod:`curses` is
built against a curses library that does not NUL-terminate the ``cchar_t``
text array (such as NetBSD curses).
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
In :mod:`tkinter`, the *name* parameter of the
:meth:`~tkinter.Misc.wait_variable`, :meth:`~tkinter.Misc.setvar` and
:meth:`~tkinter.Misc.getvar` methods and the *value* parameter of
:meth:`!setvar` are now required. Their former default values
(``'PY_VAR'`` and ``'1'``) were not meaningful.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The :meth:`tarfile.TarFile.extract` method now applies the given filter when
it extracts a link target from the archive as a fallback.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix ``test_embed`` failing when CPython is configured with a split exec prefix
(``--exec-prefix`` differing from ``--prefix``).
Loading
Loading