Skip to content

Commit 7dd5e94

Browse files
Merge remote-tracking branch 'upstream/main' into curses-chtype-overflow
# Conflicts: # Modules/clinic/_cursesmodule.c.h
2 parents 2d7e971 + a85e73b commit 7dd5e94

29 files changed

Lines changed: 475 additions & 80 deletions

Doc/deprecations/soft-deprecations.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ There are no plans to remove :term:`soft deprecated` APIs.
1919

2020
(Contributed by Gregory P. Smith in :gh:`86519` and
2121
Hugo van Kemenade in :gh:`148100`.)
22+
23+
* Using ``'F'`` and ``'D'`` format type codes of the :mod:`struct` module
24+
now are :term:`soft deprecated` in favor of two-letter forms ``'Zf'``
25+
and ``'Zd'``.
26+
(Contributed by Sergey B Kirpichev in :gh:`121249`.)

Doc/library/curses.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,40 @@ The module :mod:`!curses` defines the following functions:
699699
Save the current state of the terminal modes in a buffer, usable by
700700
:func:`resetty`.
701701

702+
.. function:: scr_dump(filename)
703+
704+
Write the current contents of the virtual screen to *filename*, which may be
705+
a string or a :term:`path-like object`. The file can later be read by
706+
:func:`scr_restore`, :func:`scr_init` or :func:`scr_set`. This is the
707+
whole-screen counterpart of :meth:`window.putwin`.
708+
709+
.. versionadded:: next
710+
711+
.. function:: scr_restore(filename)
712+
713+
Set the virtual screen to the contents of *filename*, which must have been
714+
written by :func:`scr_dump`. The next call to :func:`doupdate` or
715+
:meth:`window.refresh` restores the screen to those contents.
716+
717+
.. versionadded:: next
718+
719+
.. function:: scr_init(filename)
720+
721+
Initialize the assumed contents of the terminal from *filename*, which must
722+
have been written by :func:`scr_dump`. Use it when the terminal already
723+
displays those contents, for example after another program has drawn the
724+
screen, so that curses does not redraw what is already there.
725+
726+
.. versionadded:: next
727+
728+
.. function:: scr_set(filename)
729+
730+
Use *filename*, which must have been written by :func:`scr_dump`, as both
731+
the virtual screen and the assumed terminal contents. This combines the
732+
effects of :func:`scr_restore` and :func:`scr_init`.
733+
734+
.. versionadded:: next
735+
702736
.. function:: get_escdelay()
703737

704738
Retrieves the value set by :func:`set_escdelay`.
@@ -1195,6 +1229,17 @@ Window objects
11951229
object for the derived window.
11961230

11971231

1232+
.. method:: window.dupwin()
1233+
1234+
Return a new window that is an exact duplicate of the window: it has the same
1235+
size, position, contents and attributes. Unlike a window created by
1236+
:meth:`subwin` or :meth:`derwin`, the duplicate is independent of the
1237+
original -- it has its own cell buffer, so later changes to one do not affect
1238+
the other.
1239+
1240+
.. versionadded:: next
1241+
1242+
11981243
.. method:: window.echochar(ch[, attr])
11991244

12001245
Add character *ch* with attribute *attr*, and immediately call :meth:`refresh`

Doc/library/struct.rst

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,6 @@ platform-dependent.
261261
+--------+--------------------------+--------------------+----------------+------------+
262262
| ``d`` | :c:expr:`double` | float | 8 | \(4) |
263263
+--------+--------------------------+--------------------+----------------+------------+
264-
| ``F`` | :c:expr:`float complex` | complex | 8 | \(10) |
265-
+--------+--------------------------+--------------------+----------------+------------+
266-
| ``D`` | :c:expr:`double complex` | complex | 16 | \(10) |
267-
+--------+--------------------------+--------------------+----------------+------------+
268264
| ``Zf`` | :c:expr:`float complex` | complex | 8 | \(10) |
269265
+--------+--------------------------+--------------------+----------------+------------+
270266
| ``Zd`` | :c:expr:`double complex` | complex | 16 | \(10) |
@@ -287,6 +283,7 @@ platform-dependent.
287283

288284
.. versionchanged:: 3.15
289285
Added support for the ``'Zf'`` and ``'Zd'`` formats.
286+
``'F'`` and ``'D'`` formats are :term:`soft deprecated`.
290287

291288
.. seealso::
292289

@@ -377,13 +374,15 @@ Notes:
377374
are accepted.
378375

379376
(10)
380-
For the ``'F'`` and ``'D'`` type codes, the packed representation uses
377+
For the ``'Zf'`` and ``'Zd'`` type codes, the packed representation uses
381378
the IEEE 754 binary32 and binary64 format for components of the complex
382379
number, regardless of the floating-point format used by the platform.
383-
Note that complex types (``F``/``Zf`` and ``D``/``Zd``) are available unconditionally,
380+
Note that complex types are available unconditionally,
384381
despite complex types being an optional feature in C.
385382
As specified in the C11 standard, each complex type is represented by a
386383
two-element C array containing, respectively, the real and imaginary parts.
384+
The ``'F'`` and ``'D'`` (for ``'Zf'`` and ``'Zd'``, respectively) format
385+
characters are supported for compatibility.
387386

388387

389388
A type code may be preceded by an integral repeat count. For example,

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,6 +2280,11 @@ New deprecations
22802280

22812281
(Contributed by Sergey B Kirpichev and Serhiy Storchaka in :gh:`143715`.)
22822282

2283+
* Using ``'F'`` and ``'D'`` type codes now are :term:`soft deprecated`
2284+
in favor of two-letter forms ``'Zf'`` and ``'Zd'``.
2285+
(Contributed by Sergey B Kirpichev in :gh:`121249`.)
2286+
2287+
22832288
* :mod:`typing`:
22842289

22852290
* The following statements now cause ``DeprecationWarning``\ s to be emitted

Doc/whatsnew/3.16.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,15 @@ curses
156156
accept a :class:`~curses.complexstr`.
157157
(Contributed by Serhiy Storchaka in :gh:`152233`.)
158158

159+
* Add the :mod:`curses` window method :meth:`~curses.window.dupwin`, which
160+
returns a new window that is an independent duplicate of an existing one.
161+
(Contributed by Serhiy Storchaka in :gh:`152258`.)
162+
163+
* Add the :mod:`curses` functions :func:`~curses.scr_dump`,
164+
:func:`~curses.scr_restore`, :func:`~curses.scr_init` and
165+
:func:`~curses.scr_set`, which dump the whole screen to a file and restore it.
166+
(Contributed by Serhiy Storchaka in :gh:`152260`.)
167+
159168
gzip
160169
----
161170

Lib/test/support/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"requires_gil_enabled", "requires_linux_version", "requires_mac_ver",
3636
"check_syntax_error",
3737
"requires_gzip", "requires_bz2", "requires_lzma", "requires_zstd",
38-
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
38+
"bigmemtest", "nomemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
3939
"requires_IEEE_754", "requires_zlib",
4040
"has_fork_support", "requires_fork",
4141
"has_subprocess_support", "requires_subprocess",
@@ -1305,6 +1305,22 @@ def wrapper(self):
13051305
return wrapper
13061306
return decorator
13071307

1308+
def nomemtest(f):
1309+
"""Check that we can use this test with `_testcapi.set_nomemory`."""
1310+
from .import_helper import import_module
1311+
1312+
@functools.wraps(f)
1313+
def internal(*args, **kwargs):
1314+
import_module('_testcapi')
1315+
return f(*args, **kwargs)
1316+
1317+
return unittest.skipIf(
1318+
# Python built with Py_TRACE_REFS fail with a fatal error in
1319+
# _PyRefchain_Trace() on memory allocation error.
1320+
Py_TRACE_REFS,
1321+
'cannot test Py_TRACE_REFS build',
1322+
)(cpython_only(internal))
1323+
13081324
def bigaddrspacetest(f):
13091325
"""Decorator for tests that fill the address space."""
13101326
def wrapper(self):

Lib/test/test_atexit.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,7 @@ def callback():
191191
self.assertEqual(os.read(r, len(expected)), expected)
192192
os.close(r)
193193

194-
# Python built with Py_TRACE_REFS fail with a fatal error in
195-
# _PyRefchain_Trace() on memory allocation error.
196-
@unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
194+
@support.nomemtest
197195
def test_atexit_with_low_memory(self):
198196
# gh-140080: Test that setting low memory after registering an atexit
199197
# callback doesn't cause an infinite loop during finalization.

Lib/test/test_capi/test_mem.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,7 @@ def test_pyobject_forbidden_bytes_is_freed(self):
117117
def test_pyobject_freed_is_freed(self):
118118
self.check_pyobject_is_freed('check_pyobject_freed_is_freed')
119119

120-
# Python built with Py_TRACE_REFS fail with a fatal error in
121-
# _PyRefchain_Trace() on memory allocation error.
122-
@unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
120+
@support.nomemtest
123121
def test_set_nomemory(self):
124122
code = """if 1:
125123
import _testcapi

Lib/test/test_class.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,6 @@ def __delattr__(self, *args):
449449

450450
def testHasAttrString(self):
451451
import sys
452-
from test.support import import_helper
453452
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
454453

455454
class A:
@@ -1013,11 +1012,8 @@ class C:
10131012
C.a = X()
10141013
C.a = X()
10151014

1016-
@cpython_only
1015+
@support.nomemtest
10171016
def test_detach_materialized_dict_no_memory(self):
1018-
# Skip test if _testcapi is not available:
1019-
import_helper.import_module('_testcapi')
1020-
10211017
code = """if 1:
10221018
import test.support
10231019
import _testcapi

Lib/test/test_curses.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,35 @@ def test_subwindows_references(self):
218218
del win2
219219
gc_collect()
220220

221+
def test_dupwin(self):
222+
win = curses.newwin(5, 10, 2, 3)
223+
win.addstr(0, 0, 'ABCDE')
224+
win.addstr(1, 0, 'fghij')
225+
dup = win.dupwin()
226+
# Same geometry and contents as the original.
227+
self.assertEqual(dup.getbegyx(), win.getbegyx())
228+
self.assertEqual(dup.getmaxyx(), win.getmaxyx())
229+
self.assertEqual(dup.instr(0, 0, 5), b'ABCDE')
230+
self.assertEqual(dup.instr(1, 0, 5), b'fghij')
231+
# The duplicate is independent, not a subwindow.
232+
if hasattr(dup, 'is_subwin'):
233+
self.assertIs(dup.is_subwin(), False)
234+
self.assertIsNone(dup.getparent())
235+
# Changes to one do not affect the other.
236+
dup.addstr(0, 0, 'xxxxx')
237+
win.addstr(1, 0, 'YYYYY')
238+
self.assertEqual(win.instr(0, 0, 5), b'ABCDE')
239+
self.assertEqual(dup.instr(0, 0, 5), b'xxxxx')
240+
self.assertEqual(dup.instr(1, 0, 5), b'fghij')
241+
self.assertEqual(win.instr(1, 0, 5), b'YYYYY')
242+
# A subwindow can also be duplicated; the duplicate is independent.
243+
sub = win.subwin(3, 5, 2, 3)
244+
subdup = sub.dupwin()
245+
self.assertEqual(subdup.getmaxyx(), sub.getmaxyx())
246+
if hasattr(subdup, 'is_subwin'):
247+
self.assertIs(subdup.is_subwin(), False)
248+
self.assertIsNone(subdup.getparent())
249+
221250
def test_move_cursor(self):
222251
stdscr = self.stdscr
223252
win = stdscr.subwin(10, 15, 2, 5)
@@ -1096,6 +1125,43 @@ def test_putwin(self):
10961125
self.assertEqual(win.getmaxyx(), (5, 12))
10971126
self.assertEqual(win.instr(2, 0), b' Lorem ipsum')
10981127

1128+
def test_scr_dump(self):
1129+
# Test scr_dump(), scr_restore(), scr_init() and scr_set().
1130+
# scr_dump() writes the virtual screen to a named file; the other three
1131+
# functions load it back. The dumped image is internal curses state,
1132+
# not a window, so the round-trip is checked by comparing dump files
1133+
# rather than reading cells.
1134+
stdscr = self.stdscr
1135+
stdscr.erase()
1136+
stdscr.addstr(0, 0, 'screen dump test')
1137+
stdscr.refresh()
1138+
with tempfile.TemporaryDirectory() as d:
1139+
dump = os.path.join(d, 'dump')
1140+
self.assertIsNone(curses.scr_dump(dump))
1141+
# Dumping the same screen again is deterministic.
1142+
dump2 = os.path.join(d, 'dump2')
1143+
curses.scr_dump(dump2)
1144+
with open(dump, 'rb') as f1, open(dump2, 'rb') as f2:
1145+
self.assertEqual(f1.read(), f2.read())
1146+
# scr_restore() reloads that virtual screen, so dumping it again
1147+
# reproduces the original file even after the screen has changed.
1148+
stdscr.erase()
1149+
stdscr.addstr(0, 0, 'something else')
1150+
stdscr.refresh()
1151+
self.assertIsNone(curses.scr_restore(dump))
1152+
restored = os.path.join(d, 'restored')
1153+
curses.scr_dump(restored)
1154+
with open(dump, 'rb') as f1, open(restored, 'rb') as f2:
1155+
self.assertEqual(f1.read(), f2.read())
1156+
# scr_init() and scr_set() accept a dump file and return None.
1157+
self.assertIsNone(curses.scr_init(dump))
1158+
self.assertIsNone(curses.scr_set(dump))
1159+
# A bytes (path-like) filename is accepted too.
1160+
curses.scr_dump(os.fsencode(dump))
1161+
# Restoring from a missing file is an error.
1162+
self.assertRaises(curses.error,
1163+
curses.scr_restore, os.path.join(d, 'nope'))
1164+
10991165
def test_borders_and_lines(self):
11001166
win = curses.newwin(5, 10, 5, 2)
11011167
win.border('|', '!', '-', '_',
@@ -1339,8 +1405,6 @@ def test_state_getters(self):
13391405
# Each is_*() getter returns the value set by the matching setter.
13401406
for setter, getter in [
13411407
('clearok', 'is_cleared'),
1342-
('idcok', 'is_idcok'),
1343-
('idlok', 'is_idlok'),
13441408
('keypad', 'is_keypad'),
13451409
('leaveok', 'is_leaveok'),
13461410
('nodelay', 'is_nodelay'),
@@ -1351,6 +1415,19 @@ def test_state_getters(self):
13511415
self.assertIs(getattr(stdscr, getter)(), True)
13521416
getattr(stdscr, setter)(False)
13531417
self.assertIs(getattr(stdscr, getter)(), False)
1418+
1419+
# idcok()/idlok() only take effect if the terminal can insert/delete
1420+
# characters/lines, so the getter reflects that capability.
1421+
stdscr.idcok(True)
1422+
self.assertIs(stdscr.is_idcok(), curses.has_ic())
1423+
stdscr.idcok(False)
1424+
self.assertIs(stdscr.is_idcok(), False)
1425+
1426+
stdscr.idlok(True)
1427+
self.assertIs(stdscr.is_idlok(),
1428+
curses.has_il() or curses.tigetstr('csr') is not None)
1429+
stdscr.idlok(False)
1430+
self.assertIs(stdscr.is_idlok(), False)
13541431
if hasattr(stdscr, 'immedok'):
13551432
stdscr.immedok(True)
13561433
self.assertIs(stdscr.is_immedok(), True)

0 commit comments

Comments
 (0)