Skip to content

Commit 780a4e1

Browse files
Add custom 'exit' function to return from REPL.
- Don't terminate `sys.stdin` when `exit` is called (important for `embed()`). - Don't require 'exit' to be called with parentheses.
1 parent f66a289 commit 780a4e1

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed

ptpython/repl.py

+45-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import warnings
2121
from dis import COMPILER_FLAG_NAMES
2222
from pathlib import Path
23-
from typing import Any, Callable, ContextManager, Iterable, Sequence
23+
from typing import Any, Callable, ContextManager, Iterable, NoReturn, Sequence
2424

2525
from prompt_toolkit.formatted_text import OneStyleAndTextTuple
2626
from prompt_toolkit.patch_stdout import patch_stdout as patch_stdout_context
@@ -40,7 +40,15 @@
4040
except ImportError:
4141
PyCF_ALLOW_TOP_LEVEL_AWAIT = 0
4242

43-
__all__ = ["PythonRepl", "enable_deprecation_warnings", "run_config", "embed"]
43+
44+
__all__ = [
45+
"PythonRepl",
46+
"enable_deprecation_warnings",
47+
"run_config",
48+
"embed",
49+
"exit",
50+
"ReplExit",
51+
]
4452

4553

4654
def _get_coroutine_flag() -> int | None:
@@ -91,9 +99,16 @@ def run_and_show_expression(self, expression: str) -> None:
9199
raise
92100
except SystemExit:
93101
raise
102+
except ReplExit:
103+
raise
94104
except BaseException as e:
95105
self._handle_exception(e)
96106
else:
107+
if isinstance(result, exit):
108+
# When `exit` is evaluated without parentheses.
109+
# Automatically trigger the `ReplExit` exception.
110+
raise ReplExit
111+
97112
# Print.
98113
if result is not None:
99114
self._show_result(result)
@@ -155,7 +170,10 @@ def run(self) -> None:
155170
continue
156171

157172
# Run it; display the result (or errors if applicable).
158-
self.run_and_show_expression(text)
173+
try:
174+
self.run_and_show_expression(text)
175+
except ReplExit:
176+
return
159177
finally:
160178
if self.terminal_title:
161179
clear_title()
@@ -383,6 +401,7 @@ def get_ptpython() -> PythonInput:
383401
return self
384402

385403
globals["get_ptpython"] = get_ptpython
404+
globals["exit"] = exit()
386405

387406
def _remove_from_namespace(self) -> None:
388407
"""
@@ -459,6 +478,29 @@ def enter_to_continue() -> None:
459478
enter_to_continue()
460479

461480

481+
class exit:
482+
"""
483+
Exit the ptpython REPL.
484+
"""
485+
486+
# This custom exit function ensures that the `embed` function returns from
487+
# where we are embedded, and Python doesn't close `sys.stdin` like
488+
# the default `exit` from `_sitebuiltins.Quitter` does.
489+
490+
def __call__(self) -> NoReturn:
491+
raise ReplExit
492+
493+
def __repr__(self) -> str:
494+
# (Same message as the built-in Python REPL.)
495+
return "Use exit() or Ctrl-D (i.e. EOF) to exit"
496+
497+
498+
class ReplExit(Exception):
499+
"""
500+
Exception raised by ptpython's exit function.
501+
"""
502+
503+
462504
def embed(
463505
globals: dict[str, Any] | None = None,
464506
locals: dict[str, Any] | None = None,

0 commit comments

Comments
 (0)