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 Doc/library/crypt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ being deprecated in Python 3.11. The removal was decided in :pep:`594`.

Applications can use the :mod:`hashlib` module from the standard library.
Other possible replacements are third-party libraries from PyPI:
:pypi:`legacycrypt`, :pypi:`bcrypt`, :pypi:`argon2-cffi`, or :pypi:`passlib`.
:pypi:`legacycrypt`, :pypi:`bcrypt`, or :pypi:`argon2-cffi`.
These are not supported or maintained by the Python core team.

The last version of Python that provided the :mod:`!crypt` module was
Expand Down
2 changes: 0 additions & 2 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1569,8 +1569,6 @@ and are now removed:

* :pypi:`bcrypt`:
Modern password hashing for your software and your servers.
* :pypi:`passlib`:
Comprehensive password hashing framework supporting over 30 schemes.
* :pypi:`argon2-cffi`:
The secure Argon2 password hashing algorithm.
* :pypi:`legacycrypt`:
Expand Down
19 changes: 11 additions & 8 deletions Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,17 @@ class delete(EditCommand):
def do(self) -> None:
r = self.reader
b = r.buffer
if (
r.pos == 0
and len(b) == 0 # this is something of a hack
and self.event[-1] == "\004"
):
r.update_screen()
r.console.finish()
raise EOFError
if self.event[-1] == "\004":
if b and b[-1].endswith("\n"):
self.finish = True
elif (
r.pos == 0
and len(b) == 0 # this is something of a hack
):
r.update_screen()
r.console.finish()
raise EOFError

for i in range(r.get_arg()):
if r.pos != len(b):
del b[r.pos]
Expand Down
7 changes: 6 additions & 1 deletion Lib/_pyrepl/unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,12 @@ def restore(self):
os.write(self.output_fd, b"\033[?7h")

if hasattr(self, "old_sigwinch"):
signal.signal(signal.SIGWINCH, self.old_sigwinch)
try:
signal.signal(signal.SIGWINCH, self.old_sigwinch)
except ValueError as e:
import threading
if threading.current_thread() is threading.main_thread():
raise e
del self.old_sigwinch

def push_char(self, char: int | bytes) -> None:
Expand Down
4 changes: 3 additions & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,9 @@ def add_subparsers(self, **kwargs):
# prog defaults to the usage message of this parser, skipping
# optional arguments and with no "usage:" prefix
if kwargs.get('prog') is None:
formatter = self._get_formatter()
# Create formatter without color to avoid storing ANSI codes in prog
formatter = self.formatter_class(prog=self.prog)
formatter._set_color(False)
positionals = self._get_positional_actions()
groups = self._mutually_exclusive_groups
formatter.add_usage(None, positionals, groups, '')
Expand Down
3 changes: 2 additions & 1 deletion Lib/asyncio/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def callback():
return
except BaseException:
if keyboard_interrupted:
self.write("\nKeyboardInterrupt\n")
if not CAN_USE_PYREPL:
self.write("\nKeyboardInterrupt\n")
else:
self.showtraceback()
return self.STATEMENT_FAILED
Expand Down
15 changes: 14 additions & 1 deletion Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
captured_stderr,
force_not_colorized,
force_not_colorized_test_class,
swap_attr,
)
from test.support import import_helper
from test.support import os_helper
Expand Down Expand Up @@ -7128,7 +7129,8 @@ class TestColorized(TestCase):
def setUp(self):
super().setUp()
# Ensure color even if ran with NO_COLOR=1
_colorize.can_colorize = lambda *args, **kwargs: True
self.enterContext(swap_attr(_colorize, 'can_colorize',
lambda *args, **kwargs: True))
self.theme = _colorize.get_theme(force_color=True).argparse

def test_argparse_color(self):
Expand Down Expand Up @@ -7355,6 +7357,17 @@ def __init__(self, prog):
{short_b}+f{reset}, {long_b}++foo{reset} {label_b}FOO{reset} foo help
'''))

def test_subparser_prog_is_stored_without_color(self):
parser = argparse.ArgumentParser(prog='complex', color=True)
sub = parser.add_subparsers(dest='command')
demo_parser = sub.add_parser('demo')

self.assertNotIn('\x1b[', demo_parser.prog)

demo_parser.color = False
help_text = demo_parser.format_help()
self.assertNotIn('\x1b[', help_text)


class TestModule(unittest.TestCase):
def test_deprecated__version__(self):
Expand Down
83 changes: 83 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1825,3 +1825,86 @@ def test_detect_pip_usage_in_repl(self):
" outside of the Python REPL"
)
self.assertIn(hint, output)

class TestPyReplCtrlD(TestCase):
"""Test Ctrl+D behavior in _pyrepl to match old pre-3.13 REPL behavior.

Ctrl+D should:
- Exit on empty buffer (raises EOFError)
- Delete character when cursor is in middle of line
- Perform no operation when cursor is at end of line without newline
- Exit multiline mode when cursor is at end with trailing newline
- Run code up to that point when pressed on blank line with preceding lines
"""
def prepare_reader(self, events):
console = FakeConsole(events)
config = ReadlineConfig(readline_completer=None)
reader = ReadlineAlikeReader(console=console, config=config)
return reader

def test_ctrl_d_empty_line(self):
"""Test that pressing Ctrl+D on empty line exits the program"""
events = [
Event(evt="key", data="\x04", raw=bytearray(b"\x04")), # Ctrl+D
]
reader = self.prepare_reader(events)
with self.assertRaises(EOFError):
multiline_input(reader)

def test_ctrl_d_multiline_with_new_line(self):
"""Test that pressing Ctrl+D in multiline mode with trailing newline exits multiline mode"""
events = itertools.chain(
code_to_events("def f():\n pass\n"), # Enter multiline mode with trailing newline
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04")), # Ctrl+D
],
)
reader, _ = handle_all_events(events)
self.assertTrue(reader.finished)
self.assertEqual("def f():\n pass\n", "".join(reader.buffer))

def test_ctrl_d_multiline_middle_of_line(self):
"""Test that pressing Ctrl+D in multiline mode with cursor in middle deletes character"""
events = itertools.chain(
code_to_events("def f():\n hello world"), # Enter multiline mode
[
Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))
] * 5, # move cursor to 'w' in "world"
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04"))
], # Ctrl+D should delete 'w'
)
reader, _ = handle_all_events(events)
self.assertFalse(reader.finished)
self.assertEqual("def f():\n hello orld", "".join(reader.buffer))

def test_ctrl_d_multiline_end_of_line_no_newline(self):
"""Test that pressing Ctrl+D at end of line without newline performs no operation"""
events = itertools.chain(
code_to_events("def f():\n hello"), # Enter multiline mode, no trailing newline
[
Event(evt="key", data="\x04", raw=bytearray(b"\x04"))
], # Ctrl+D should be no-op
)
reader, _ = handle_all_events(events)
self.assertFalse(reader.finished)
self.assertEqual("def f():\n hello", "".join(reader.buffer))

def test_ctrl_d_single_line_middle_of_line(self):
"""Test that pressing Ctrl+D in single line mode deletes current character"""
events = itertools.chain(
code_to_events("hello"),
[Event(evt="key", data="left", raw=bytearray(b"\x1bOD"))], # move left
[Event(evt="key", data="\x04", raw=bytearray(b"\x04"))], # Ctrl+D
)
reader, _ = handle_all_events(events)
self.assertEqual("hell", "".join(reader.buffer))

def test_ctrl_d_single_line_end_no_newline(self):
"""Test that pressing Ctrl+D at end of single line without newline does nothing"""
events = itertools.chain(
code_to_events("hello"), # cursor at end of line
[Event(evt="key", data="\x04", raw=bytearray(b"\x04"))], # Ctrl+D
)
reader, _ = handle_all_events(events)
self.assertEqual("hello", "".join(reader.buffer))
14 changes: 13 additions & 1 deletion Lib/test/test_pyrepl/test_unix_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import signal
import subprocess
import sys
import threading
import unittest
from functools import partial
from test import support
from test.support import os_helper, force_not_colorized_test_class
from test.support import script_helper
from test.support import script_helper, threading_helper

from unittest import TestCase
from unittest.mock import MagicMock, call, patch, ANY, Mock
Expand Down Expand Up @@ -318,6 +319,17 @@ def test_restore_with_invalid_environ_on_macos(self, _os_write):
console.prepare() # needed to call restore()
console.restore() # this should succeed

@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_restore_in_thread(self, _os_write):
# gh-139391: ensure that console.restore() silently suppresses
# exceptions when calling signal.signal() from a non-main thread.
console = unix_console([])
console.old_sigwinch = signal.SIG_DFL
thread = threading.Thread(target=console.restore)
thread.start()
thread.join() # this should not raise


@unittest.skipIf(sys.platform == "win32", "No Unix console on Windows")
class TestUnixConsoleEIOHandling(TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed Ctrl+D (^D) behavior in _pyrepl module to match old pre-3.13 REPL behavior.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix an issue when, on non-Windows platforms, it was not possible to
gracefully exit a ``python -m asyncio`` process suspended by Ctrl+Z
and later resumed by :manpage:`fg` other than with :manpage:`kill`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prevent premature colorization of subparser ``prog`` in :meth:`argparse.ArgumentParser.add_subparsers` to respect color environment variable changes after parser creation.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix to not print KeyboardInterrupt twice in default asyncio REPL.
Loading