Skip to content

Commit b4009b3

Browse files
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>
1 parent 31fd03a commit b4009b3

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
@@ -80,6 +80,23 @@ def _colorama_workaround() -> None:
8080
pass
8181

8282

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

testing/test_capture.py

+30
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io
77
from io import UnsupportedOperation
88
import os
9+
import re
910
import subprocess
1011
import sys
1112
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)