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
7 changes: 6 additions & 1 deletion Lib/_pyrepl/historical_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,14 @@ def suspend(self) -> SimpleContextManager:

@contextmanager
def suspend_history(self) -> SimpleContextManager:
with self.suspend_history_writes():
del self.history[:]
yield

@contextmanager
def suspend_history_writes(self) -> SimpleContextManager:
try:
old_history = self.history[:]
del self.history[:]
yield
finally:
self.history[:] = old_history
Expand Down
3 changes: 2 additions & 1 deletion Lib/_pyrepl/readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ def input(self, prompt: object = "") -> str:
prompt_str = str(prompt)
reader.ps1 = prompt_str
sys.audit("builtins.input", prompt_str)
result = reader.readline(startup_hook=self.startup_hook)
with reader.suspend_history_writes():
result = reader.readline(startup_hook=self.startup_hook)
sys.audit("builtins.input/result", result)
return result

Expand Down
58 changes: 58 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,64 @@ def test_get_line_buffer_returns_str(self):
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)
self.assertIs(type(wrapper.get_line_buffer()), str)

def test_input_does_not_add_history(self):
for history in ([], ["previous input"]):
with self.subTest(history=history):
reader = self.prepare_reader(
code_to_events("first input\nsecond input\n1+1\n")
)
reader.history[:] = history
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)

self.assertEqual(wrapper.input(), "first input")
self.assertEqual(reader.history, history)
self.assertEqual(wrapper.input(), "second input")
self.assertEqual(reader.history, history)
self.assertEqual(multiline_input(reader), "1+1")
self.assertEqual(reader.history, [*history, "1+1"])

def test_input_can_read_history(self):
events = [
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
Event(evt="key", data="\n", raw=bytearray(b"\n")),
]
reader = self.prepare_reader(events)
reader.history.append("previous input")
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)

self.assertEqual(wrapper.input(), "previous input")
self.assertEqual(reader.history, ["previous input"])

def test_input_does_not_modify_history(self):
events = itertools.chain(
[Event(evt="key", data="up", raw=bytearray(b"\x1bOA"))],
code_to_events(" edited"),
[Event(evt="key", data="down", raw=bytearray(b"\x1bOB"))],
code_to_events("new input\n"),
)
reader = self.prepare_reader(events)
reader.history.append("previous input")
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)

self.assertEqual(wrapper.input(), "new input")
self.assertEqual(reader.history, ["previous input"])

def test_input_error_does_not_modify_history(self):
for text, exception in (
("partial input\x03", KeyboardInterrupt),
("\x04", EOFError),
):
with self.subTest(exception=exception):
reader = self.prepare_reader(code_to_events(text + "1+1\n"))
reader.history.append("previous input")
wrapper = _ReadlineWrapper(f_in=None, f_out=None, reader=reader)

with self.assertRaises(exception):
wrapper.input()
self.assertEqual(reader.history, ["previous input"])
self.assertEqual(multiline_input(reader), "1+1")
self.assertEqual(reader.history, ["previous input", "1+1"])

def test_multiline_edit(self):
events = itertools.chain(
code_to_events("def f():\n...\n\n"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prevent ``input`` calls in PyREPL from modifying readline history.
Loading