diff --git a/aiida/backends/djsite/utils.py b/aiida/backends/djsite/utils.py index a8b18a3e4b..fbd285e591 100644 --- a/aiida/backends/djsite/utils.py +++ b/aiida/backends/djsite/utils.py @@ -162,16 +162,23 @@ def check_schema_version(profile_name): set_db_schema_version(code_schema_version) db_schema_version = get_db_schema_version() - if code_schema_version != db_schema_version: + if code_schema_version > db_schema_version: raise ConfigurationError( - 'Database schema version {} is outdated compared to the code schema version {}\n' - 'Before you upgrade, make sure all calculations and workflows have finished running.\n' - 'If this is not the case, revert the code to the previous version and finish them first.\n' - 'To migrate the database to the current version, run the following commands:' + 'Database schema version {} is outdated compared to the code schema version {}.\n' + 'Note: Have all calculations and workflows have finished running? ' + 'If not, revert the code to the previous version and let them finish before upgrading.\n' + 'To migrate the database to the current code version, run the following commands:' '\n verdi -p {} daemon stop\n verdi -p {} database migrate'.format( db_schema_version, code_schema_version, profile_name, profile_name ) ) + elif code_schema_version < db_schema_version: + raise ConfigurationError( + 'Database schema version {} is newer than code schema version {}.\n' + 'You cannot use an outdated code with a newer database. Please upgrade.'.format( + db_schema_version, code_schema_version + ) + ) def set_db_schema_version(version): diff --git a/aiida/backends/tests/cmdline/commands/test_code.py b/aiida/backends/tests/cmdline/commands/test_code.py index fd9d15baee..84882c0676 100644 --- a/aiida/backends/tests/cmdline/commands/test_code.py +++ b/aiida/backends/tests/cmdline/commands/test_code.py @@ -192,7 +192,7 @@ def test_relabel_code_full_bad(self): result = self.cli_runner.invoke(relabel, [str(self.code.pk), 'new_code@otherstuff']) self.assertIsNotNone(result.exception) - def test_delete_one(self): + def test_code_delete_one(self): result = self.cli_runner.invoke(delete, [str(self.code.pk)]) self.assertIsNone(result.exception, result.output) @@ -200,6 +200,15 @@ def test_delete_one(self): from aiida.orm import Code Code.get_from_string('code') + def test_code_delete_one_force(self): + result = self.cli_runner.invoke(delete, [str(self.code.pk), '--force']) + self.assertIsNone(result.exception, result.output) + + with self.assertRaises(NotExistent): + from aiida.orm import Code + Code.get_from_string('code') + + def test_code_list(self): # set up second code 'code2' from aiida.orm import Code diff --git a/aiida/cmdline/commands/cmd_code.py b/aiida/cmdline/commands/cmd_code.py index 2af36a14a5..d959d9c3f3 100644 --- a/aiida/cmdline/commands/cmd_code.py +++ b/aiida/cmdline/commands/cmd_code.py @@ -172,25 +172,25 @@ def show(code, verbose): @verdi_code.command() @arguments.CODES() +@options.VERBOSE() +@options.DRY_RUN() +@options.FORCE() @with_dbenv() -def delete(codes): +def delete(codes, verbose, dry_run, force): """Delete a code. - Note that it is possible to delete a code only if it has not yet been used - as an input of a calculation, i.e., if it does not have outgoing links. + Note that codes are part of the data provenance, and deleting a code will delete all calculations using it. """ - from aiida.common.exceptions import InvalidOperation - from aiida.orm import Node + from aiida.manage.database.delete.nodes import delete_nodes - for code in codes: - try: - pk = code.pk - full_label = code.full_label - Node.objects.delete(pk) # pylint: disable=no-member - except InvalidOperation as exception: - echo.echo_error(str(exception)) - else: - echo.echo_success('Code<{}> {} deleted'.format(pk, full_label)) + verbosity = 1 + if force: + verbosity = 0 + elif verbose: + verbosity = 2 + + node_pks_to_delete = [code.pk for code in codes] + delete_nodes(node_pks_to_delete, dry_run=dry_run, verbosity=verbosity, force=force) @verdi_code.command()