Skip to content

Commit b75cfb1

Browse files
The-Compilerwebknjaz
authored andcommitted
Add readline workaround for libedit (#13176)
* Add readline workaround for libedit We had a very similar workaround before for pyreadline, which had a similar issue: - Introduced in #1281 - Removed in #8848 for #8733 and #8847 This technically will regress the issues above, but those issues just mean that `import readline` is broken in general, so the user should fix it instead (by e.g. uninstalling pyreadline). Fixes #12888 Fixes #13170 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Handle no readline on Windows --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> (cherry picked from commit b4009b3)
1 parent edbfff7 commit b75cfb1

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

changelog/12888.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed broken input when using Python 3.13+ and a ``libedit`` build of Python, such as on macOS or with uv-managed Python binaries from the ``python-build-standalone`` project. This could manifest e.g. by a broken prompt when using ``Pdb``, or seeing empty inputs with manual usage of ``input()`` and suspended capturing.

src/_pytest/capture.py

+18
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ def _colorama_workaround() -> None:
7979
pass
8080

8181

82+
def _readline_workaround() -> None:
83+
"""Ensure readline is imported early so it attaches to the correct stdio handles.
84+
85+
This isn't a problem with the default GNU readline implementation, but in
86+
some configurations, Python uses libedit instead (on macOS, and for prebuilt
87+
binaries such as used by uv).
88+
89+
In theory this is only needed if readline.backend == "libedit", but the
90+
workaround consists of importing readline here, so we already worked around
91+
the issue by the time we could check if we need to.
92+
"""
93+
try:
94+
import readline # noqa: F401
95+
except ImportError:
96+
pass
97+
98+
8299
def _windowsconsoleio_workaround(stream: TextIO) -> None:
83100
"""Workaround for Windows Unicode console handling.
84101
@@ -140,6 +157,7 @@ def pytest_load_initial_conftests(early_config: Config) -> Generator[None]:
140157
if ns.capture == "fd":
141158
_windowsconsoleio_workaround(sys.stdout)
142159
_colorama_workaround()
160+
_readline_workaround()
143161
pluginmanager = early_config.pluginmanager
144162
capman = CaptureManager(ns.capture)
145163
pluginmanager.register(capman, "capturemanager")

testing/test_capture.py

+30
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io
66
from io import UnsupportedOperation
77
import os
8+
import re
89
import subprocess
910
import sys
1011
import textwrap
@@ -1666,3 +1667,32 @@ def test_logging():
16661667
)
16671668
result.stdout.no_fnmatch_line("*Captured stderr call*")
16681669
result.stdout.no_fnmatch_line("*during collection*")
1670+
1671+
1672+
def test_libedit_workaround(pytester: Pytester) -> None:
1673+
pytester.makeconftest("""
1674+
import pytest
1675+
1676+
1677+
def pytest_terminal_summary(config):
1678+
capture = config.pluginmanager.getplugin("capturemanager")
1679+
capture.suspend_global_capture(in_=True)
1680+
1681+
print("Enter 'hi'")
1682+
value = input()
1683+
print(f"value: {value!r}")
1684+
1685+
capture.resume_global_capture()
1686+
""")
1687+
readline = pytest.importorskip("readline")
1688+
backend = getattr(readline, "backend", readline.__doc__) # added in Python 3.13
1689+
print(f"Readline backend: {backend}")
1690+
1691+
child = pytester.spawn_pytest("")
1692+
child.expect(r"Enter 'hi'")
1693+
child.sendline("hi")
1694+
rest = child.read().decode("utf8")
1695+
print(rest)
1696+
match = re.search(r"^value: '(.*)'\r?$", rest, re.MULTILINE)
1697+
assert match is not None
1698+
assert match.group(1) == "hi"

0 commit comments

Comments
 (0)