From 48d4e0b59098eff8c55873d4394ed813c6cf80aa Mon Sep 17 00:00:00 2001 From: Jusong Yu Date: Fri, 6 Jan 2023 17:26:53 +0100 Subject: [PATCH] CLI: verdi export to a yaml file with keys from code class fixes #3521 Add `verdi code export` command to export code from command line as a ymal file. This is mentioned in usability improvement as well as having a command to export the code and computer setup. Keys of YAML file are read from the cli option of the corresponding code class. --- aiida/cmdline/commands/cmd_code.py | 21 ++++++++++++++++ aiida/orm/nodes/data/code/abstract.py | 1 - docs/source/reference/command_line.rst | 1 + tests/cmdline/commands/test_code.py | 25 +++++++++++++++++++ .../commands/test_code/test_code_export.yml | 8 ++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/cmdline/commands/test_code/test_code_export.yml diff --git a/aiida/cmdline/commands/cmd_code.py b/aiida/cmdline/commands/cmd_code.py index 31921135b5..fff74b48cb 100644 --- a/aiida/cmdline/commands/cmd_code.py +++ b/aiida/cmdline/commands/cmd_code.py @@ -13,6 +13,7 @@ import click import tabulate +import yaml from aiida.cmdline.commands.cmd_verdi import verdi from aiida.cmdline.groups.dynamic import DynamicEntryPointCommandGroup @@ -235,6 +236,26 @@ def show(code): echo.echo(tabulate.tabulate(table)) +@verdi_code.command() +@arguments.CODE() +@arguments.OUTPUT_FILE(type=click.Path(exists=False)) +@with_dbenv() +def export(code, output_file): + """Export code to a yaml file.""" + code_data = {} + + for key in code.get_cli_options().keys(): + if key == 'computer': + value = getattr(code, key).label + else: + value = getattr(code, key) + + code_data[key] = str(value) + + with open(output_file, 'w', encoding='utf-8') as yfhandle: + yaml.dump(code_data, yfhandle) + + @verdi_code.command() @arguments.CODES() @options.DRY_RUN() diff --git a/aiida/orm/nodes/data/code/abstract.py b/aiida/orm/nodes/data/code/abstract.py index ed87a4f0a7..89c295d17a 100644 --- a/aiida/orm/nodes/data/code/abstract.py +++ b/aiida/orm/nodes/data/code/abstract.py @@ -64,7 +64,6 @@ def __init__( self.append_text = append_text self.prepend_text = prepend_text self.use_double_quotes = use_double_quotes - self.use_double_quotes = use_double_quotes self.is_hidden = is_hidden @abc.abstractmethod diff --git a/docs/source/reference/command_line.rst b/docs/source/reference/command_line.rst index 9a3994a4c4..6ab004b6b5 100644 --- a/docs/source/reference/command_line.rst +++ b/docs/source/reference/command_line.rst @@ -75,6 +75,7 @@ Below is a list with all available subcommands. create Create a new code. delete Delete a code. duplicate Duplicate a code allowing to change some parameters. + export Export code to a yaml file. hide Hide one or more codes from `verdi code list`. list List the available codes. relabel Relabel a code. diff --git a/tests/cmdline/commands/test_code.py b/tests/cmdline/commands/test_code.py index 37b9cf0ce0..efda6d3e04 100644 --- a/tests/cmdline/commands/test_code.py +++ b/tests/cmdline/commands/test_code.py @@ -236,6 +236,31 @@ def test_code_duplicate_ignore(run_cli_command, aiida_local_code_factory, non_in assert duplicate.description == '' +@pytest.mark.usefixtures('aiida_profile_clean') +def test_code_export(run_cli_command, aiida_local_code_factory, tmp_path, file_regression): + """Test export the code setup to str.""" + prepend_text = 'module load something\n some command' + code = aiida_local_code_factory('core.arithmetic.add', '/bin/cat', label='code', prepend_text=prepend_text) + filepath = tmp_path / 'code.yml' + options = [str(code.pk), str(filepath)] + run_cli_command(cmd_code.export, options) + + # file regression check + with open(filepath, 'r', encoding='utf-8') as fhandle: + content = fhandle.read() + file_regression.check(content, extension='.yml') + + # round trip test by create code from the config file + # we pass the new label to override since cannot have two code with same labels + new_label = 'code0' + run_cli_command( + cmd_code.code_create, ['core.code.installed', '--non-interactive', '--config', filepath, '--label', new_label] + ) + new_code = load_code(new_label) + assert code.base.attributes.all == new_code.base.attributes.all + assert isinstance(new_code, InstalledCode) + + @pytest.mark.parametrize('non_interactive_editor', ('vim -cwq',), indirect=True) def test_from_config_local_file(non_interactive_editor, run_cli_command, aiida_localhost): """Test setting up a code from a config file on disk.""" diff --git a/tests/cmdline/commands/test_code/test_code_export.yml b/tests/cmdline/commands/test_code/test_code_export.yml new file mode 100644 index 0000000000..68391abcc6 --- /dev/null +++ b/tests/cmdline/commands/test_code/test_code_export.yml @@ -0,0 +1,8 @@ +append_text: '' +computer: localhost +default_calc_job_plugin: core.arithmetic.add +description: code +filepath_executable: /bin/cat +label: code +prepend_text: "module load something\n some command" +use_double_quotes: 'False'