Skip to content

Commit 08b7417

Browse files
Fix handling of KeyboardInterrupt in REPL during evaluation of __repr__.
This fixes the issue that if calling `__repr__`, `__pt_repr__` or formatting the output using "Black" takes too long and the uses presses control-C, that we don't terminate the REPL by mistake.
1 parent 5af2dac commit 08b7417

File tree

1 file changed

+69
-45
lines changed

1 file changed

+69
-45
lines changed

ptpython/repl.py

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -102,30 +102,39 @@ def run(self) -> None:
102102

103103
try:
104104
while True:
105-
# Read.
106105
try:
107-
text = self.read()
108-
except EOFError:
109-
return
110-
111-
# Eval.
112-
try:
113-
result = self.eval(text)
114-
except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
106+
# Read.
107+
try:
108+
text = self.read()
109+
except EOFError:
110+
return
111+
112+
# Eval.
113+
try:
114+
result = self.eval(text)
115+
except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
116+
raise
117+
except SystemExit:
118+
return
119+
except BaseException as e:
120+
self._handle_exception(e)
121+
else:
122+
# Print.
123+
if result is not None:
124+
self.show_result(result)
125+
126+
# Loop.
127+
self.current_statement_index += 1
128+
self.signatures = []
129+
130+
except KeyboardInterrupt as e:
131+
# Handle all possible `KeyboardInterrupt` errors. This can
132+
# happen during the `eval`, but also during the
133+
# `show_result` if something takes too long.
134+
# (Try/catch is around the whole block, because we want to
135+
# prevent that a Control-C keypress terminates the REPL in
136+
# any case.)
115137
self._handle_keyboard_interrupt(e)
116-
except SystemExit:
117-
return
118-
except BaseException as e:
119-
self._handle_exception(e)
120-
else:
121-
# Print.
122-
if result is not None:
123-
self.show_result(result)
124-
125-
# Loop.
126-
self.current_statement_index += 1
127-
self.signatures = []
128-
129138
finally:
130139
if self.terminal_title:
131140
clear_title()
@@ -152,31 +161,38 @@ async def run_async(self) -> None:
152161

153162
try:
154163
while True:
155-
# Read.
156164
try:
157-
text = await loop.run_in_executor(None, self.read)
158-
except EOFError:
159-
return
160-
161-
# Eval.
162-
try:
163-
result = await self.eval_async(text)
164-
except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
165+
# Read.
166+
try:
167+
text = await loop.run_in_executor(None, self.read)
168+
except EOFError:
169+
return
170+
171+
# Eval.
172+
try:
173+
result = await self.eval_async(text)
174+
except KeyboardInterrupt as e: # KeyboardInterrupt doesn't inherit from Exception.
175+
raise
176+
except SystemExit:
177+
return
178+
except BaseException as e:
179+
self._handle_exception(e)
180+
else:
181+
# Print.
182+
if result is not None:
183+
await loop.run_in_executor(
184+
None, lambda: self.show_result(result)
185+
)
186+
187+
# Loop.
188+
self.current_statement_index += 1
189+
self.signatures = []
190+
191+
except KeyboardInterrupt as e:
192+
# XXX: This does not yet work properly. In some situations,
193+
# `KeyboardInterrupt` exceptions can end up in the event
194+
# loop selector.
165195
self._handle_keyboard_interrupt(e)
166-
except SystemExit:
167-
return
168-
except BaseException as e:
169-
self._handle_exception(e)
170-
else:
171-
# Print.
172-
if result is not None:
173-
await loop.run_in_executor(
174-
None, lambda: self.show_result(result)
175-
)
176-
177-
# Loop.
178-
self.current_statement_index += 1
179-
self.signatures = []
180196
finally:
181197
if self.terminal_title:
182198
clear_title()
@@ -273,12 +289,18 @@ def _compile_with_flags(self, code: str, mode: str):
273289
def show_result(self, result: object) -> None:
274290
"""
275291
Show __repr__ for an `eval` result.
292+
293+
Note: this can raise `KeyboardInterrupt` if either calling `__repr__`,
294+
`__pt_repr__` or formatting the output with "Black" takes to long
295+
and the user presses Control-C.
276296
"""
277297
out_prompt = to_formatted_text(self.get_output_prompt())
278298

279299
# If the repr is valid Python code, use the Pygments lexer.
280300
try:
281301
result_repr = repr(result)
302+
except KeyboardInterrupt:
303+
raise # Don't catch here.
282304
except BaseException as e:
283305
# Calling repr failed.
284306
self._handle_exception(e)
@@ -313,6 +335,8 @@ def show_result(self, result: object) -> None:
313335
)
314336
if isinstance(formatted_result_repr, list):
315337
formatted_result_repr = FormattedText(formatted_result_repr)
338+
except KeyboardInterrupt:
339+
raise # Don't catch here.
316340
except:
317341
# For bad code, `__getattr__` can raise something that's not an
318342
# `AttributeError`. This happens already when calling `hasattr()`.

0 commit comments

Comments
 (0)