diff --git a/docs/source/reference/command_line.rst b/docs/source/reference/command_line.rst index bf4d668928..1d7287e40c 100644 --- a/docs/source/reference/command_line.rst +++ b/docs/source/reference/command_line.rst @@ -80,7 +80,7 @@ Below is a list with all available subcommands. list List the available codes. relabel Relabel a code. reveal Reveal one or more hidden codes in `verdi code list`. - setup Setup a new code. + setup (Deprecated) Setup a new code (use `verdi code create`). show Display detailed information for a code. test Run tests for the given code to check whether it is usable. @@ -531,7 +531,7 @@ Below is a list with all available subcommands. Usage: [OPTIONS] - Setup a new profile. + (Deprecated) Setup a new profile (use `verdi profile setup`). This method assumes that an empty PSQL database has been created and that the database user has been created. diff --git a/src/aiida/cmdline/commands/cmd_code.py b/src/aiida/cmdline/commands/cmd_code.py index 5b7b610a01..477d2f61ab 100644 --- a/src/aiida/cmdline/commands/cmd_code.py +++ b/src/aiida/cmdline/commands/cmd_code.py @@ -18,7 +18,7 @@ from aiida.cmdline.params import arguments, options, types from aiida.cmdline.params.options.commands import code as options_code from aiida.cmdline.utils import echo, echo_tabulate -from aiida.cmdline.utils.decorators import deprecated_command, with_dbenv +from aiida.cmdline.utils.decorators import with_dbenv from aiida.common import exceptions @@ -89,7 +89,7 @@ def set_code_builder(ctx, param, value): # because the ``LABEL`` option has a callback that relies on the computer being already set. Execution order is # guaranteed only for the interactive case, however. For the non-interactive case, the callback is called explicitly # once more in the command body to cover the case when the label is specified before the computer. -@verdi_code.command('setup') +@verdi_code.command('setup', deprecated='Please use `verdi code create` instead.') @options_code.ON_COMPUTER() @options_code.COMPUTER() @options_code.LABEL() @@ -105,9 +105,8 @@ def set_code_builder(ctx, param, value): @options.CONFIG_FILE() @click.pass_context @with_dbenv() -@deprecated_command('This command will be removed soon, use `verdi code create` instead.') def setup_code(ctx, non_interactive, **kwargs): - """Setup a new code.""" + """Setup a new code (use `verdi code create`).""" from aiida.orm.utils.builders.code import CodeBuilder options_code.validate_label_uniqueness(ctx, None, kwargs['label']) diff --git a/src/aiida/cmdline/commands/cmd_setup.py b/src/aiida/cmdline/commands/cmd_setup.py index 2f76d56434..0c18ce4238 100644 --- a/src/aiida/cmdline/commands/cmd_setup.py +++ b/src/aiida/cmdline/commands/cmd_setup.py @@ -17,8 +17,7 @@ from aiida.manage.configuration import Profile, load_profile -@verdi.command('setup') -@decorators.deprecated_command('This command is deprecated, use `verdi profile setup core.psql_dos` instead.') +@verdi.command('setup', deprecated='Please use `verdi profile setup` instead.') @options.NON_INTERACTIVE() @options_setup.SETUP_PROFILE() @options_setup.SETUP_USER_EMAIL() @@ -68,7 +67,7 @@ def setup( test_profile, profile_uuid, ): - """Setup a new profile. + """Setup a new profile (use `verdi profile setup`). This method assumes that an empty PSQL database has been created and that the database user has been created. """ diff --git a/src/aiida/cmdline/groups/verdi.py b/src/aiida/cmdline/groups/verdi.py index a3c40e50b8..e156b8413d 100644 --- a/src/aiida/cmdline/groups/verdi.py +++ b/src/aiida/cmdline/groups/verdi.py @@ -9,6 +9,7 @@ import click +from aiida.cmdline.utils.echo import echo_deprecated from aiida.common.exceptions import ConfigurationError from aiida.common.extendeddicts import AttributeDict from aiida.manage.configuration import get_config @@ -79,6 +80,43 @@ def __init__(self, *args, **kwargs): self.obj = LazyVerdiObjAttributeDict(self) +class VerdiCommand(click.Command): + """Custom command implementation to customize the logic of printing deprecation messages. + + If a command is deprecated, the :class:`click.Command` adds a deprecation marker in the short help and the full + help text, and prints a deprecation warning when the command is invoked. The problem is that the deprecation warning + is printed after the prompting for parameters, which for interactive commands mean the deprecation warning comes too + late, when the user has already provided all prompts. + + Here, the :meth:`click.Command.parse_args` method is overridden, which is called before the interactive options + start to prompt, such that the deprecation warning can be printed. The :meth:`click.Command.invoke` method is also + overridden in order to skip the printing of the deprecation message handled by ``click`` as that would result in + the deprecation message being printed twice. + """ + + def parse_args(self, ctx: click.Context, args: t.List[str]) -> t.List[str]: + """Given a context and a list of arguments this creates the parser and parses the arguments. + + Then context is modified as necessary. + + This is automatically invoked by :meth:`click.BaseCommand.make_context`. + """ + if self.deprecated: + # We are abusing click.Command `deprecated` member variable by using a + # string instead of a bool to also use it as optional deprecated message + echo_deprecated( + self.deprecated + if isinstance(self.deprecated, str) # type: ignore[redundant-expr] + else 'This command is deprecated.' + ) + + return super().parse_args(ctx, args) + + def invoke(self, ctx: click.Context) -> t.Any: + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + class VerdiCommandGroup(click.Group): """Subclass of :class:`click.Group` for the ``verdi`` CLI. @@ -87,6 +125,7 @@ class VerdiCommandGroup(click.Group): """ context_class = VerdiContext + command_class = VerdiCommand @staticmethod def add_verbosity_option(cmd: click.Command) -> click.Command: diff --git a/tests/cmdline/commands/test_code.py b/tests/cmdline/commands/test_code.py index a36a7bb28a..8aeeb5cec8 100644 --- a/tests/cmdline/commands/test_code.py +++ b/tests/cmdline/commands/test_code.py @@ -46,6 +46,21 @@ def test_help(run_cli_command): run_cli_command(cmd_code.setup_code, ['--help']) +def test_code_setup_deprecation(run_cli_command): + """Checks if a deprecation warning is printed in stdout and stderr.""" + # Checks if the deprecation warning is present when invoking the help page + result = run_cli_command(cmd_code.setup_code, ['--help']) + assert 'Deprecated:' in result.output + assert 'Deprecated:' in result.stderr + + # Checks if the deprecation warning is present when invoking the command + # Runs setup in interactive mode and sends Ctrl+D (\x04) as input so we exit the prompts. + # This way we can check if the deprecated message was printed with the first prompt. + result = run_cli_command(cmd_code.setup_code, user_input='\x04', use_subprocess=True, raises=True) + assert 'Deprecated:' in result.output + assert 'Deprecated:' in result.stderr + + @pytest.mark.usefixtures('aiida_profile_clean') def test_code_list_no_codes_error_message(run_cli_command): """Test ``verdi code list`` when no codes exist.""" diff --git a/tests/cmdline/commands/test_setup.py b/tests/cmdline/commands/test_setup.py index 9a12e3fb88..6c7a668542 100644 --- a/tests/cmdline/commands/test_setup.py +++ b/tests/cmdline/commands/test_setup.py @@ -40,6 +40,20 @@ def init_profile(self, pg_test_cluster, empty_config, run_cli_command): self.pg_test = pg_test_cluster self.cli_runner = run_cli_command + def test_setup_deprecation(self): + """Checks if a deprecation warning is printed in stdout and stderr.""" + # Checks if the deprecation warning is present when invoking the help page + result = self.cli_runner(cmd_setup.setup, ['--help']) + assert 'Deprecated:' in result.output + assert 'Deprecated:' in result.stderr + + # Checks if the deprecation warning is present when invoking the command + # Runs setup in interactive mode and sends Ctrl+D (\x04) as input so we exit the prompts. + # This way we can check if the deprecation warning was printed with the first prompt. + result = self.cli_runner(cmd_setup.setup, user_input='\x04', use_subprocess=True, raises=True) + assert 'Deprecated:' in result.output + assert 'Deprecated:' in result.stderr + def test_help(self): """Check that the `--help` option is eager, is not overruled and will properly display the help message.