From d3018a66698c70e1d7ee993ef8e069fb1c1db670 Mon Sep 17 00:00:00 2001 From: Robert Blackhart Date: Sat, 4 Jun 2022 17:10:13 -0400 Subject: [PATCH] allow commands tagged with atstart to be async and run in the background (perhaps some long running process while the rest of the shell executes) --- pyproject.toml | 2 +- recline/commands/cli_command.py | 16 +++++++++++----- recline/repl/shell.py | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d8c4cd6..0bc38e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "recline" -version = "2022.5" +version = "2022.6" description = "Writing argparse-based command line applications can become tedious, repetitive, and difficult to do right. Relax and let this library free you from that burden." license = "BSD-3-Clause" authors = ["NetApp "] diff --git a/recline/commands/cli_command.py b/recline/commands/cli_command.py index 6be2c71..f926d36 100644 --- a/recline/commands/cli_command.py +++ b/recline/commands/cli_command.py @@ -40,6 +40,7 @@ def __init__( is_alias=False, hidden=False, is_async=False, + is_background=False, ): self.func = func self.name = name if name else func.__name__ @@ -47,6 +48,7 @@ def __init__( self.group = group self.is_async = is_async self._hidden = hidden + self.is_background = is_background signature = inspect.signature(func) parameters = signature.parameters @@ -322,7 +324,8 @@ def command( aliases: List[str] = None, atstart: bool = False, atexit: bool = False, - hidden: Union[Callable[[], bool], bool] = False + hidden: Union[Callable[[], bool], bool] = False, + background: bool = False ): """Wrapping a function with this registers it with the recline library and exposes it as a command the user of the application can call. @@ -352,13 +355,16 @@ def command( output or available for autocompletion. It will still be executable by the user if they type it out. This can be either a function which evaluates to True or False, or just a constant True or False. + background: If the command is async and long running, then setting + background to True may be advisable so that other commands can be + run in the foreground in the meantime. """ if func is None: return partial( command, name=name, group=group, aliases=aliases, atstart=atstart, - atexit=atexit, hidden=hidden, + atexit=atexit, hidden=hidden, background=background, ) is_async = inspect.iscoroutinefunction(func) @@ -367,16 +373,16 @@ def command( def wrapper(*args, **kwargs): return func(*args, **kwargs) - _register(wrapper, name, group, aliases, atstart, atexit, hidden, is_async) + _register(wrapper, name, group, aliases, atstart, atexit, hidden, is_async, background) return wrapper # pylint: disable=too-many-arguments -def _register(func, name, group, aliases, atstart, atexit, hidden, is_async): +def _register(func, name, group, aliases, atstart, atexit, hidden, is_async, is_background): if atstart: if commands.START_COMMAND: raise RuntimeError(f'A start command is already defined: {func}') - commands.START_COMMAND = CLICommand(func) + commands.START_COMMAND = CLICommand(func, is_async=is_async, is_background=is_background) return if atexit: if commands.EXIT_COMMAND: diff --git a/recline/repl/shell.py b/recline/repl/shell.py index 1bac23b..6a809b0 100644 --- a/recline/repl/shell.py +++ b/recline/repl/shell.py @@ -201,7 +201,10 @@ def signal_handler(signum, frame): atexit.register(_run_command, commands.EXIT_COMMAND, []) if commands.START_COMMAND: - _run_command(commands.START_COMMAND, argv[1:]) + try: + _run_command(commands.START_COMMAND, argv[1:]) + except CommandBackgrounded: + pass def _run_command(command: str, cmd_args: List[str]) -> int: @@ -217,7 +220,7 @@ def _run_command(command: str, cmd_args: List[str]) -> int: if command.is_async: command_thread = AsyncCommand(command, *args, **kwargs) command_thread.start() - if namespace.background: + if namespace.background or command.is_background: raise CommandBackgrounded(command_thread.job_pid) result = command_thread.foreground() else: