Skip to content

Commit

Permalink
verdi code setup: validate the uniqueness of label for local codes
Browse files Browse the repository at this point in the history
In commit d25339d `verdi code setup`
was improved to have a callback for the `label` option to check for its
uniqueness. However, it only implemented this for "remote" computers,
which have an associated `Computer` and so the uniqueness criterion is
on the "full label", which is the `label@computer.label`.

However, there are also "local" codes, which don't have an associated
computer and so only have the label as the identifier that should be
unique. The callback `validate_label_uniqueness` is now updated to
distinguish between these two cases.
  • Loading branch information
sphuber committed Nov 16, 2021
1 parent f8d1615 commit f85d029
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 11 deletions.
30 changes: 24 additions & 6 deletions aiida/cmdline/params/options/commands/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,34 @@ def is_not_on_computer(ctx):


def validate_label_uniqueness(ctx, _, value):
"""Validate the uniqueness of the full label of the code, i.e., `label@computer.label`.
"""Validate the uniqueness of the label of the code.
The exact uniqueness criterion depends on the type of the code, whether it is "local" or "remote". For the former,
the `label` itself should be unique, whereas for the latter it is the full label, i.e., `label@computer.label`.
.. note:: For this to work in the case of the remote code, the computer parameter already needs to have been parsed
In interactive mode, this means that the computer parameter needs to be defined after the label parameter in the
command definition. For non-interactive mode, the parsing order will always be determined by the order the
parameters are specified by the caller and so this validator may get called before the computer is parsed. For
that reason, this validator should also be called in the command itself, to ensure it has both the label and
computer parameter available.
.. note:: For this to work, the computer parameter already needs to have been parsed. In interactive mode, this
means that the computer parameter needs to be defined after the label parameter in the command definition. For
non-interactive mode, the parsing order will always be determined by the order the parameters are specified by
the caller and so this validator may get called before the computer is parsed. For that reason, this validator
should also be called in the command itself, to ensure it has both the label and computer parameter available.
"""
from aiida.common import exceptions
from aiida.orm import load_code

computer = ctx.params.get('computer', None)
on_computer = ctx.params.get('on_computer', None)

if on_computer is False:
try:
load_code(value)
except exceptions.NotExistent:
pass
except exceptions.MultipleObjectsError:
raise click.BadParameter(f'multiple copies of the remote code `{value}` already exist.')
else:
raise click.BadParameter(f'the code `{value}` already exists.')

if computer is not None:
full_label = f'{value}@{computer.label}'
Expand All @@ -44,6 +60,8 @@ def validate_label_uniqueness(ctx, _, value):
load_code(full_label)
except exceptions.NotExistent:
pass
except exceptions.MultipleObjectsError:
raise click.BadParameter(f'multiple copies of the local code `{full_label}` already exist.')
else:
raise click.BadParameter(f'the code `{full_label}` already exists.')

Expand Down
73 changes: 68 additions & 5 deletions tests/cmdline/commands/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import tempfile
import textwrap

import click
import pytest

from aiida.cmdline.commands import cmd_code
from aiida.common.exceptions import NotExistent
from aiida.cmdline.params.options.commands.code import validate_label_uniqueness
from aiida.common.exceptions import MultipleObjectsError, NotExistent
from aiida.orm import Code, load_code


Expand Down Expand Up @@ -281,10 +283,10 @@ def test_from_config_url(non_interactive_editor, run_cli_command, aiida_localhos

@pytest.mark.usefixtures('clear_database_before_test')
@pytest.mark.parametrize('non_interactive_editor', ('sleep 1; vim -cwq',), indirect=True)
def test_code_setup_duplicate_full_label_interactive(
def test_code_setup_remote_duplicate_full_label_interactive(
run_cli_command, aiida_local_code_factory, aiida_localhost, non_interactive_editor
):
"""Test ``verdi code setup`` in interactive mode when specifying a full label that already exists."""
"""Test ``verdi code setup`` for a remote code in interactive mode specifying an existing full label."""
label = 'some-label'
aiida_local_code_factory('core.arithmetic.add', '/bin/cat', computer=aiida_localhost, label=label)
assert isinstance(load_code(label), Code)
Expand All @@ -297,10 +299,10 @@ def test_code_setup_duplicate_full_label_interactive(

@pytest.mark.usefixtures('clear_database_before_test')
@pytest.mark.parametrize('label_first', (True, False))
def test_code_setup_duplicate_full_label_non_interactive(
def test_code_setup_remote_duplicate_full_label_non_interactive(
run_cli_command, aiida_local_code_factory, aiida_localhost, label_first
):
"""Test ``verdi code setup`` in non-interactive mode when specifying a full label that already exists."""
"""Test ``verdi code setup`` for a remote code in non-interactive mode specifying an existing full label."""
label = 'some-label'
aiida_local_code_factory('core.arithmetic.add', '/bin/cat', computer=aiida_localhost, label=label)
assert isinstance(load_code(label), Code)
Expand All @@ -314,3 +316,64 @@ def test_code_setup_duplicate_full_label_non_interactive(

result = run_cli_command(cmd_code.setup_code, options, raises=True)
assert f'the code `{label}@{aiida_localhost.label}` already exists.' in result.output


@pytest.mark.usefixtures('clear_database_before_test')
@pytest.mark.parametrize('non_interactive_editor', ('sleep 1; vim -cwq',), indirect=True)
def test_code_setup_local_duplicate_full_label_interactive(
run_cli_command, aiida_local_code_factory, aiida_localhost, non_interactive_editor
):
"""Test ``verdi code setup`` for a local code in interactive mode specifying an existing full label."""
label = 'some-label'
code = Code(local_executable='bash', files=['/bin/bash'])
code.label = label
code.store()
assert isinstance(load_code(label), Code)

label_unique = 'label-unique'
user_input = '\n'.join(['no', label, label_unique, 'd', 'core.arithmetic.add', '/bin', 'bash'])
run_cli_command(cmd_code.setup_code, user_input=user_input)
assert isinstance(load_code(label_unique), Code)


@pytest.mark.usefixtures('clear_database_before_test')
def test_code_setup_local_duplicate_full_label_non_interactive(
run_cli_command, aiida_local_code_factory, aiida_localhost
):
"""Test ``verdi code setup`` for a local code in non-interactive mode specifying an existing full label."""
label = 'some-label'
code = Code(local_executable='bash', files=['/bin/bash'])
code.label = label
code.store()
assert isinstance(load_code(label), Code)

options = [
'-n', '-D', 'd', '-P', 'core.arithmetic.add', '--store-in-db', '--code-folder=/bin', '--code-rel-path=bash',
'--label', label
]

result = run_cli_command(cmd_code.setup_code, options, raises=True)
assert f'the code `{label}` already exists.' in result.output


@pytest.mark.usefixtures('clear_database_before_test')
def test_validate_label_uniqueness(monkeypatch, aiida_localhost):
"""Test the ``validate_label_uniqueness`` validator."""
from aiida import orm

def load_code(*args, **kwargs):
raise MultipleObjectsError()

monkeypatch.setattr(orm, 'load_code', load_code)

ctx = click.Context(cmd_code.setup_code)
ctx.params = {'on_computer': False}

with pytest.raises(click.BadParameter, match=r'multiple copies of the remote code `.*` already exist.'):
validate_label_uniqueness(ctx, None, 'some-code')

ctx = click.Context(cmd_code.setup_code)
ctx.params = {'on_computer': None, 'computer': aiida_localhost}

with pytest.raises(click.BadParameter, match=r'multiple copies of the local code `.*` already exist.'):
validate_label_uniqueness(ctx, None, 'some-code')

0 comments on commit f85d029

Please sign in to comment.