Skip to content

Commit 036f185

Browse files
committed
async_alert() raises a RuntimeError if called from the main thread.
1 parent 65ba274 commit 036f185

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Bug Fixes
33
* Fixed value for `ansi.Bg.YELLOW`.
44
* Fixed unit tests for `ansi.allow_style`.
5+
* Enhancements
6+
* `async_alert()` raises a `RuntimeError` if called from the main thread.
57

68
## 2.4.0 (February 22, 2022)
79
* Bug Fixes

cmd2/cmd2.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,7 +1984,7 @@ def _perform_completion(
19841984
# Save the quote so we can add a matching closing quote later.
19851985
completion_token_quote = raw_completion_token[0]
19861986

1987-
# readline still performs word breaks after a quote. Therefore something like quoted search
1987+
# readline still performs word breaks after a quote. Therefore, something like quoted search
19881988
# text with a space would have resulted in begidx pointing to the middle of the token we
19891989
# we want to complete. Figure out where that token actually begins and save the beginning
19901990
# portion of it that was not part of the text readline gave us. We will remove it from the
@@ -2064,7 +2064,7 @@ def complete( # type: ignore[override]
20642064
until it returns a non-string value. It should return the next possible completion starting with text.
20652065
20662066
Since readline suppresses any exception raised in completer functions, they can be difficult to debug.
2067-
Therefore this function wraps the actual tab completion logic and prints to stderr any exception that
2067+
Therefore, this function wraps the actual tab completion logic and prints to stderr any exception that
20682068
occurs before returning control to readline.
20692069
20702070
:param text: the current word that user is typing
@@ -2097,7 +2097,7 @@ def complete( # type: ignore[override]
20972097
begidx = max(readline.get_begidx() - num_stripped, 0)
20982098
endidx = max(readline.get_endidx() - num_stripped, 0)
20992099

2100-
# Shortcuts are not word break characters when tab completing. Therefore shortcuts become part
2100+
# Shortcuts are not word break characters when tab completing. Therefore, shortcuts become part
21012101
# of the text variable if there isn't a word break, like a space, after it. We need to remove it
21022102
# from text and update the indexes. This only applies if we are at the beginning of the command line.
21032103
shortcut_to_restore = ''
@@ -4056,7 +4056,7 @@ def do_shell(self, args: argparse.Namespace) -> None:
40564056
# sh reports an incorrect return code for some applications when Ctrl-C is pressed within that
40574057
# application (e.g. less). Since sh received the SIGINT, it sets the return code to reflect being
40584058
# closed by SIGINT even though less did not exit upon a Ctrl-C press. In the same situation, other
4059-
# shells like bash and zsh report the actual return code of less. Therefore we will try to run the
4059+
# shells like bash and zsh report the actual return code of less. Therefore, we will try to run the
40604060
# user's preferred shell which most likely will be something other than sh. This also allows the user
40614061
# to run builtin commands of their preferred shell.
40624062
shell = os.environ.get("SHELL")
@@ -4103,7 +4103,7 @@ def _reset_py_display() -> None:
41034103
console to create its own display settings since they won't exist.
41044104
41054105
IPython does not have this problem since it always overwrites the display settings when it
4106-
is run. Therefore this method only needs to be called before creating a Python console.
4106+
is run. Therefore, this method only needs to be called before creating a Python console.
41074107
"""
41084108
# Delete any prompts that have been set
41094109
attributes = ['ps1', 'ps2', 'ps3']
@@ -4318,7 +4318,7 @@ def py_quit() -> None:
43184318
saved_cmd2_env = self._set_up_py_shell_env(interp)
43194319

43204320
# Since quit() or exit() raise an EmbeddedConsoleExit, interact() exits before printing
4321-
# the exitmsg. Therefore we will not provide it one and print it manually later.
4321+
# the exitmsg. Therefore, we will not provide it one and print it manually later.
43224322
interp.interact(banner=banner, exitmsg='')
43234323
except BaseException:
43244324
# We don't care about any exception that happened in the interactive console
@@ -4453,7 +4453,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover
44534453

44544454
# The IPython application is a singleton and won't be recreated next time
44554455
# this function runs. That's a problem since the contents of local_vars
4456-
# may need to be changed. Therefore we must destroy all instances of the
4456+
# may need to be changed. Therefore, we must destroy all instances of the
44574457
# relevant classes.
44584458
TerminalIPythonApp.clear_instance()
44594459
TerminalInteractiveShell.clear_instance()
@@ -5026,14 +5026,22 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
50265026
text and cursor location is left alone.
50275027
50285028
IMPORTANT: This function will not print an alert unless it can acquire self.terminal_lock to ensure
5029-
a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
5029+
a prompt is onscreen. Therefore, it is best to acquire the lock before calling this function
50305030
to guarantee the alert prints and to avoid raising a RuntimeError.
50315031
5032+
This function is only needed when you need to print an alert while the main thread is blocking
5033+
at the prompt. Therefore, this should never be called from the main thread. Doing so will
5034+
raise a RuntimeError.
5035+
50325036
:param alert_msg: the message to display to the user
50335037
:param new_prompt: If you also want to change the prompt that is displayed, then include it here.
50345038
See async_update_prompt() docstring for guidance on updating a prompt.
5039+
:raises RuntimeError: if called from the main thread.
50355040
:raises RuntimeError: if called while another thread holds `terminal_lock`
50365041
"""
5042+
if threading.current_thread() is threading.main_thread():
5043+
raise RuntimeError("async_alert should not be called from the main thread")
5044+
50375045
if not (vt100_support and self.use_rawinput):
50385046
return
50395047

@@ -5096,14 +5104,19 @@ def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover
50965104
be shifted and the update will not be seamless.
50975105
50985106
IMPORTANT: This function will not update the prompt unless it can acquire self.terminal_lock to ensure
5099-
a prompt is onscreen. Therefore it is best to acquire the lock before calling this function
5107+
a prompt is onscreen. Therefore, it is best to acquire the lock before calling this function
51005108
to guarantee the prompt changes and to avoid raising a RuntimeError.
51015109
5110+
This function is only needed when you need to update the prompt while the main thread is blocking
5111+
at the prompt. Therefore, this should never be called from the main thread. Doing so will
5112+
raise a RuntimeError.
5113+
51025114
If user is at a continuation prompt while entering a multiline command, the onscreen prompt will
5103-
not change. However self.prompt will still be updated and display immediately after the multiline
5115+
not change. However, self.prompt will still be updated and display immediately after the multiline
51045116
line command completes.
51055117
51065118
:param new_prompt: what to change the prompt to
5119+
:raises RuntimeError: if called from the main thread.
51075120
:raises RuntimeError: if called while another thread holds `terminal_lock`
51085121
"""
51095122
self.async_alert('', new_prompt)
@@ -5113,7 +5126,7 @@ def set_window_title(title: str) -> None: # pragma: no cover
51135126
"""
51145127
Set the terminal window title.
51155128
5116-
NOTE: This function writes to stderr. Therefore if you call this during a command run by a pyscript,
5129+
NOTE: This function writes to stderr. Therefore, if you call this during a command run by a pyscript,
51175130
the string which updates the title will appear in that command's CommandResult.stderr data.
51185131
51195132
:param title: the new window title

0 commit comments

Comments
 (0)