Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions docs/source/reference/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +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.
export Export code(s) to yaml file(s).
hide Hide one or more codes from `verdi code list`.
list List the available codes.
relabel Relabel a code.
Expand All @@ -100,17 +100,18 @@ Below is a list with all available subcommands.
--help Show this message and exit.

Commands:
configure Configure the transport for a computer and user.
delete Delete a computer.
disable Disable the computer for the given user.
duplicate Duplicate a computer allowing to change some parameters.
enable Enable the computer for the given user.
export Export the setup or configuration of a computer.
list List all available computers.
relabel Relabel a computer.
setup Create a new computer.
show Show detailed information for a computer.
test Test the connection to a computer.
configure Configure the transport for a computer and user.
delete Delete a computer.
disable Disable the computer for the given user.
duplicate Duplicate a computer allowing to change some parameters.
enable Enable the computer for the given user.
export Export the setup or configuration of a computer.
list List all available computers.
relabel Relabel a computer.
setup Create a new computer.
setup-many Create multiple computers from YAML configuration files.
show Show detailed information for a computer.
test Test the connection to a computer.


.. _reference:command-line:verdi-config:
Expand Down Expand Up @@ -453,7 +454,7 @@ Below is a list with all available subcommands.
--broker-host HOSTNAME Hostname for the message broker. [default: 127.0.0.1]
--broker-port INTEGER Port for the message broker. [default: 5672]
--broker-virtual-host TEXT Name of the virtual host for the message broker without
leading forward slash. [default: ""]
leading forward slash.
--repository DIRECTORY Absolute path to the file repository.
--test-profile Designate the profile to be used for running the test
suite only.
Expand Down
118 changes: 89 additions & 29 deletions src/aiida/cmdline/commands/cmd_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,43 +250,103 @@ def show(code):


@verdi_code.command()
@arguments.CODE()
@arguments.OUTPUT_FILE(type=click.Path(exists=False, path_type=pathlib.Path), required=False)
@click.argument('codes_and_output', nargs=-1, required=True)
@click.option('-a', '--all', 'export_all', is_flag=True, help='Export all codes.')
@options.OVERWRITE()
@options.SORT()
@with_dbenv()
def export(code, output_file, overwrite, sort):
"""Export code to a yaml file. If no output file is given, default name is created based on the code label."""
def export(codes_and_output, export_all, overwrite, sort):
"""Export code(s) to yaml file(s).

Usage:
- verdi code export CODE_ID [CODE_ID ...] [OUTPUT_FILE]
- verdi code export --all

If no output file is given, default names are created based on the code labels.
Custom output filename can only be provided when exporting a single code.
"""
from aiida import orm
from aiida.common import NotExistent

other_args = {'sort': sort}
fileformat = 'yaml'

if output_file is None:
output_file = pathlib.Path(f'{code.full_label}.{fileformat}')

try:
# In principle, output file validation is also done in the `data_export` function. However, the
# `validate_output_filename` function is applied here, as well, as it is also used in the `Computer` export, and
# as `Computer` is not derived from `Data`, it cannot be exported by `data_export`, so
# `validate_output_filename` cannot be removed in favor of the validation done in `data_export`.
validate_output_filename(
output_file=output_file,
overwrite=overwrite,
)
except (FileExistsError, IsADirectoryError) as exception:
raise click.BadParameter(str(exception), param_hint='OUTPUT_FILE') from exception
# Handle --all option
if export_all:
if codes_and_output:
echo.echo_critical('Cannot specify both --all and individual codes.')

# Get all non-hidden codes
query = orm.QueryBuilder()
query.append(orm.Code, filters={f'extras.{orm.Code.HIDDEN_KEY}': {'!==': True}})
codes = [code for [code] in query.all()]
if not codes:
echo.echo_report('No codes found in the database.')
return
output_file = None
else:
# Parse codes and potential output file
if not codes_and_output:
echo.echo_critical('Must specify either --all or individual code identifiers.')

codes = []
output_file = None

# Try to parse all arguments as codes first
for i, arg in enumerate(codes_and_output):
try:
code = orm.load_code(arg)
codes.append(code)
except NotExistent:
# This argument is not a valid code identifier
if i == len(codes_and_output) - 1:
# Last argument and not a code - treat as output file
output_file = pathlib.Path(arg)
break
else:
# Not last argument and not a code - error
echo.echo_critical(f'Invalid code identifier: {arg}')

if not codes:
echo.echo_critical('No valid code identifiers provided.')

# Validate output file usage
if output_file and len(codes) > 1:
msg = 'Custom output filename can only be provided if a single code is being exported.'
raise click.BadParameter(msg)

# Export each code
for code in codes:
# Determine the output file for this specific code
if output_file is None:
current_output_file = pathlib.Path(f'{code.full_label}.{fileformat}')
else:
current_output_file = output_file

try:
data_export(
node=code,
output_fname=output_file,
fileformat=fileformat,
other_args=other_args,
overwrite=overwrite,
)
except Exception as exception:
echo.echo_critical(f'Error in the `data_export` function: {exception}')
try:
# In principle, output file validation is also done in the `data_export` function. However, the
# `validate_output_filename` function is applied here, as well, as it is also used in the `Computer` export,
# and as `Computer` is not derived from `Data`, it cannot be exported by `data_export`, so
# `validate_output_filename` cannot be removed in favor of the validation done in `data_export`.
validate_output_filename(
output_file=current_output_file,
overwrite=overwrite,
)
except (FileExistsError, IsADirectoryError) as exception:
raise click.BadParameter(str(exception), param_hint='OUTPUT_FILE') from exception

echo.echo_success(f'Code<{code.pk}> {code.label} exported to file `{output_file}`.')
try:
data_export(
node=code,
output_fname=current_output_file,
fileformat=fileformat,
other_args=other_args,
overwrite=overwrite,
)
except Exception as exception:
echo.echo_critical(f'Error in the `data_export` function: {exception}')

echo.echo_success(f'Code<{code.pk}> {code.label} exported to file `{current_output_file}`.')


@verdi_code.command()
Expand Down
33 changes: 33 additions & 0 deletions src/aiida/cmdline/commands/cmd_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,39 @@ def computer_setup(ctx, non_interactive, **kwargs):
echo.echo_report(f' verdi -p {profile.name} computer configure {computer.transport_type} {computer.label}')


@verdi_computer.command('setup-many')
@click.argument('config_files', nargs=-1, required=True, type=click.Path(exists=True, path_type=pathlib.Path))
@with_dbenv()
def computer_setup_many(config_files):
"""Create multiple computers from YAML configuration files."""
import yaml

from aiida.common.exceptions import IntegrityError
from aiida.orm.utils.builders.computer import ComputerBuilder

for config_path in config_files:
try:
with open(config_path, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)

computer_builder = ComputerBuilder(**config_data)
computer = computer_builder.new()
computer.store()

echo.echo_success(f'Computer<{computer.pk}> {computer.label} created')
except IntegrityError as e:
if 'UNIQUE constraint failed: db_dbcomputer.label' in str(e):
msg = (
f'Error processing {config_path}: Computer with label "{config_data.get("label", "unknown")}"'
'already exists'
)
echo.echo_error(msg)
else:
echo.echo_error(f'Error processing {config_path}: Database integrity error - {e}')
except Exception as e:
echo.echo_error(f'Error processing {config_path}: {e}')


@verdi_computer.command('duplicate')
@arguments.COMPUTER(callback=set_computer_builder)
@options_computer.LABEL(contextual_default=partial(get_parameter_default, 'label'))
Expand Down
8 changes: 8 additions & 0 deletions src/aiida/cmdline/params/options/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
'COMPUTER',
'COMPUTERS',
'CONFIG_FILE',
'CONFIG_FILES',
'DATA',
'DATUM',
'DB_BACKEND',
Expand Down Expand Up @@ -735,6 +736,13 @@ def set_log_level(ctx: click.Context, _param: click.Parameter, value: t.Any) ->
help='Load option values from configuration file in yaml format (local path or URL).',
)

CONFIG_FILES = click.option(
'--config-files',
multiple=True,
type=click.Path(exists=True, path_type=pathlib.Path),
help='Load computer setup from multiple configuration files in YAML format. Can be specified multiple times.',
)

IDENTIFIER = OverridableOption(
'-i',
'--identifier',
Expand Down
Loading