From 14ecf15c7fbbd43935e1915b95da3a81c44d32ea Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Tue, 18 Dec 2018 09:20:10 +0100 Subject: [PATCH] Implement `verdi config` (#2354) The new command `verdi config` replaces the various commands under `verdi devel` that were used to list, get, set and unset configuration options, that were used to be called "properties". Since the term "property" is a reserved keyword, it was decided to rename them to "options", which is also the term used by "git". The interface of `verdi config` also mirrors that of `git config`. That is to say to get the value of an option simply call `git config ` and to set it `git config `. To unset it, the `--unset` flag can be used. Finally, the getting, setting or unsetting can be applied to a certain "scope", meaning to be configuration wide or profile specific. To make the implementation of this command simple and clean, the bulk of the work was in refactoring the definition, construction and operation on the configuration of an AiiDA instance. This is now represented by the `Config` class, through which these configuration options can be set or unset as well as retrieved. --- .ci/setup_profiles.sh | 2 +- .pre-commit-config.yaml | 14 - aiida/__init__.py | 22 +- aiida/backends/djsite/settings/settings.py | 4 +- aiida/backends/profile.py | 4 +- aiida/backends/sqlalchemy/tests/migrations.py | 14 +- aiida/backends/sqlalchemy/utils.py | 4 +- aiida/backends/testbase.py | 15 +- aiida/backends/testimplbase.py | 6 +- aiida/backends/tests/__init__.py | 5 +- .../cmdline/commands/test_calculation.py | 44 +-- .../tests/cmdline/commands/test_code.py | 18 +- .../tests/cmdline/commands/test_comment.py | 6 +- .../tests/cmdline/commands/test_computer.py | 40 +-- .../tests/cmdline/commands/test_config.py | 83 ++++++ .../tests/cmdline/commands/test_data.py | 22 +- .../tests/cmdline/commands/test_devel.py | 157 ----------- .../tests/cmdline/commands/test_export.py | 10 +- .../tests/cmdline/commands/test_node.py | 22 +- .../tests/cmdline/commands/test_process.py | 4 +- .../tests/cmdline/commands/test_profile.py | 215 ++++++--------- .../tests/cmdline/commands/test_work.py | 30 +-- .../cmdline/params/types/test_calculation.py | 23 +- .../tests/cmdline/params/types/test_code.py | 34 +-- .../cmdline/params/types/test_computer.py | 20 +- .../tests/cmdline/params/types/test_data.py | 20 +- .../tests/cmdline/params/types/test_group.py | 20 +- .../cmdline/params/types/test_identifier.py | 28 +- .../tests/cmdline/params/types/test_node.py | 20 +- .../tests/cmdline/params/types/test_plugin.py | 30 +-- .../cmdline/params/types/test_workflow.py | 8 +- .../tests/manage/configuration/test_config.py | 189 +++++++++++++ .../manage/configuration/test_options.py | 54 ++++ .../manage/configuration/test_profile.py | 57 ++++ aiida/backends/tests/utils/configuration.py | 106 ++++++++ aiida/cmdline/commands/__init__.py | 9 +- aiida/cmdline/commands/cmd_calculation.py | 4 +- aiida/cmdline/commands/cmd_config.py | 53 ++++ aiida/cmdline/commands/cmd_daemon.py | 6 +- aiida/cmdline/commands/cmd_devel.py | 94 ------- aiida/cmdline/commands/cmd_profile.py | 12 +- aiida/cmdline/commands/cmd_quicksetup.py | 30 +-- aiida/cmdline/commands/cmd_setup.py | 8 +- aiida/cmdline/commands/cmd_user.py | 6 +- aiida/cmdline/commands/cmd_verdi.py | 15 +- aiida/cmdline/params/arguments/__init__.py | 2 + aiida/cmdline/params/types/__init__.py | 10 +- aiida/cmdline/params/types/config.py | 39 +++ aiida/cmdline/params/types/profile.py | 8 +- aiida/cmdline/utils/common.py | 11 +- aiida/cmdline/utils/decorators.py | 4 +- aiida/cmdline/utils/defaults.py | 19 +- aiida/cmdline/utils/shell.py | 12 +- .../additions/backup_script/backup_base.py | 4 +- aiida/common/caching.py | 26 +- aiida/common/log.py | 23 +- aiida/common/setup.py | 255 +----------------- aiida/common/warnings.py | 2 +- aiida/control/profile.py | 29 +- aiida/daemon/client.py | 8 +- aiida/manage/configuration/__init__.py | 27 +- aiida/manage/configuration/config.py | 220 +++++++-------- .../configuration/migrations/migrations.py | 4 +- aiida/manage/configuration/options.py | 218 +++++++++++++++ aiida/manage/configuration/profile.py | 14 +- aiida/manage/configuration/utils.py | 74 +++++ aiida/manage/manager.py | 9 +- aiida/orm/querybuilder.py | 6 +- aiida/plugins/utils.py | 5 +- aiida/settings.py | 4 +- aiida/tools/dbexporters/tcod.py | 13 +- aiida/utils/fixtures.py | 22 +- aiida/utils/json.py | 8 +- aiida/work/rmq.py | 4 +- .../source/developer_guide/core/internals.rst | 2 +- docs/source/verdi/verdi_user_guide.rst | 18 +- docs/source/working_with_aiida/scripting.rst | 2 +- .../working_with_aiida/troubleshooting.rst | 4 +- 78 files changed, 1514 insertions(+), 1179 deletions(-) create mode 100644 aiida/backends/tests/cmdline/commands/test_config.py delete mode 100644 aiida/backends/tests/cmdline/commands/test_devel.py create mode 100644 aiida/backends/tests/manage/configuration/test_config.py create mode 100644 aiida/backends/tests/manage/configuration/test_options.py create mode 100644 aiida/backends/tests/manage/configuration/test_profile.py create mode 100644 aiida/backends/tests/utils/configuration.py create mode 100644 aiida/cmdline/commands/cmd_config.py create mode 100644 aiida/cmdline/params/types/config.py create mode 100644 aiida/manage/configuration/options.py create mode 100644 aiida/manage/configuration/utils.py diff --git a/.ci/setup_profiles.sh b/.ci/setup_profiles.sh index 245f654a70..654d96f400 100755 --- a/.ci/setup_profiles.sh +++ b/.ci/setup_profiles.sh @@ -23,5 +23,5 @@ then verdi profile setdefault $TEST_AIIDA_BACKEND # Set the polling interval to 0 otherwise the tests take too long - verdi devel setproperty runner.poll.interval 0 + verdi config runner.poll.interval 0 fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index acf6cee5f1..d0c9f860d0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,9 +10,7 @@ (?x)^( docs/.*| examples/.*| - aiida/backends/djsite/db/admin.py| aiida/backends/djsite/db/__init__.py| - aiida/backends/djsite/db/management/__init__.py| aiida/backends/djsite/db/migrations/0001_initial.py| aiida/backends/djsite/db/migrations/0002_db_state_change.py| aiida/backends/djsite/db/migrations/0003_add_link_type.py| @@ -32,13 +30,11 @@ aiida/backends/djsite/db/migrations/0017_drop_dbcalcstate.py| aiida/backends/djsite/db/migrations/__init__.py| aiida/backends/djsite/db/models.py| - aiida/backends/djsite/db/subtests/djangomigrations.py| aiida/backends/djsite/db/subtests/migrations.py| aiida/backends/djsite/db/subtests/generic.py| aiida/backends/djsite/db/subtests/__init__.py| aiida/backends/djsite/db/subtests/nodes.py| aiida/backends/djsite/db/subtests/query.py| - aiida/backends/djsite/db/views.py| aiida/backends/djsite/globalsettings.py| aiida/backends/djsite/__init__.py| aiida/backends/djsite/manage.py| @@ -103,22 +99,12 @@ aiida/backends/tests/cmdline/commands/test_comment.py| aiida/backends/tests/cmdline/commands/test_computer.py| aiida/backends/tests/cmdline/commands/test_data.py| - aiida/backends/tests/cmdline/commands/test_devel.py| aiida/backends/tests/cmdline/commands/test_export.py| aiida/backends/tests/cmdline/commands/test_group.py| aiida/backends/tests/cmdline/commands/test_node.py| aiida/backends/tests/cmdline/commands/test_user.py| aiida/backends/tests/cmdline/commands/test_work.py| aiida/backends/tests/cmdline/commands/test_workflow.py| - aiida/backends/tests/cmdline/params/types/test_calculation.py| - aiida/backends/tests/cmdline/params/types/test_code.py| - aiida/backends/tests/cmdline/params/types/test_computer.py| - aiida/backends/tests/cmdline/params/types/test_data.py| - aiida/backends/tests/cmdline/params/types/test_group.py| - aiida/backends/tests/cmdline/params/types/test_identifier.py| - aiida/backends/tests/cmdline/params/types/test_node.py| - aiida/backends/tests/cmdline/params/types/test_plugin.py| - aiida/backends/tests/cmdline/params/types/test_workflow.py| aiida/backends/tests/common/test_datastructures.py| aiida/backends/tests/computer.py| aiida/backends/tests/control/test_computer_ctrl.py| diff --git a/aiida/__init__.py b/aiida/__init__.py index 04d4c001f5..781bba9622 100644 --- a/aiida/__init__.py +++ b/aiida/__init__.py @@ -23,17 +23,18 @@ from __future__ import division from __future__ import print_function from __future__ import absolute_import + import warnings -from aiida.common.log import configure_logging -from aiida.common.setup import get_property import aiida.common.warnings +from aiida.common.log import configure_logging +from aiida.manage import get_config_option -__copyright__ = (u"Copyright (c), This file is part of the AiiDA platform. " - u"For further information please visit http://www.aiida.net/. All rights reserved.") -__license__ = "MIT license, see LICENSE.txt file." -__version__ = "1.0.0a4" -__authors__ = "The AiiDA team." +__copyright__ = (u'Copyright (c), This file is part of the AiiDA platform. ' + u'For further information please visit http://www.aiida.net/. All rights reserved.') +__license__ = 'MIT license, see LICENSE.txt file.' +__version__ = '1.0.0a4' +__authors__ = 'The AiiDA team.' __paper__ = (u'G. Pizzi, A. Cepellotti, R. Sabatini, N. Marzari, and B. Kozinsky,' u'"AiiDA: automated interactive infrastructure and database for computational science", ' u'Comp. Mat. Sci 111, 218-230 (2016); http://dx.doi.org/10.1016/j.commatsci.2015.09.013 ' @@ -43,10 +44,9 @@ # Configure the default logging configure_logging() -if get_property("warnings.showdeprecations"): - # If the user does not want to get AiiDA deprecation warnings, - # we disable them - this can be achieved with:: - # verdi devel setproperty warnings.showdeprecations False +if get_config_option('warnings.showdeprecations'): + # If the user does not want to get AiiDA deprecation warnings, we disable them - this can be achieved with:: + # verdi config warnings.showdeprecations False # Note that the AiidaDeprecationWarning does NOT inherit from DeprecationWarning warnings.simplefilter('default', aiida.common.warnings.AiidaDeprecationWarning) # pylint: disable=no-member # This should default to 'once', i.e. once per different message diff --git a/aiida/backends/djsite/settings/settings.py b/aiida/backends/djsite/settings/settings.py index 1bfca7160b..7e73b55c8c 100644 --- a/aiida/backends/djsite/settings/settings.py +++ b/aiida/backends/djsite/settings/settings.py @@ -20,7 +20,7 @@ from aiida.backends import settings from aiida.common.setup import parse_repository_uri -from aiida.manage import load_config +from aiida.manage import get_config from aiida.utils.timezone import get_current_timezone # Assumes that parent directory of aiida is root for @@ -31,7 +31,7 @@ sys.path = [BASE_DIR] + sys.path try: - CONFIG = load_config() + CONFIG = get_config() except MissingConfigurationError: raise MissingConfigurationError("Please run the AiiDA Installation, no config found") diff --git a/aiida/backends/profile.py b/aiida/backends/profile.py index a794bcee28..5783d2e22a 100644 --- a/aiida/backends/profile.py +++ b/aiida/backends/profile.py @@ -25,12 +25,12 @@ def load_profile(profile=None): Load the profile. This function is called by load_dbenv and SHOULD NOT be called by the user by hand. """ - from aiida.manage import load_config + from aiida.manage import get_config if settings.LOAD_PROFILE_CALLED: raise InvalidOperation('You cannot call load_profile multiple times!') - config = load_config() + config = get_config() settings.LOAD_PROFILE_CALLED = True diff --git a/aiida/backends/sqlalchemy/tests/migrations.py b/aiida/backends/sqlalchemy/tests/migrations.py index a96731c138..eaf6b716be 100644 --- a/aiida/backends/sqlalchemy/tests/migrations.py +++ b/aiida/backends/sqlalchemy/tests/migrations.py @@ -7,26 +7,22 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### - from __future__ import division from __future__ import absolute_import from __future__ import print_function -import copy -import unittest import os +import unittest + from alembic import command from alembic.config import Config from aiida.backends import sqlalchemy as sa from aiida.backends.sqlalchemy import utils from aiida.backends.sqlalchemy.models.base import Base -from aiida.backends.sqlalchemy.utils import (get_migration_head, - get_db_schema_version) -from aiida.backends.testbase import AiidaTestCase -from aiida.common.setup import set_property, get_property - from aiida.backends.sqlalchemy.tests.utils import new_database +from aiida.backends.sqlalchemy.utils import get_migration_head, get_db_schema_version +from aiida.backends.testbase import AiidaTestCase alembic_root = os.path.join(os.path.dirname(__file__), 'migrations', 'alembic') @@ -146,8 +142,6 @@ def test_migrations_forward_backward(self): alemb_table_names) # Delete only the tables that exist for table in tables_to_drop: - from psycopg2 import ProgrammingError - from sqlalchemy.orm import sessionmaker, scoped_session try: with sa.engine.begin() as connection: connection.execute('DROP TABLE {};'.format(table)) diff --git a/aiida/backends/sqlalchemy/utils.py b/aiida/backends/sqlalchemy/utils.py index b794714bb7..31744b7a6d 100644 --- a/aiida/backends/sqlalchemy/utils.py +++ b/aiida/backends/sqlalchemy/utils.py @@ -109,9 +109,9 @@ def _load_dbenv_noschemacheck(profile=None, connection=None): """ Load the SQLAlchemy database. """ - from aiida.manage import load_config + from aiida.manage import get_config - config = load_config() + config = get_config() profile = config.current_profile reset_session(profile.dictionary) diff --git a/aiida/backends/testbase.py b/aiida/backends/testbase.py index ebc9b70232..66cb6311e1 100644 --- a/aiida/backends/testbase.py +++ b/aiida/backends/testbase.py @@ -10,20 +10,19 @@ from __future__ import division from __future__ import absolute_import from __future__ import print_function + import os import sys import unittest -from unittest import ( - TestSuite, defaultTestLoader as test_loader) +import traceback from tornado import ioloop -import traceback from aiida.backends import settings from aiida.backends.tests import get_db_test_list from aiida.common.exceptions import ConfigurationError, TestsNotAllowedError, InternalError from aiida.common.utils import classproperty -from aiida.manage import get_manager, reset_manager +from aiida.manage import reset_manager def check_if_tests_can_run(): @@ -164,6 +163,10 @@ def tearDownClass(cls, *args, **kwargs): cls.clean_db() cls.__backend_instance.tearDownClass_method(*args, **kwargs) + def assertClickSuccess(self, cli_result): + self.assertEqual(cli_result.exit_code, 0) + self.assertClickResultNoException(cli_result) + def assertClickResultNoException(self, cli_result): self.assertIsNone(cli_result.exception, ''.join(traceback.format_exception(*cli_result.exc_info))) @@ -174,7 +177,7 @@ def run_aiida_db_tests(tests_to_run, verbose=False): Return the list of test results. """ # Empty test suite that will be populated - test_suite = TestSuite() + test_suite = unittest.TestSuite() actually_run_tests = [] num_tests_expected = 0 @@ -196,7 +199,7 @@ def run_aiida_db_tests(tests_to_run, verbose=False): for modulename in modulenames: if modulename not in found_modulenames: try: - test_suite.addTest(test_loader.loadTestsFromName(modulename)) + test_suite.addTest(unittest.defaultTestLoader.loadTestsFromName(modulename)) except AttributeError as exception: try: import importlib diff --git a/aiida/backends/testimplbase.py b/aiida/backends/testimplbase.py index 550f740d19..320ffed7ec 100644 --- a/aiida/backends/testimplbase.py +++ b/aiida/backends/testimplbase.py @@ -16,7 +16,6 @@ from aiida.common.exceptions import InternalError from aiida import orm -from aiida.manage import get_manager @six.add_metaclass(ABCMeta) @@ -77,6 +76,8 @@ def insert_data(self): """ This method inserts default data into the database. """ + from aiida.manage import get_config + self.computer = orm.Computer( name='localhost', hostname='localhost', @@ -86,7 +87,8 @@ def insert_data(self): backend=self.backend ).store() - email = get_manager().get_profile().default_user_email + config = get_config() + email = config.current_profile.default_user_email self.user = orm.User(email=email).store() self.user_email = email diff --git a/aiida/backends/tests/__init__.py b/aiida/backends/tests/__init__.py index 0bec639749..ef2c929416 100644 --- a/aiida/backends/tests/__init__.py +++ b/aiida/backends/tests/__init__.py @@ -53,9 +53,9 @@ 'cmdline.commands.code': ['aiida.backends.tests.cmdline.commands.test_code'], 'cmdline.commands.comment': ['aiida.backends.tests.cmdline.commands.test_comment'], 'cmdline.commands.computer': ['aiida.backends.tests.cmdline.commands.test_computer'], + 'cmdline.commands.config': ['aiida.backends.tests.cmdline.commands.test_config'], 'cmdline.commands.data': ['aiida.backends.tests.cmdline.commands.test_data'], 'cmdline.commands.database': ['aiida.backends.tests.cmdline.commands.test_database'], - 'cmdline.commands.devel': ['aiida.backends.tests.cmdline.commands.test_devel'], 'cmdline.commands.export': ['aiida.backends.tests.cmdline.commands.test_export'], 'cmdline.commands.graph': ['aiida.backends.tests.cmdline.commands.test_graph'], 'cmdline.commands.group': ['aiida.backends.tests.cmdline.commands.test_group'], @@ -80,7 +80,10 @@ 'common.archive': ['aiida.backends.tests.common.test_archive'], 'common.datastructures': ['aiida.backends.tests.common.test_datastructures'], 'daemon.client': ['aiida.backends.tests.daemon.test_client'], + 'manage.configuration.config.': ['aiida.backends.tests.manage.configuration.test_config'], 'manage.configuration.migrations.': ['aiida.backends.tests.manage.configuration.migrations.test_migrations'], + 'manage.configuration.options.': ['aiida.backends.tests.manage.configuration.test_options'], + 'manage.configuration.profile.': ['aiida.backends.tests.manage.configuration.test_profile'], 'orm.authinfo': ['aiida.backends.tests.orm.authinfo'], 'orm.comments': ['aiida.backends.tests.orm.comments'], 'orm.computer': ['aiida.backends.tests.computer'], diff --git a/aiida/backends/tests/cmdline/commands/test_calculation.py b/aiida/backends/tests/cmdline/commands/test_calculation.py index c3ae98b04d..7318550eb3 100644 --- a/aiida/backends/tests/cmdline/commands/test_calculation.py +++ b/aiida/backends/tests/cmdline/commands/test_calculation.py @@ -153,7 +153,7 @@ def test_calculation_list_all_user(self): options = ['-r', '-a', flag] result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 14) + self.assertEqual(len(get_result_lines(result)), 14) def test_calculation_list_all(self): """Test verdi calculation list with the all option""" @@ -162,13 +162,13 @@ def test_calculation_list_all(self): options = ['-r'] result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 7, result.output) + self.assertEqual(len(get_result_lines(result)), 7, result.output) for flag in ['-a', '--all']: options = ['-r', flag] result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 14, result.output) + self.assertEqual(len(get_result_lines(result)), 14, result.output) def test_calculation_list_limit(self): """Test verdi calculation list with the limit option""" @@ -177,7 +177,7 @@ def test_calculation_list_limit(self): options = ['-r', flag, limit] result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), limit) + self.assertEqual(len(get_result_lines(result)), limit) def test_calculation_list_project(self): """Test verdi calculation list with the project option""" @@ -201,9 +201,9 @@ def test_calculation_list_process_state(self): self.assertIsNone(result.exception, result.output) if state == 'finished': - self.assertEquals(len(get_result_lines(result)), 7, result.output) + self.assertEqual(len(get_result_lines(result)), 7, result.output) else: - self.assertEquals(len(get_result_lines(result)), 7, result.output) + self.assertEqual(len(get_result_lines(result)), 7, result.output) def test_calculation_list_failed(self): """Test verdi calculation list with the failed filter""" @@ -212,7 +212,7 @@ def test_calculation_list_failed(self): result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 4, result.output) + self.assertEqual(len(get_result_lines(result)), 4, result.output) def test_calculation_list_exit_status(self): """Test verdi calculation list with the exit status filter""" @@ -222,7 +222,7 @@ def test_calculation_list_exit_status(self): result = self.cli_runner.invoke(command.calculation_list, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) + self.assertEqual(len(get_result_lines(result)), 1) def test_calculation_show(self): """Test verdi calculation show""" @@ -231,7 +231,7 @@ def test_calculation_show(self): options = [] result = self.cli_runner.invoke(command.calculation_show, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 0) + self.assertEqual(len(get_result_lines(result)), 0) # Giving a single identifier should print a non empty string message options = [str(self.calcs[0].pk)] @@ -252,7 +252,7 @@ def test_calculation_logshow(self): options = [] result = self.cli_runner.invoke(command.calculation_logshow, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 0) + self.assertEqual(len(get_result_lines(result)), 0) # Giving a single identifier should print a non empty string message options = [str(self.calcs[0].pk)] @@ -292,7 +292,7 @@ def test_calculation_inputls(self): options = [self.arithmetic_job.uuid] result = self.cli_runner.invoke(command.calculation_inputls, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 3) + self.assertEqual(len(get_result_lines(result)), 3) self.assertIn('.aiida', get_result_lines(result)) self.assertIn('aiida.in', get_result_lines(result)) self.assertIn('_aiidasubmit.sh', get_result_lines(result)) @@ -300,7 +300,7 @@ def test_calculation_inputls(self): options = [self.arithmetic_job.uuid, '.aiida'] result = self.cli_runner.invoke(command.calculation_inputls, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 2) + self.assertEqual(len(get_result_lines(result)), 2) self.assertIn('calcinfo.json', get_result_lines(result)) self.assertIn('job_tmpl.json', get_result_lines(result)) @@ -313,7 +313,7 @@ def test_calculation_outputls(self): options = [self.arithmetic_job.uuid] result = self.cli_runner.invoke(command.calculation_outputls, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 3) + self.assertEqual(len(get_result_lines(result)), 3) self.assertIn('_scheduler-stderr.txt', get_result_lines(result)) self.assertIn('_scheduler-stdout.txt', get_result_lines(result)) self.assertIn('aiida.out', get_result_lines(result)) @@ -321,7 +321,7 @@ def test_calculation_outputls(self): options = [self.arithmetic_job.uuid, 'aiida.out'] result = self.cli_runner.invoke(command.calculation_outputls, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) + self.assertEqual(len(get_result_lines(result)), 1) self.assertIn('aiida.out', get_result_lines(result)) def test_calculation_inputcat(self): @@ -333,14 +333,14 @@ def test_calculation_inputcat(self): options = [self.arithmetic_job.uuid] result = self.cli_runner.invoke(command.calculation_inputcat, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) - self.assertEquals(get_result_lines(result)[0], '2 3') + self.assertEqual(len(get_result_lines(result)), 1) + self.assertEqual(get_result_lines(result)[0], '2 3') options = [self.arithmetic_job.uuid, 'aiida.in'] result = self.cli_runner.invoke(command.calculation_inputcat, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) - self.assertEquals(get_result_lines(result)[0], '2 3') + self.assertEqual(len(get_result_lines(result)), 1) + self.assertEqual(get_result_lines(result)[0], '2 3') def test_calculation_outputcat(self): """Test verdi calculation outputcat""" @@ -351,14 +351,14 @@ def test_calculation_outputcat(self): options = [self.arithmetic_job.uuid] result = self.cli_runner.invoke(command.calculation_outputcat, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) - self.assertEquals(get_result_lines(result)[0], '5') + self.assertEqual(len(get_result_lines(result)), 1) + self.assertEqual(get_result_lines(result)[0], '5') options = [self.arithmetic_job.uuid, 'aiida.out'] result = self.cli_runner.invoke(command.calculation_outputcat, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) - self.assertEquals(get_result_lines(result)[0], '5') + self.assertEqual(len(get_result_lines(result)), 1) + self.assertEqual(get_result_lines(result)[0], '5') def test_calculation_cleanworkdir(self): """Test verdi calculation cleanworkdir""" diff --git a/aiida/backends/tests/cmdline/commands/test_code.py b/aiida/backends/tests/cmdline/commands/test_code.py index 9595743371..cb62b5b508 100644 --- a/aiida/backends/tests/cmdline/commands/test_code.py +++ b/aiida/backends/tests/cmdline/commands/test_code.py @@ -159,13 +159,13 @@ def test_relabel_code(self): self.assertIsNone(result.exception, result.output) from aiida.orm import load_node new_code = load_node(self.code.pk) - self.assertEquals(new_code.label, 'new_code') + self.assertEqual(new_code.label, 'new_code') def test_relabel_code_full(self): self.cli_runner.invoke(relabel, [str(self.code.pk), 'new_code@comp']) from aiida.orm import load_node new_code = load_node(self.code.pk) - self.assertEquals(new_code.label, 'new_code') + self.assertEqual(new_code.label, 'new_code') def test_relabel_code_full_bad(self): result = self.cli_runner.invoke(relabel, [str(self.code.pk), 'new_code@otherstuff']) @@ -228,9 +228,9 @@ def test_code_duplicate_interactive(self): from aiida.orm import Code new_code = Code.get_from_string(label) - self.assertEquals(self.code.description, new_code.description) - self.assertEquals(self.code.get_prepend_text(), new_code.get_prepend_text()) - self.assertEquals(self.code.get_append_text(), new_code.get_append_text()) + self.assertEqual(self.code.description, new_code.description) + self.assertEqual(self.code.get_prepend_text(), new_code.get_prepend_text()) + self.assertEqual(self.code.get_append_text(), new_code.get_append_text()) def test_code_duplicate_non_interactive(self): label = 'code_duplicate_noninteractive' @@ -239,7 +239,7 @@ def test_code_duplicate_non_interactive(self): from aiida.orm import Code new_code = Code.get_from_string(label) - self.assertEquals(self.code.description, new_code.description) - self.assertEquals(self.code.get_prepend_text(), new_code.get_prepend_text()) - self.assertEquals(self.code.get_append_text(), new_code.get_append_text()) - self.assertEquals(self.code.get_input_plugin_name(), new_code.get_input_plugin_name()) + self.assertEqual(self.code.description, new_code.description) + self.assertEqual(self.code.get_prepend_text(), new_code.get_prepend_text()) + self.assertEqual(self.code.get_append_text(), new_code.get_append_text()) + self.assertEqual(self.code.get_input_plugin_name(), new_code.get_input_plugin_name()) diff --git a/aiida/backends/tests/cmdline/commands/test_comment.py b/aiida/backends/tests/cmdline/commands/test_comment.py index d90255e6f0..c6bd51c686 100644 --- a/aiida/backends/tests/cmdline/commands/test_comment.py +++ b/aiida/backends/tests/cmdline/commands/test_comment.py @@ -48,16 +48,16 @@ def test_comment_add(self): self.assertEqual(result.exit_code, 0) comment = self.node.get_comments() - self.assertEquals(len(comment), 1) + self.assertEqual(len(comment), 1) self.assertEqual(comment[0].content, COMMENT) def test_comment_remove(self): """Test removing a comment.""" comment = self.node.add_comment(COMMENT) - self.assertEquals(len(self.node.get_comments()), 1) + self.assertEqual(len(self.node.get_comments()), 1) options = [str(comment.pk), '--force'] result = self.cli_runner.invoke(cmd_comment.remove, options, catch_exceptions=False) self.assertEqual(result.exit_code, 0, result.output) - self.assertEquals(len(self.node.get_comments()), 0) + self.assertEqual(len(self.node.get_comments()), 0) diff --git a/aiida/backends/tests/cmdline/commands/test_computer.py b/aiida/backends/tests/cmdline/commands/test_computer.py index 4b69974f22..34ba037aa2 100644 --- a/aiida/backends/tests/cmdline/commands/test_computer.py +++ b/aiida/backends/tests/cmdline/commands/test_computer.py @@ -734,16 +734,16 @@ def test_computer_duplicate_interactive(self): self.assertIsNone(result.exception, result.output) new_computer = orm.Computer.objects.get(name=label) - self.assertEquals(self.comp.description, new_computer.description) - self.assertEquals(self.comp.get_hostname(), new_computer.get_hostname()) - self.assertEquals(self.comp.get_transport_type(), new_computer.get_transport_type()) - self.assertEquals(self.comp.get_scheduler_type(), new_computer.get_scheduler_type()) - self.assertEquals(self.comp.get_shebang(), new_computer.get_shebang()) - self.assertEquals(self.comp.get_workdir(), new_computer.get_workdir()) - self.assertEquals(self.comp.get_mpirun_command(), new_computer.get_mpirun_command()) - self.assertEquals(self.comp.get_default_mpiprocs_per_machine(), new_computer.get_default_mpiprocs_per_machine()) - self.assertEquals(self.comp.get_prepend_text(), new_computer.get_prepend_text()) - self.assertEquals(self.comp.get_append_text(), new_computer.get_append_text()) + self.assertEqual(self.comp.description, new_computer.description) + self.assertEqual(self.comp.get_hostname(), new_computer.get_hostname()) + self.assertEqual(self.comp.get_transport_type(), new_computer.get_transport_type()) + self.assertEqual(self.comp.get_scheduler_type(), new_computer.get_scheduler_type()) + self.assertEqual(self.comp.get_shebang(), new_computer.get_shebang()) + self.assertEqual(self.comp.get_workdir(), new_computer.get_workdir()) + self.assertEqual(self.comp.get_mpirun_command(), new_computer.get_mpirun_command()) + self.assertEqual(self.comp.get_default_mpiprocs_per_machine(), new_computer.get_default_mpiprocs_per_machine()) + self.assertEqual(self.comp.get_prepend_text(), new_computer.get_prepend_text()) + self.assertEqual(self.comp.get_append_text(), new_computer.get_append_text()) def test_computer_duplicate_non_interactive(self): label = 'computer_duplicate_noninteractive' @@ -752,13 +752,13 @@ def test_computer_duplicate_non_interactive(self): self.assertIsNone(result.exception, result.output) new_computer = orm.Computer.objects.get(name=label) - self.assertEquals(self.comp.description, new_computer.description) - self.assertEquals(self.comp.get_hostname(), new_computer.get_hostname()) - self.assertEquals(self.comp.get_transport_type(), new_computer.get_transport_type()) - self.assertEquals(self.comp.get_scheduler_type(), new_computer.get_scheduler_type()) - self.assertEquals(self.comp.get_shebang(), new_computer.get_shebang()) - self.assertEquals(self.comp.get_workdir(), new_computer.get_workdir()) - self.assertEquals(self.comp.get_mpirun_command(), new_computer.get_mpirun_command()) - self.assertEquals(self.comp.get_default_mpiprocs_per_machine(), new_computer.get_default_mpiprocs_per_machine()) - self.assertEquals(self.comp.get_prepend_text(), new_computer.get_prepend_text()) - self.assertEquals(self.comp.get_append_text(), new_computer.get_append_text()) + self.assertEqual(self.comp.description, new_computer.description) + self.assertEqual(self.comp.get_hostname(), new_computer.get_hostname()) + self.assertEqual(self.comp.get_transport_type(), new_computer.get_transport_type()) + self.assertEqual(self.comp.get_scheduler_type(), new_computer.get_scheduler_type()) + self.assertEqual(self.comp.get_shebang(), new_computer.get_shebang()) + self.assertEqual(self.comp.get_workdir(), new_computer.get_workdir()) + self.assertEqual(self.comp.get_mpirun_command(), new_computer.get_mpirun_command()) + self.assertEqual(self.comp.get_default_mpiprocs_per_machine(), new_computer.get_default_mpiprocs_per_machine()) + self.assertEqual(self.comp.get_prepend_text(), new_computer.get_prepend_text()) + self.assertEqual(self.comp.get_append_text(), new_computer.get_append_text()) diff --git a/aiida/backends/tests/cmdline/commands/test_config.py b/aiida/backends/tests/cmdline/commands/test_config.py new file mode 100644 index 0000000000..0594876712 --- /dev/null +++ b/aiida/backends/tests/cmdline/commands/test_config.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Tests for `verdi config`.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +from click.testing import CliRunner + +from aiida.backends.testbase import AiidaTestCase +from aiida.backends.tests.utils.configuration import with_temporary_config_instance +from aiida.cmdline.commands import cmd_verdi +from aiida.manage import get_config + + +class TestVerdiConfig(AiidaTestCase): + """Tests for `verdi config`.""" + + def setUp(self): + self.cli_runner = CliRunner() + + @with_temporary_config_instance + def test_config_option_set(self): + """Test the `verdi config` command when setting an option.""" + config = get_config() + + option_name = 'daemon.timeout' + option_values = [str(10), str(20)] + + for option_value in option_values: + options = ['config', option_name, str(option_value)] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + + self.assertClickSuccess(result) + self.assertEqual(str(config.option_get(option_name, scope=config.current_profile.name)), option_value) + + @with_temporary_config_instance + def test_config_option_get(self): + """Test the `verdi config` command when getting an option.""" + option_name = 'daemon.timeout' + option_value = str(30) + + options = ['config', option_name, option_value] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + self.assertClickResultNoException(result) + + options = ['config', option_name] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + self.assertIn(option_value, result.output.strip()) + + @with_temporary_config_instance + def test_config_option_unset(self): + """Test the `verdi config` command when unsetting an option.""" + option_name = 'daemon.timeout' + option_value = str(30) + + options = ['config', option_name, str(option_value)] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + + options = ['config', option_name] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + self.assertIn(option_value, result.output.strip()) + + options = ['config', option_name, '--unset'] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + self.assertIn('{} unset'.format(option_name), result.output.strip()) + + options = ['config', option_name] + result = self.cli_runner.invoke(cmd_verdi.verdi, options) + self.assertClickSuccess(result) + self.assertEqual(result.output, '') diff --git a/aiida/backends/tests/cmdline/commands/test_data.py b/aiida/backends/tests/cmdline/commands/test_data.py index 678a3aff38..f22cee3ff9 100644 --- a/aiida/backends/tests/cmdline/commands/test_data.py +++ b/aiida/backends/tests/cmdline/commands/test_data.py @@ -94,7 +94,7 @@ def data_export_test(self, datatype, ids, supported_formats): # Check that the simple command works as expected options = [str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command did not finish " "correctly") + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly") dump_flags = ['-F', '--format'] for flag in dump_flags: @@ -103,7 +103,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, format, str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly. Output:\n{}".format(res.output)) @@ -117,7 +117,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, filepath, str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command should finish correctly." "Output:\n{}".format(res.output)) @@ -131,7 +131,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, filepath, '-f', str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command should finish correctly." "Output: {}".format(res.output)) finally: @@ -288,7 +288,7 @@ def test_arrayshow(self): # with captured_output() as (out, err): options = [str(self.a.id)] res = self.cli_runner.invoke(cmd_array.array_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command did not finish " "correctly") + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly") class TestVerdiDataBands(AiidaTestCase, TestVerdiDataListable): @@ -384,7 +384,7 @@ def test_bandexporthelp(self): def test_bandsexport(self): options = [str(self.ids[TestVerdiDataListable.NODE_ID_STR])] res = self.cli_runner.invoke(cmd_bands.bands_export, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, 'The command did not finish correctly') + self.assertEqual(res.exit_code, 0, 'The command did not finish correctly') self.assertIn(b"[1.0, 3.0]", res.output_bytes, 'The string [1.0, 3.0] was not found in the bands' 'export') @@ -413,7 +413,7 @@ def test_parametershow(self): for format in supported_formats: options = [str(self.p.id)] res = self.cli_runner.invoke(cmd_parameter.parameter_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data parameter show did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data parameter show did not" " finish correctly") self.assertIn(b'"a": 1', res.output_bytes, 'The string "a": 1 was not found in the output' ' of verdi data parameter show') @@ -448,7 +448,7 @@ def test_remoteshowhelp(self): def test_remoteshow(self): options = [str(self.r.id)] res = self.cli_runner.invoke(cmd_remote.remote_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data remote show did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data remote show did not" " finish correctly") self.assertIn(b'Remote computer name:', res.output_bytes, 'The string "Remote computer name:" was not found in the' ' output of verdi data remote show') @@ -463,7 +463,7 @@ def test_remotelshelp(self): def test_remotels(self): options = ['--long', str(self.r.id)] res = self.cli_runner.invoke(cmd_remote.remote_ls, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data remote ls did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data remote ls did not" " finish correctly") self.assertIn(b'file.txt', res.output_bytes, 'The file "file.txt" was not found in the output' ' of verdi data remote ls') @@ -474,7 +474,7 @@ def test_remotecathelp(self): def test_remotecat(self): options = [str(self.r.id), 'file.txt'] res = self.cli_runner.invoke(cmd_remote.remote_cat, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data parameter cat did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data parameter cat did not" " finish correctly") self.assertIn(b'test string', res.output_bytes, 'The string "test string" was not found in the output' ' of verdi data remote cat file.txt') @@ -589,7 +589,7 @@ def test_export(self): for flag in dump_flags: options = [flag, 'tcod', '-i', '0', str(self.ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(trajectory_export, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, 'The command did not finish correctly') + self.assertEqual(res.exit_code, 0, 'The command did not finish correctly') class TestVerdiDataStructure(AiidaTestCase, TestVerdiDataListable, TestVerdiDataExportable): diff --git a/aiida/backends/tests/cmdline/commands/test_devel.py b/aiida/backends/tests/cmdline/commands/test_devel.py deleted file mode 100644 index aef591ceb9..0000000000 --- a/aiida/backends/tests/cmdline/commands/test_devel.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -########################################################################### -# Copyright (c), The AiiDA team. All rights reserved. # -# This file is part of the AiiDA code. # -# # -# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # -# For further information on the license, see the LICENSE.txt file # -# For further information please visit http://www.aiida.net # -########################################################################### -"""Tests for `verdi devel`.""" -from __future__ import division -from __future__ import print_function -from __future__ import absolute_import - -import tempfile - -from click.testing import CliRunner - -from aiida.backends.testbase import AiidaTestCase -from aiida.cmdline.commands import cmd_devel -from aiida.common.setup import create_config_noninteractive, _property_table, exists_property, get_property, del_property -from aiida.manage.configuration.setup import create_instance_directories -from aiida.manage.configuration import settings -from aiida.manage import load_config - - -def get_result_lines(result): - return [e for e in result.output.split('\n') if e] - - -class TestVerdiDevel(AiidaTestCase): - """Tests for `verdi devel`.""" - - @classmethod - def setUpClass(cls, *args, **kwargs): - super(TestVerdiDevel, cls).setUpClass() - from aiida.manage.configuration import config as config_module - - cls._new_aiida_config_folder = tempfile.mkdtemp() - cls._old_aiida_config_folder = settings.AIIDA_CONFIG_FOLDER - cls._old_config = config_module.CONFIG - - config_module.CONFIG = None - settings.AIIDA_CONFIG_FOLDER = cls._new_aiida_config_folder - create_instance_directories() - - cls.profile_name = 'aiida_dummy_profile_1234' - cls.dummy_profile = {} - cls.dummy_profile['backend'] = 'django' - cls.dummy_profile['db_host'] = 'localhost' - cls.dummy_profile['db_port'] = '5432' - cls.dummy_profile['email'] = 'dummy@localhost' - cls.dummy_profile['db_name'] = cls.profile_name - cls.dummy_profile['db_user'] = 'dummy_user' - cls.dummy_profile['db_pass'] = 'dummy_pass' - cls.dummy_profile['repo'] = settings.AIIDA_CONFIG_FOLDER + '/repository_' + cls.profile_name - - create_config_noninteractive(profile_name=cls.profile_name, **cls.dummy_profile) - - config = load_config() - config.set_default_profile(cls.profile_name, overwrite=True) - - @classmethod - def tearDownClass(cls, *args, **kwargs): - """ - Remove dummy profiles created in setUpClass. - - :param args: list of arguments - :param kwargs: list of keyword arguments - """ - import os - import shutil - from aiida.manage.configuration import config as config_module - - config_module.CONFIG = cls._old_config - settings.AIIDA_CONFIG_FOLDER = cls._old_aiida_config_folder - - if os.path.isdir(cls._new_aiida_config_folder): - shutil.rmtree(cls._new_aiida_config_folder) - - def setUp(self): - self.cli_runner = CliRunner() - - def tearDown(self): - """Delete any properties that were set.""" - for prop in sorted(_property_table.keys()): - if exists_property(prop): - del_property(prop) - - def test_describe_properties(self): - """Test the `verdi devel describeproperties` command""" - result = self.cli_runner.invoke(cmd_devel.devel_describeproperties, []) - - self.assertIsNone(result.exception, result.output) - self.assertTrue(len(get_result_lines(result)) > 0) - - for prop in _property_table: - self.assertIn(prop, result.output) - - def test_list_properties(self): - """Test the `verdi devel listproperties` command""" - - # Since dummy configuration does not have explicit properties set, with default options the result is empty - options = [] - result = self.cli_runner.invoke(cmd_devel.devel_listproperties, options) - self.assertIsNone(result.exception, result.output) - self.assertTrue(len(get_result_lines(result)) == 0) - - # Toggling the all flag should show all available options - for flag in ['-a', '--all']: - - options = [flag] - result = self.cli_runner.invoke(cmd_devel.devel_listproperties, options) - self.assertIsNone(result.exception, result.output) - self.assertTrue(len(get_result_lines(result)) > 0) - - for prop in _property_table: - self.assertIn(prop, result.output) - - def test_set_property(self): - """Test the `verdi devel setproperty` command.""" - property_name = 'daemon.timeout' - property_values = [10, 20] - - for property_value in property_values: - options = [property_name, str(property_value)] - result = self.cli_runner.invoke(cmd_devel.devel_setproperty, options) - - self.assertIsNone(result.exception, result.output) - self.assertEquals(get_property(property_name), property_value) - - def test_get_property(self): - """Test the `verdi devel getproperty` command.""" - property_name = 'daemon.timeout' - property_value = 30 - - options = [property_name, str(property_value)] - result = self.cli_runner.invoke(cmd_devel.devel_setproperty, options) - - options = [property_name] - result = self.cli_runner.invoke(cmd_devel.devel_getproperty, options) - self.assertIsNone(result.exception, result.output) - self.assertEquals(str(property_value), result.output.strip()) - - def test_del_property(self): - """Test the `verdi devel delproperty` command.""" - property_name = 'daemon.timeout' - property_value = 30 - - options = [property_name, str(property_value)] - result = self.cli_runner.invoke(cmd_devel.devel_setproperty, options) - - options = [property_name] - result = self.cli_runner.invoke(cmd_devel.devel_delproperty, options) - self.assertIsNone(result.exception, result.output) - self.assertIn(property_name, result.output.strip()) - self.assertFalse(exists_property(property_name)) diff --git a/aiida/backends/tests/cmdline/commands/test_export.py b/aiida/backends/tests/cmdline/commands/test_export.py index 780dd539de..92ee1ea38c 100644 --- a/aiida/backends/tests/cmdline/commands/test_export.py +++ b/aiida/backends/tests/cmdline/commands/test_export.py @@ -177,7 +177,7 @@ def test_migrate_versions_old(self): result = self.cli_runner.invoke(cmd_export.migrate, options) self.assertIsNone(result.exception, result.output) self.assertTrue(os.path.isfile(filename_output)) - self.assertEquals(zipfile.ZipFile(filename_output).testzip(), None) + self.assertEqual(zipfile.ZipFile(filename_output).testzip(), None) finally: delete_temporary_file(filename_output) @@ -223,7 +223,7 @@ def test_migrate_force(self): result = self.cli_runner.invoke(cmd_export.migrate, options) self.assertIsNone(result.exception, result.output) self.assertTrue(os.path.isfile(filename_output)) - self.assertEquals(zipfile.ZipFile(filename_output).testzip(), None) + self.assertEqual(zipfile.ZipFile(filename_output).testzip(), None) def test_migrate_silent(self): """Test that the captured output is an empty string when the -s/--silent option is passed.""" @@ -240,10 +240,10 @@ def test_migrate_silent(self): try: options = [option, filename_input, filename_output] result = self.cli_runner.invoke(cmd_export.migrate, options) - self.assertEquals(result.output, '') + self.assertEqual(result.output, '') self.assertIsNone(result.exception, result.output) self.assertTrue(os.path.isfile(filename_output)) - self.assertEquals(zipfile.ZipFile(filename_output).testzip(), None) + self.assertEqual(zipfile.ZipFile(filename_output).testzip(), None) finally: delete_temporary_file(filename_output) @@ -290,4 +290,4 @@ def test_inspect(self): options = ['--version', filename_input] result = self.cli_runner.invoke(cmd_export.inspect, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(result.output.strip(), version_number) + self.assertEqual(result.output.strip(), version_number) diff --git a/aiida/backends/tests/cmdline/commands/test_node.py b/aiida/backends/tests/cmdline/commands/test_node.py index 84152fc368..d6c497321e 100644 --- a/aiida/backends/tests/cmdline/commands/test_node.py +++ b/aiida/backends/tests/cmdline/commands/test_node.py @@ -93,7 +93,7 @@ def data_export_test(self, datatype, ids, supported_formats): # Check that the simple command works as expected options = [str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command did not finish " "correctly") + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly") dump_flags = ['-F', '--format'] for flag in dump_flags: @@ -102,7 +102,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, format, str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly. Output:\n{}".format(res.output)) @@ -116,7 +116,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, filepath, str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command should finish correctly." "Output:\n{}".format(res.output)) @@ -130,7 +130,7 @@ def data_export_test(self, datatype, ids, supported_formats): options = [flag, filepath, '-f', str(ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(export_cmd, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, + self.assertEqual(res.exit_code, 0, "The command should finish correctly." "Output: {}".format(res.output)) finally: @@ -287,7 +287,7 @@ def test_arrayshow(self): # with captured_output() as (out, err): options = [str(self.a.id)] res = self.cli_runner.invoke(cmd_array.array_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command did not finish " "correctly") + self.assertEqual(res.exit_code, 0, "The command did not finish " "correctly") class TestVerdiDataBands(AiidaTestCase, TestVerdiDataListable): @@ -383,7 +383,7 @@ def test_bandexporthelp(self): def test_bandsexport(self): options = [str(self.ids[TestVerdiDataListable.NODE_ID_STR])] res = self.cli_runner.invoke(cmd_bands.bands_export, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, 'The command did not finish correctly') + self.assertEqual(res.exit_code, 0, 'The command did not finish correctly') self.assertIn(b"[1.0, 3.0]", res.output_bytes, 'The string [1.0, 3.0] was not found in the bands' 'export') @@ -412,7 +412,7 @@ def test_parametershow(self): for format in supported_formats: options = [str(self.p.id)] res = self.cli_runner.invoke(cmd_parameter.parameter_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data parameter show did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data parameter show did not" " finish correctly") self.assertIn(b'"a": 1', res.output_bytes, 'The string "a": 1 was not found in the output' ' of verdi data parameter show') @@ -449,7 +449,7 @@ def test_remoteshowhelp(self): def test_remoteshow(self): options = [str(self.r.id)] res = self.cli_runner.invoke(cmd_remote.remote_show, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data remote show did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data remote show did not" " finish correctly") self.assertIn(b'Remote computer name:', res.output_bytes, 'The string "Remote computer name:" was not found in the' ' output of verdi data remote show') @@ -464,7 +464,7 @@ def test_remotelshelp(self): def test_remotels(self): options = ['--long', str(self.r.id)] res = self.cli_runner.invoke(cmd_remote.remote_ls, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data remote ls did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data remote ls did not" " finish correctly") self.assertIn(b'file.txt', res.output_bytes, 'The file "file.txt" was not found in the output' ' of verdi data remote ls') @@ -475,7 +475,7 @@ def test_remotecathelp(self): def test_remotecat(self): options = [str(self.r.id), 'file.txt'] res = self.cli_runner.invoke(cmd_remote.remote_cat, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, "The command verdi data parameter cat did not" " finish correctly") + self.assertEqual(res.exit_code, 0, "The command verdi data parameter cat did not" " finish correctly") self.assertIn(b'test string', res.output_bytes, 'The string "test string" was not found in the output' ' of verdi data remote cat file.txt') @@ -590,7 +590,7 @@ def test_export(self): for flag in dump_flags: options = [flag, 'tcod', '-i', '0', str(self.ids[self.NODE_ID_STR])] res = self.cli_runner.invoke(trajectory_export, options, catch_exceptions=False) - self.assertEquals(res.exit_code, 0, 'The command did not finish correctly') + self.assertEqual(res.exit_code, 0, 'The command did not finish correctly') class TestVerdiDataStructure(AiidaTestCase, TestVerdiDataListable, TestVerdiDataExportable): diff --git a/aiida/backends/tests/cmdline/commands/test_process.py b/aiida/backends/tests/cmdline/commands/test_process.py index d72d48907c..d6cadbbb71 100644 --- a/aiida/backends/tests/cmdline/commands/test_process.py +++ b/aiida/backends/tests/cmdline/commands/test_process.py @@ -43,10 +43,10 @@ class TestVerdiProcessDaemon(AiidaTestCase): def setUp(self): super(TestVerdiProcessDaemon, self).setUp() - from aiida.manage import load_config + from aiida.manage import get_config from aiida.daemon.client import DaemonClient - profile = load_config().current_profile + profile = get_config().current_profile self.daemon_client = DaemonClient(profile) self.daemon_pid = subprocess.Popen( self.daemon_client.cmd_string.split(), stderr=sys.stderr, stdout=sys.stdout).pid diff --git a/aiida/backends/tests/cmdline/commands/test_profile.py b/aiida/backends/tests/cmdline/commands/test_profile.py index cd46fbf25c..49ff9cf532 100644 --- a/aiida/backends/tests/cmdline/commands/test_profile.py +++ b/aiida/backends/tests/cmdline/commands/test_profile.py @@ -7,183 +7,126 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### -"""Test suite to test the `verdi profile` commands.""" +"""Tests for `verdi profile`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import -import tempfile - from click.testing import CliRunner -from six.moves import range from aiida.backends.testbase import AiidaTestCase -from aiida.common.setup import create_config_noninteractive -from aiida.manage.configuration.setup import create_instance_directories -from aiida.manage.configuration import settings -from aiida.manage import load_config +from aiida.cmdline.commands import cmd_profile +from aiida.backends.tests.utils.configuration import create_mock_profile, with_temporary_config_instance +from aiida.manage import get_config class TestVerdiProfileSetup(AiidaTestCase): - """ - Test suite to test verdi profile command - """ + """Tests for `verdi profile`.""" - @classmethod - def setUpClass(cls, *args, **kwargs): - """ - Create dummy 5 profiles and set first profile as default profile. - All tests will run on these dummy profiles. + def setUp(self): + """Create a CLI runner to invoke the CLI commands.""" + super(TestVerdiProfileSetup, self).setUp() + self.cli_runner = CliRunner() + self.config = None + self.profile_list = [] - :param args: list of arguments - :param kwargs: list of keyword arguments - """ - super(TestVerdiProfileSetup, cls).setUpClass() - from aiida.manage.configuration import config as config_module - - cls._old_config = config_module.CONFIG - cls._new_aiida_config_folder = tempfile.mkdtemp() - cls._old_aiida_config_folder = settings.AIIDA_CONFIG_FOLDER - - config_module.CONFIG = None - settings.AIIDA_CONFIG_FOLDER = cls._new_aiida_config_folder - create_instance_directories() - - dummy_profile_list = ['dummy_profile1', 'dummy_profile2', 'dummy_profile3', 'dummy_profile4', 'dummy_profile5'] - dummy_profile_list = ["{}_{}".format(profile_name, get_random_string(4)) for profile_name in dummy_profile_list] - - for profile_name in dummy_profile_list: - dummy_profile = {} - dummy_profile['backend'] = 'django' - dummy_profile['db_host'] = 'localhost' - dummy_profile['db_port'] = '5432' - dummy_profile['email'] = 'dummy@localhost' - dummy_profile['db_name'] = profile_name - dummy_profile['db_user'] = 'dummy_user' - dummy_profile['db_pass'] = 'dummy_pass' - dummy_profile['repo'] = settings.AIIDA_CONFIG_FOLDER + '/repository_' + profile_name - create_config_noninteractive(profile_name=profile_name, **dummy_profile) - - config = load_config() - config.set_default_profile(dummy_profile_list[0], overwrite=True) - cls.dummy_profile_list = dummy_profile_list - - @classmethod - def tearDownClass(cls, *args, **kwargs): - """ - Remove dummy profiles created in setUpClass. + def mock_profiles(self): + """Create mock profiles and a runner object to invoke the CLI commands. - :param args: list of arguments - :param kwargs: list of keyword arguments + Note: this cannot be done in the `setUp` or `setUpClass` methods, because the temporary configuration instance + is not generated until the test function is entered, which calls the `with_temporary_config_instance` + decorator. """ - import os - import shutil - from aiida.manage.configuration import config as config_module + self.config = get_config() + self.profile_list = ['mock_profile1', 'mock_profile2', 'mock_profile3', 'mock_profile4'] - config_module.CONFIG = cls._old_config - settings.AIIDA_CONFIG_FOLDER = cls._old_aiida_config_folder + for profile_name in self.profile_list: + profile = create_mock_profile(profile_name) + self.config.add_profile(profile_name, profile) - if os.path.isdir(cls._new_aiida_config_folder): - shutil.rmtree(cls._new_aiida_config_folder) - - def setUp(self): - """ - Create runner object to run tests - """ - self.cli_runner = CliRunner() + self.config.set_default_profile(self.profile_list[0], overwrite=True).store() + @with_temporary_config_instance def test_help(self): - """ - Tests help text for all profile sub commands - """ - options = ["--help"] - from aiida.cmdline.commands.cmd_profile import (profile_list, profile_setdefault, profile_delete) + """Tests help text for all `verdi profile` commands.""" + self.mock_profiles() + + options = ['--help'] + + result = self.cli_runner.invoke(cmd_profile.profile_list, options) + self.assertClickSuccess(result) + self.assertIn('Usage', result.output) - result = self.cli_runner.invoke(profile_list, options) - self.assertIsNone(result.exception, result.output) + result = self.cli_runner.invoke(cmd_profile.profile_setdefault, options) + self.assertClickSuccess(result) self.assertIn('Usage', result.output) - result = self.cli_runner.invoke(profile_setdefault, options) - self.assertIsNone(result.exception, result.output) + result = self.cli_runner.invoke(cmd_profile.profile_delete, options) + self.assertClickSuccess(result) self.assertIn('Usage', result.output) - result = self.cli_runner.invoke(profile_delete, options) - self.assertIsNone(result.exception, result.output) + result = self.cli_runner.invoke(cmd_profile.profile_show, options) + self.assertClickSuccess(result) self.assertIn('Usage', result.output) + @with_temporary_config_instance def test_list(self): - """ - Test for verdi profile list command - """ - from aiida.cmdline.commands.cmd_profile import profile_list - result = self.cli_runner.invoke(profile_list) - self.assertIsNone(result.exception, result.output) - self.assertIn('configuration folder: ' + self._new_aiida_config_folder, result.output) - self.assertIn('* {}'.format(self.dummy_profile_list[0]), result.output) - self.assertIn(self.dummy_profile_list[1], result.output) + """Test the `verdi profile list` command.""" + self.mock_profiles() + + result = self.cli_runner.invoke(cmd_profile.profile_list) + self.assertClickSuccess(result) + self.assertIn('configuration folder: ' + self.config.dirpath, result.output) + self.assertIn('* {}'.format(self.profile_list[0]), result.output) + self.assertIn(self.profile_list[1], result.output) + @with_temporary_config_instance def test_setdefault(self): - """ - Test for verdi profile setdefault command - """ - from aiida.cmdline.commands.cmd_profile import profile_setdefault - result = self.cli_runner.invoke(profile_setdefault, [self.dummy_profile_list[1]]) - self.assertIsNone(result.exception, result.output) + """Test the `verdi profile setdefault` command.""" + self.mock_profiles() - from aiida.cmdline.commands.cmd_profile import profile_list - result = self.cli_runner.invoke(profile_list) + result = self.cli_runner.invoke(cmd_profile.profile_setdefault, [self.profile_list[1]]) + self.assertClickSuccess(result) - self.assertIsNone(result.exception, result.output) - self.assertIn('configuration folder: ' + self._new_aiida_config_folder, result.output) - self.assertIn('* {}'.format(self.dummy_profile_list[1]), result.output) - self.assertIsNone(result.exception, result.output) + result = self.cli_runner.invoke(cmd_profile.profile_list) + self.assertClickSuccess(result) + self.assertIn('configuration folder: ' + self.config.dirpath, result.output) + self.assertIn('* {}'.format(self.profile_list[1]), result.output) + self.assertClickSuccess(result) + + @with_temporary_config_instance def test_show(self): - """ - Test for verdi profile show command - """ - from aiida.cmdline.commands.cmd_profile import profile_show + """Test the `verdi profile show` command""" + self.mock_profiles() - config = load_config() - profile_name = self.dummy_profile_list[0] + config = get_config() + profile_name = self.profile_list[0] profile = config.get_profile(profile_name) - result = self.cli_runner.invoke(profile_show, [profile_name]) - self.assertIsNone(result.exception, result.output) + result = self.cli_runner.invoke(cmd_profile.profile_show, [profile_name]) + self.assertClickSuccess(result) for key, value in profile.dictionary.items(): self.assertIn(key.lower(), result.output) self.assertIn(value, result.output) + @with_temporary_config_instance def test_delete(self): - """ - Test for verdi profile delete command - """ - from aiida.cmdline.commands.cmd_profile import profile_delete, profile_list - - # delete single profile - result = self.cli_runner.invoke(profile_delete, ["--force", self.dummy_profile_list[2]]) - self.assertClickResultNoException(result) - - result = self.cli_runner.invoke(profile_list) - self.assertClickResultNoException(result) - - self.assertNotIn(self.dummy_profile_list[2], result.output) - self.assertClickResultNoException(result) - - # delete multiple profile - result = self.cli_runner.invoke(profile_delete, - ["--force", self.dummy_profile_list[3], self.dummy_profile_list[4]]) - self.assertClickResultNoException(result) + """Test the `verdi profile delete` command.""" + self.mock_profiles() - result = self.cli_runner.invoke(profile_list) - self.assertClickResultNoException(result) - self.assertNotIn(self.dummy_profile_list[3], result.output) - self.assertNotIn(self.dummy_profile_list[4], result.output) - self.assertClickResultNoException(result) + result = self.cli_runner.invoke(cmd_profile.profile_delete, ['--force', self.profile_list[1]]) + self.assertClickSuccess(result) + result = self.cli_runner.invoke(cmd_profile.profile_list) + self.assertClickSuccess(result) + self.assertNotIn(self.profile_list[1], result.output) -def get_random_string(length): - import string - import random + result = self.cli_runner.invoke(cmd_profile.profile_delete, + ['--force', self.profile_list[2], self.profile_list[3]]) + self.assertClickSuccess(result) - return ''.join(random.choice(string.ascii_letters) for m in range(length)) + result = self.cli_runner.invoke(cmd_profile.profile_list) + self.assertClickSuccess(result) + self.assertNotIn(self.profile_list[2], result.output) + self.assertNotIn(self.profile_list[3], result.output) diff --git a/aiida/backends/tests/cmdline/commands/test_work.py b/aiida/backends/tests/cmdline/commands/test_work.py index a1af4cf931..db1398dba1 100644 --- a/aiida/backends/tests/cmdline/commands/test_work.py +++ b/aiida/backends/tests/cmdline/commands/test_work.py @@ -46,7 +46,7 @@ def test_list(self): # Number of output lines in -r/--raw format should be zero when there are no calculations yet result = self.cli_runner.invoke(cmd_work.work_list, ['-r']) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 0) + self.assertEqual(len(get_result_lines(result)), 0) calcs = [] @@ -76,45 +76,45 @@ def test_list(self): # Default behavior should yield all active states (CREATED, RUNNING and WAITING) so six in total result = self.cli_runner.invoke(cmd_work.work_list, ['-r']) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 6) + self.assertEqual(len(get_result_lines(result)), 6) # Adding the all option should return all entries regardless of process state for flag in ['-a', '--all']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 12) + self.assertEqual(len(get_result_lines(result)), 12) # Passing the limit option should limit the results for flag in ['-l', '--limit']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag, '6']) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 6) + self.assertEqual(len(get_result_lines(result)), 6) # Filtering for a specific process state for flag in ['-S', '--process-state']: for flag_value in ['created', 'running', 'waiting', 'killed', 'excepted', 'finished']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag, flag_value]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 2) + self.assertEqual(len(get_result_lines(result)), 2) # Filtering for exit status should only get us one for flag in ['-E', '--exit-status']: for exit_status in ['0', '1']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag, exit_status]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) + self.assertEqual(len(get_result_lines(result)), 1) # Passing the failed flag as a shortcut for FINISHED + non-zero exit status for flag in ['-X', '--failed']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) + self.assertEqual(len(get_result_lines(result)), 1) # Projecting on pk should allow us to verify all the pks for flag in ['-P', '--project']: result = self.cli_runner.invoke(cmd_work.work_list, ['-r', flag, 'pk']) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 6) + self.assertEqual(len(get_result_lines(result)), 6) for line in get_result_lines(result): self.assertIn(line.strip(), [str(calc.pk) for calc in calcs]) @@ -134,29 +134,29 @@ def test_report(self): result = self.cli_runner.invoke(cmd_work.work_report, [str(grandparent.pk)]) self.assertClickResultNoException(result) - self.assertEquals(len(get_result_lines(result)), 3) + self.assertEqual(len(get_result_lines(result)), 3) result = self.cli_runner.invoke(cmd_work.work_report, [str(parent.pk)]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 2) + self.assertEqual(len(get_result_lines(result)), 2) result = self.cli_runner.invoke(cmd_work.work_report, [str(child.pk)]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) + self.assertEqual(len(get_result_lines(result)), 1) # Max depth should limit nesting level for flag in ['-m', '--max-depth']: for flag_value in [1, 2]: result = self.cli_runner.invoke(cmd_work.work_report, [str(grandparent.pk), flag, str(flag_value)]) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), flag_value) + self.assertEqual(len(get_result_lines(result)), flag_value) # Filtering for other level name such as WARNING should not have any hits and only print the no log message for flag in ['-l', '--levelname']: result = self.cli_runner.invoke(cmd_work.work_report, [str(grandparent.pk), flag, 'WARNING']) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 1) - self.assertEquals(get_result_lines(result)[0], 'No log messages recorded for this entry') + self.assertEqual(len(get_result_lines(result)), 1) + self.assertEqual(get_result_lines(result)[0], 'No log messages recorded for this entry') def test_plugins(self): """Test the plugins command. As of writing there are no default plugins defined for aiida-core.""" @@ -173,7 +173,7 @@ def test_work_show(self): options = [] result = self.cli_runner.invoke(cmd_work.work_show, options) self.assertIsNone(result.exception, result.output) - self.assertEquals(len(get_result_lines(result)), 0) + self.assertEqual(len(get_result_lines(result)), 0) # Giving a single identifier should print a non empty string message options = [str(workchain_one.pk)] diff --git a/aiida/backends/tests/cmdline/params/types/test_calculation.py b/aiida/backends/tests/cmdline/params/types/test_calculation.py index fed2e5cf75..83ea76b44a 100644 --- a/aiida/backends/tests/cmdline/params/types/test_calculation.py +++ b/aiida/backends/tests/cmdline/params/types/test_calculation.py @@ -7,30 +7,29 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `CalculationParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import -import click -from click.testing import CliRunner from aiida.backends.testbase import AiidaTestCase -from aiida.cmdline.params import options from aiida.cmdline.params.types import CalculationParamType from aiida.orm.node.process import CalculationNode, CalcFunctionNode, CalcJobNode, WorkChainNode, WorkFunctionNode from aiida.orm.utils.loaders import OrmEntityLoader class TestCalculationParamType(AiidaTestCase): + """Tests for the `CalculationParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some code to test the CalculationParamType parameter type for the command line infrastructure We create an initial code with a random name and then on purpose create two code with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestCalculationParamType, cls).setUpClass() + super(TestCalculationParamType, cls).setUpClass(*args, **kwargs) cls.param = CalculationParamType() cls.entity_01 = CalculationNode().store() @@ -51,7 +50,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -59,7 +58,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -67,7 +66,7 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -78,11 +77,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -93,8 +92,8 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) diff --git a/aiida/backends/tests/cmdline/params/types/test_code.py b/aiida/backends/tests/cmdline/params/types/test_code.py index 3eb6918727..33d0201538 100644 --- a/aiida/backends/tests/cmdline/params/types/test_code.py +++ b/aiida/backends/tests/cmdline/params/types/test_code.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `CodeParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -20,24 +21,25 @@ class TestCodeParamType(AiidaTestCase): + """Tests for the `CodeParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some code to test the CodeParamType parameter type for the command line infrastructure We create an initial code with a random name and then on purpose create two code with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestCodeParamType, cls).setUpClass() + super(TestCodeParamType, cls).setUpClass(*args, **kwargs) cls.param_base = CodeParamType() cls.param_entry_point = CodeParamType(entry_point='arithmetic.add') cls.entity_01 = Code(remote_computer_exec=(cls.computer, '/bin/true')).store() - cls.entity_02 = Code(remote_computer_exec=(cls.computer, '/bin/true'), - input_plugin_name='arithmetic.add').store() - cls.entity_03 = Code(remote_computer_exec=(cls.computer, '/bin/true'), - input_plugin_name='templatereplacer').store() + cls.entity_02 = Code( + remote_computer_exec=(cls.computer, '/bin/true'), input_plugin_name='arithmetic.add').store() + cls.entity_03 = Code( + remote_computer_exec=(cls.computer, '/bin/true'), input_plugin_name='templatereplacer').store() cls.entity_01.label = 'computer_01' cls.entity_02.label = str(cls.entity_01.pk) @@ -49,7 +51,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -57,7 +59,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -65,15 +67,15 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.label) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_fullname(self): """ Verify that using the LABEL@machinename will retrieve the correct entity """ - identifier = '{}@{}'.format(self.entity_01.label, self.computer.name) + identifier = '{}@{}'.format(self.entity_01.label, self.computer.name) # pylint: disable=no-member result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -84,11 +86,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.label) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -99,17 +101,17 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.label) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param_base.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) def test_entry_point_validation(self): """Verify that when an `entry_point` is defined in the constructor, it is respected in the validation.""" identifier = '{}'.format(self.entity_02.pk) result = self.param_entry_point.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) with self.assertRaises(click.BadParameter): identifier = '{}'.format(self.entity_03.pk) diff --git a/aiida/backends/tests/cmdline/params/types/test_computer.py b/aiida/backends/tests/cmdline/params/types/test_computer.py index 6de2304726..02021a866f 100644 --- a/aiida/backends/tests/cmdline/params/types/test_computer.py +++ b/aiida/backends/tests/cmdline/params/types/test_computer.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `ComputerParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -18,16 +19,17 @@ class TestComputerParamType(AiidaTestCase): + """Tests for the `ComputerParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some computers to test the ComputerParamType parameter type for the command line infrastructure We create an initial computer with a random name and then on purpose create two computers with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestComputerParamType, cls).setUpClass() + super(TestComputerParamType, cls).setUpClass(*args, **kwargs) kwargs = { 'hostname': 'localhost', @@ -47,7 +49,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -55,7 +57,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -63,7 +65,7 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.name) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -74,11 +76,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.name) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.name, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -89,8 +91,8 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.name) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.name, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) diff --git a/aiida/backends/tests/cmdline/params/types/test_data.py b/aiida/backends/tests/cmdline/params/types/test_data.py index d780e0c7a3..5fe598b270 100644 --- a/aiida/backends/tests/cmdline/params/types/test_data.py +++ b/aiida/backends/tests/cmdline/params/types/test_data.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `DataParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -17,16 +18,17 @@ class TestDataParamType(AiidaTestCase): + """Tests for the `DataParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some code to test the DataParamType parameter type for the command line infrastructure We create an initial code with a random name and then on purpose create two code with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestDataParamType, cls).setUpClass() + super(TestDataParamType, cls).setUpClass(*args, **kwargs) cls.param = DataParamType() cls.entity_01 = Data().store() @@ -43,7 +45,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -51,7 +53,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -59,7 +61,7 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -70,11 +72,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -85,8 +87,8 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) diff --git a/aiida/backends/tests/cmdline/params/types/test_group.py b/aiida/backends/tests/cmdline/params/types/test_group.py index bea65d7a07..5f4f806a96 100644 --- a/aiida/backends/tests/cmdline/params/types/test_group.py +++ b/aiida/backends/tests/cmdline/params/types/test_group.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `GroupParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -17,16 +18,17 @@ class TestGroupParamType(AiidaTestCase): + """Tests for the `GroupParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some groups to test the GroupParamType parameter type for the command line infrastructure We create an initial group with a random name and then on purpose create two groups with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestGroupParamType, cls).setUpClass() + super(TestGroupParamType, cls).setUpClass(*args, **kwargs) cls.param = GroupParamType() cls.entity_01 = Group(label='group_01').store() @@ -39,7 +41,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -47,7 +49,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -55,7 +57,7 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -66,11 +68,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -81,8 +83,8 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) diff --git a/aiida/backends/tests/cmdline/params/types/test_identifier.py b/aiida/backends/tests/cmdline/params/types/test_identifier.py index fe5b551347..02a972c7ef 100644 --- a/aiida/backends/tests/cmdline/params/types/test_identifier.py +++ b/aiida/backends/tests/cmdline/params/types/test_identifier.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `IdentifierParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -18,33 +19,34 @@ class TestIdentifierParamType(AiidaTestCase): + """Tests for the `IdentifierParamType`.""" def test_base_class(self): """ The base class is abstract and should not be constructable """ with self.assertRaises(TypeError): - param_type = IdentifierParamType() + IdentifierParamType() # pylint: disable=abstract-class-instantiated - def test_identifier_sub_classes_invalid_type(self): + def test_identifier_sub_invalid_type(self): """ The sub_classes keyword argument should expect a tuple """ with self.assertRaises(TypeError): - param_type = NodeParamType(sub_classes='aiida.data:structure') + NodeParamType(sub_classes='aiida.data:structure') with self.assertRaises(TypeError): - param_type = NodeParamType(sub_classes=('aiida.data:structure', )) + NodeParamType(sub_classes=(None,)) - def test_identifier_sub_classes_invalid_type(self): + def test_identifier_sub_invalid_entry_point(self): """ The sub_classes keyword argument should expect a tuple of valid entry point strings """ with self.assertRaises(ValueError): - param_type = NodeParamType(sub_classes=('aiida.data.structure', )) + NodeParamType(sub_classes=('aiida.data.structure',)) with self.assertRaises(ValueError): - param_type = NodeParamType(sub_classes=('aiida.data:not_existent', )) + NodeParamType(sub_classes=('aiida.data:not_existent',)) def test_identifier_sub_classes(self): """ @@ -58,14 +60,14 @@ def test_identifier_sub_classes(self): param_type_scoped = NodeParamType(sub_classes=('aiida.data:bool', 'aiida.data:float')) # For the base NodeParamType all node types should be matched - self.assertEquals(param_type_normal.convert(str(node_bool.pk), None, None).uuid, node_bool.uuid) - self.assertEquals(param_type_normal.convert(str(node_float.pk), None, None).uuid, node_float.uuid) - self.assertEquals(param_type_normal.convert(str(node_int.pk), None, None).uuid, node_int.uuid) + self.assertEqual(param_type_normal.convert(str(node_bool.pk), None, None).uuid, node_bool.uuid) + self.assertEqual(param_type_normal.convert(str(node_float.pk), None, None).uuid, node_float.uuid) + self.assertEqual(param_type_normal.convert(str(node_int.pk), None, None).uuid, node_int.uuid) # The scoped NodeParamType should only match Bool and Float - self.assertEquals(param_type_scoped.convert(str(node_bool.pk), None, None).uuid, node_bool.uuid) - self.assertEquals(param_type_scoped.convert(str(node_float.pk), None, None).uuid, node_float.uuid) + self.assertEqual(param_type_scoped.convert(str(node_bool.pk), None, None).uuid, node_bool.uuid) + self.assertEqual(param_type_scoped.convert(str(node_float.pk), None, None).uuid, node_float.uuid) # The Int should not be found and raise with self.assertRaises(click.BadParameter): - param_type_scoped.convert(str(node_int.pk), None, None).uuid, node_int.uuid + param_type_scoped.convert(str(node_int.pk), None, None) diff --git a/aiida/backends/tests/cmdline/params/types/test_node.py b/aiida/backends/tests/cmdline/params/types/test_node.py index fbc504eb69..d12f8ecd11 100644 --- a/aiida/backends/tests/cmdline/params/types/test_node.py +++ b/aiida/backends/tests/cmdline/params/types/test_node.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `NodeParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -17,16 +18,17 @@ class TestNodeParamType(AiidaTestCase): + """Tests for the `NodeParamType`.""" @classmethod - def setUpClass(cls): + def setUpClass(cls, *args, **kwargs): """ Create some code to test the NodeParamType parameter type for the command line infrastructure We create an initial code with a random name and then on purpose create two code with a name that matches exactly the ID and UUID, respectively, of the first one. This allows us to test the rules implemented to solve ambiguities that arise when determing the identifier type """ - super(TestNodeParamType, cls).setUpClass() + super(TestNodeParamType, cls).setUpClass(*args, **kwargs) cls.param = NodeParamType() cls.entity_01 = Data().store() @@ -43,7 +45,7 @@ def test_get_by_id(self): """ identifier = '{}'.format(self.entity_01.pk) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_uuid(self): """ @@ -51,7 +53,7 @@ def test_get_by_uuid(self): """ identifier = '{}'.format(self.entity_01.uuid) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_get_by_label(self): """ @@ -59,7 +61,7 @@ def test_get_by_label(self): """ identifier = '{}'.format(self.entity_01.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) def test_ambiguous_label_pk(self): """ @@ -70,11 +72,11 @@ def test_ambiguous_label_pk(self): """ identifier = '{}'.format(self.entity_02.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_02.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_02.uuid) + self.assertEqual(result.uuid, self.entity_02.uuid) def test_ambiguous_label_uuid(self): """ @@ -85,8 +87,8 @@ def test_ambiguous_label_uuid(self): """ identifier = '{}'.format(self.entity_03.label) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_01.uuid) + self.assertEqual(result.uuid, self.entity_01.uuid) identifier = '{}{}'.format(self.entity_03.label, OrmEntityLoader.LABEL_AMBIGUITY_BREAKER_CHARACTER) result = self.param.convert(identifier, None, None) - self.assertEquals(result.uuid, self.entity_03.uuid) + self.assertEqual(result.uuid, self.entity_03.uuid) diff --git a/aiida/backends/tests/cmdline/params/types/test_plugin.py b/aiida/backends/tests/cmdline/params/types/test_plugin.py index 1a36491a79..0271fae546 100644 --- a/aiida/backends/tests/cmdline/params/types/test_plugin.py +++ b/aiida/backends/tests/cmdline/params/types/test_plugin.py @@ -7,12 +7,12 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### -"""Unit tests for plugin parameter type.""" +"""Tests for the `PluginParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import + import click -import mock from aiida.backends.testbase import AiidaTestCase from aiida.cmdline.params.types.plugin import PluginParamType @@ -20,7 +20,7 @@ class TestPluginParamType(AiidaTestCase): - """Unit tests for plugin parameter type.""" + """Tests for the `PluginParamType`.""" def test_group_definition(self): """ @@ -92,11 +92,11 @@ def test_get_entry_point_from_string(self): param.get_entry_point_from_string('not_existent') # Valid entry point strings - self.assertEquals(param.get_entry_point_from_string('aiida.transports:ssh').name, entry_point.name) - self.assertEquals(param.get_entry_point_from_string('transports:ssh').name, entry_point.name) - self.assertEquals(param.get_entry_point_from_string('ssh').name, entry_point.name) + self.assertEqual(param.get_entry_point_from_string('aiida.transports:ssh').name, entry_point.name) + self.assertEqual(param.get_entry_point_from_string('transports:ssh').name, entry_point.name) + self.assertEqual(param.get_entry_point_from_string('ssh').name, entry_point.name) - def test_get_entry_point_from_string_ambiguous(self): + def test_get_entry_point_from_ambiguous(self): """ Test the functionality of the get_entry_point_from_string which will take an entry point string and try to map it onto a valid entry point that is part of the groups defined for the parameter. @@ -109,8 +109,8 @@ def test_get_entry_point_from_string_ambiguous(self): param.get_entry_point_from_string('tcod') # Passing PARTIAL or FULL should allow entry point to be returned - self.assertEquals(param.get_entry_point_from_string('aiida.tools.dbexporters:tcod').name, entry_point.name) - self.assertEquals(param.get_entry_point_from_string('tools.dbexporters:tcod').name, entry_point.name) + self.assertEqual(param.get_entry_point_from_string('aiida.tools.dbexporters:tcod').name, entry_point.name) + self.assertEqual(param.get_entry_point_from_string('tools.dbexporters:tcod').name, entry_point.name) def test_convert(self): """ @@ -119,27 +119,27 @@ def test_convert(self): param = PluginParamType(group=('transports', 'data')) entry_point = param.convert('aiida.transports:ssh', None, None) - self.assertEquals(entry_point.name, 'ssh') + self.assertEqual(entry_point.name, 'ssh') # self.assertTrue(isinstance(entry_point, EntryPoint)) entry_point = param.convert('transports:ssh', None, None) - self.assertEquals(entry_point.name, 'ssh') + self.assertEqual(entry_point.name, 'ssh') # self.assertTrue(isinstance(entry_point, EntryPoint)) entry_point = param.convert('ssh', None, None) - self.assertEquals(entry_point.name, 'ssh') + self.assertEqual(entry_point.name, 'ssh') # self.assertTrue(isinstance(entry_point, EntryPoint)) entry_point = param.convert('aiida.data:structure', None, None) - self.assertEquals(entry_point.name, 'structure') + self.assertEqual(entry_point.name, 'structure') # self.assertTrue(isinstance(entry_point, EntryPoint)) entry_point = param.convert('data:structure', None, None) - self.assertEquals(entry_point.name, 'structure') + self.assertEqual(entry_point.name, 'structure') # self.assertTrue(isinstance(entry_point, EntryPoint)) entry_point = param.convert('structure', None, None) - self.assertEquals(entry_point.name, 'structure') + self.assertEqual(entry_point.name, 'structure') # self.assertTrue(isinstance(entry_point, EntryPoint)) with self.assertRaises(click.BadParameter): diff --git a/aiida/backends/tests/cmdline/params/types/test_workflow.py b/aiida/backends/tests/cmdline/params/types/test_workflow.py index d740f1406c..b3ee6391ff 100644 --- a/aiida/backends/tests/cmdline/params/types/test_workflow.py +++ b/aiida/backends/tests/cmdline/params/types/test_workflow.py @@ -7,6 +7,7 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +"""Tests for the `LegacyWorkflowParamType`.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import @@ -15,12 +16,13 @@ class TestLegacyWorkflowParamType(AiidaTestCase): + """Tests for the `LegacyWorkflowParamType`.""" @classmethod - def setUpClass(cls): - from aiida.workflows.test import WFTestEmpty, WFTestSimpleWithSubWF + def setUpClass(cls, *args, **kwargs): + from aiida.workflows.test import WFTestEmpty - super(TestLegacyWorkflowParamType, cls).setUpClass() + super(TestLegacyWorkflowParamType, cls).setUpClass(*args, **kwargs) cls.workflow = WFTestEmpty() cls.workflow.label = 'Unique Label' diff --git a/aiida/backends/tests/manage/configuration/test_config.py b/aiida/backends/tests/manage/configuration/test_config.py new file mode 100644 index 0000000000..1ed9cf324e --- /dev/null +++ b/aiida/backends/tests/manage/configuration/test_config.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Tests for the Config class.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +import os +import shutil +import tempfile + +from aiida.backends.testbase import AiidaTestCase +from aiida.backends.tests.utils.configuration import create_mock_profile +from aiida.common import exceptions +from aiida.manage import Config, Profile +from aiida.manage.configuration.migrations import CURRENT_CONFIG_VERSION, OLDEST_COMPATIBLE_CONFIG_VERSION +from aiida.manage.configuration.options import get_option +from aiida.utils import json + + +class TestConfig(AiidaTestCase): + """Tests for the Config class.""" + + def setUp(self): + """Setup a mock config.""" + super(TestConfig, self).setUp() + self.profile_name = 'test_profile' + self.profile = create_mock_profile(self.profile_name) + self.config_filebase = tempfile.mkdtemp() + self.config_filename = 'config.json' + self.config_filepath = os.path.join(self.config_filebase, self.config_filename) + self.config_dictionary = { + Config.KEY_VERSION: { + Config.KEY_VERSION_CURRENT: CURRENT_CONFIG_VERSION, + Config.KEY_VERSION_OLDEST_COMPATIBLE: OLDEST_COMPATIBLE_CONFIG_VERSION, + }, + Config.KEY_PROFILES: { + self.profile_name: self.profile + }, + } + + def tearDown(self): + """Clean the temporary folder.""" + super(TestConfig, self).tearDown() + if self.config_filebase and os.path.isdir(self.config_filebase): + shutil.rmtree(self.config_filebase) + + def test_basic_properties(self): + """Test the basic properties of the Config class.""" + config = Config(self.config_filepath, self.config_dictionary) + + self.assertEqual(config.filepath, self.config_filepath) + self.assertEqual(config.dirpath, self.config_filebase) + self.assertEqual(config.version, CURRENT_CONFIG_VERSION) + self.assertEqual(config.version_oldest_compatible, OLDEST_COMPATIBLE_CONFIG_VERSION) + self.assertEqual(config.dictionary, self.config_dictionary) + + def test_setting_versions(self): + """Test the version setters.""" + config = Config(self.config_filepath, self.config_dictionary) + + self.assertEqual(config.version, CURRENT_CONFIG_VERSION) + self.assertEqual(config.version_oldest_compatible, OLDEST_COMPATIBLE_CONFIG_VERSION) + + new_config_version = 1000 + new_compatible_version = 999 + + config.version = new_config_version + config.version_oldest_compatible = new_compatible_version + + self.assertEqual(config.version, new_config_version) + self.assertEqual(config.version_oldest_compatible, new_compatible_version) + + def test_construct_empty_dictionary(self): + """Constructing with empty dictionary should create basic skeleton.""" + config = Config(self.config_filepath, {}) + + self.assertTrue(Config.KEY_PROFILES in config.dictionary) + self.assertTrue(Config.KEY_VERSION in config.dictionary) + self.assertTrue(Config.KEY_VERSION_CURRENT in config.dictionary[Config.KEY_VERSION]) + self.assertTrue(Config.KEY_VERSION_OLDEST_COMPATIBLE in config.dictionary[Config.KEY_VERSION]) + + def test_default_profile(self): + """Test setting and getting default profile.""" + config = Config(self.config_filepath, self.config_dictionary) + + # If not set should return None + self.assertEqual(config.default_profile_name, None) + + # Setting it to a profile that does not exist should raise + with self.assertRaises(exceptions.ProfileConfigurationError): + config.set_default_profile('non_existing_profile') + + # After setting a default profile, it should return the right name + config.add_profile(self.profile_name, create_mock_profile(self.profile_name)) + config.set_default_profile(self.profile_name) + self.assertTrue(config.default_profile_name, self.profile_name) + + # Setting it when a default is already set, should not overwrite by default + alternative_profile_name = 'alterantive_profile_name' + config.add_profile(alternative_profile_name, create_mock_profile(alternative_profile_name)) + config.set_default_profile(self.profile_name) + self.assertTrue(config.default_profile_name, self.profile_name) + + # But with overwrite=True it should + config.set_default_profile(self.profile_name, overwrite=True) + self.assertTrue(config.default_profile_name, alternative_profile_name) + + def test_profiles(self): + """Test the properties related to retrieving, creating, updating and removing profiles.""" + config = Config(self.config_filepath, self.config_dictionary) + + # Each item returned by config.profiles should be a Profile instance + for profile in config.profiles: + self.assertIsInstance(profile, Profile) + self.assertTrue(profile.dictionary, self.config_dictionary[Config.KEY_PROFILES][profile.name]) + + # The profile_names property should return the keys of the profiles dictionary + self.assertEqual(config.profile_names, list(self.config_dictionary[Config.KEY_PROFILES])) + + # Test get_profile + profile = Profile(self.profile_name, self.profile) + self.assertEqual(config.get_profile(self.profile_name).dictionary, profile.dictionary) + + # Update a profile + updated_profile = Profile(self.profile_name, create_mock_profile(self.profile_name)) + config.update_profile(updated_profile) + self.assertEqual(config.get_profile(self.profile_name).dictionary, updated_profile.dictionary) + + # Removing an unexisting profile should raise + with self.assertRaises(exceptions.ProfileConfigurationError): + config.remove_profile('non_existing_profile') + + # Removing an existing should work and in this test case none should remain + config.remove_profile(self.profile_name) + self.assertEqual(config.profiles, []) + self.assertEqual(config.profile_names, []) + + def test_option(self): + """Test the setter, unsetter and getter of configuration options.""" + option_value = 131 + option_name = 'daemon.timeout' + option = get_option(option_name) + config = Config(self.config_filepath, self.config_dictionary) + + # Getting option that does not exist, should simply return the option default + self.assertEqual(config.option_get(option_name, scope=self.profile_name), option.default) + self.assertEqual(config.option_get(option_name, scope=None), option.default) + + # Unless we set default=False, in which case it should return None + self.assertEqual(config.option_get(option_name, scope=self.profile_name, default=False), None) + self.assertEqual(config.option_get(option_name, scope=None, default=False), None) + + # Setting an option profile configuration wide + config.option_set(option_name, option_value) + + # Getting configuration wide should get new value but None for profile specific + self.assertEqual(config.option_get(option_name, scope=None, default=False), option_value) + self.assertEqual(config.option_get(option_name, scope=self.profile_name, default=False), None) + + # Setting an option profile specific + config.option_set(option_name, option_value, scope=self.profile_name) + self.assertEqual(config.option_get(option_name, scope=self.profile_name), option_value) + + # Unsetting profile specific + config.option_unset(option_name, scope=self.profile_name) + self.assertEqual(config.option_get(option_name, scope=self.profile_name, default=False), None) + + # Unsetting configuration wide + config.option_unset(option_name, scope=None) + self.assertEqual(config.option_get(option_name, scope=None, default=False), None) + self.assertEqual(config.option_get(option_name, scope=None, default=True), option.default) + + def test_store(self): + """Test that the store method writes the configuration properly to disk.""" + config = Config(self.config_filepath, self.config_dictionary) + config.store() + + with open(config.filepath, 'r') as handle: + config_recreated = Config(config.filepath, json.load(handle)) + + self.assertEqual(config.dictionary, config_recreated.dictionary) diff --git a/aiida/backends/tests/manage/configuration/test_options.py b/aiida/backends/tests/manage/configuration/test_options.py new file mode 100644 index 0000000000..7035b23992 --- /dev/null +++ b/aiida/backends/tests/manage/configuration/test_options.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Tests for the configuration options.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +from aiida.backends.testbase import AiidaTestCase +from aiida.manage.configuration.options import get_option, get_option_names, parse_option, Option, CONFIG_OPTIONS + + +class TestConfigurationOptions(AiidaTestCase): + """Tests for the Options class.""" + + def test_get_option_names(self): + """Test `get_option_names` function.""" + self.assertEqual(get_option_names(), CONFIG_OPTIONS.keys()) + + def test_get_option(self): + """Test `get_option` function.""" + with self.assertRaises(ValueError): + get_option('no_existing_option') + + option_name = list(CONFIG_OPTIONS)[0] + option = get_option(option_name) + self.assertIsInstance(option, Option) + self.assertEqual(option.name, option_name) + + def test_parse_option(self): + """Test `parse_option` function.""" + + with self.assertRaises(ValueError): + parse_option('logging.aiida_loglevel', 1) + + with self.assertRaises(ValueError): + parse_option('logging.aiida_loglevel', 'INVALID_LOG_LEVEL') + + def test_options(self): + """Test that all defined options can be converted into Option namedtuples.""" + for option_name, option_settings in CONFIG_OPTIONS.items(): + option = get_option(option_name) + self.assertEqual(option.name, option_name) + self.assertEqual(option.key, option_settings['key']) + self.assertEqual(option.valid_type, option_settings['valid_type']) + self.assertEqual(option.valid_values, option_settings['valid_values']) + self.assertEqual(option.default, option_settings['default']) + self.assertEqual(option.description, option_settings['description']) diff --git a/aiida/backends/tests/manage/configuration/test_profile.py b/aiida/backends/tests/manage/configuration/test_profile.py new file mode 100644 index 0000000000..1b1546b9b2 --- /dev/null +++ b/aiida/backends/tests/manage/configuration/test_profile.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Tests for the Profile class.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +import uuid + +from aiida.backends.testbase import AiidaTestCase +from aiida.backends.tests.utils.configuration import create_mock_profile +from aiida.manage import Profile + + +class TestProfile(AiidaTestCase): + """Tests for the Profile class.""" + + @classmethod + def setUpClass(cls, *args, **kwargs): + """Setup a mock profile.""" + super(TestProfile, cls).setUpClass(*args, **kwargs) + cls.profile_name = 'test_profile' + cls.profile_dictionary = create_mock_profile(name=cls.profile_name) + cls.profile = Profile(cls.profile_name, cls.profile_dictionary) + + def test_base_properties(self): + """Test the basic properties of a Profile instance.""" + self.assertEqual(self.profile.name, self.profile_name) + self.assertEqual(self.profile.dictionary, self.profile_dictionary) + + # Verify that the uuid property returns a valid UUID by attempting to construct an UUID instance from it + uuid.UUID(self.profile.uuid) + + # Check that the default user email field is not None + self.assertIsNotNone(self.profile.default_user_email) + + # The RabbitMQ prefix should contain the profile UUID + self.assertIn(self.profile.uuid, self.profile.rmq_prefix) + + def test_is_test_profile(self): + """Test that a profile whose name starts with `test_` is marked as a test profile.""" + profile_name = 'not_a_test_profile' + profile_dictionary = create_mock_profile(name=profile_name) + profile = Profile(profile_name, profile_dictionary) + + # The one constructed in the setUpClass should be a test profile + self.assertTrue(self.profile.is_test_profile) + + # The profile created here should *not* be a test profile + self.assertFalse(profile.is_test_profile) diff --git a/aiida/backends/tests/utils/configuration.py b/aiida/backends/tests/utils/configuration.py new file mode 100644 index 0000000000..aeb1f38a45 --- /dev/null +++ b/aiida/backends/tests/utils/configuration.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Module that defines methods to mock an AiiDA instance complete with mock configuration and profile.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +import contextlib +import os +import shutil +import tempfile + + +def create_mock_profile(name, repository_dirpath=None): + """Create mock profile for testing purposes. + + :param name: name of the profile + :param repository_dirpath: optional absolute path to use as the base for the repository path + """ + from aiida.manage import get_config, Profile + + if repository_dirpath is None: + config = get_config() + repository_dirpath = config.dirpath + + dictionary = {} + dictionary['db_name'] = name + dictionary['db_host'] = 'localhost' + dictionary['db_port'] = '5432' + dictionary['db_user'] = 'user' + dictionary['db_pass'] = 'pass' + dictionary['email'] = 'dummy@localhost' + dictionary['backend'] = 'django' + dictionary['repo'] = os.path.join(repository_dirpath, 'repository_' + name) + dictionary[Profile.KEY_PROFILE_UUID] = Profile.generate_uuid() + dictionary[Profile.KEY_DEFAULT_USER] = 'dummy@localhost' + + return dictionary + + +@contextlib.contextmanager +def temporary_config_instance(): + """Create a temporary AiiDA instance.""" + current_config = None + current_config_path = None + current_profile_name = None + temporary_config_directory = None + + try: + from aiida.backends import settings as backend_settings + from aiida.common.setup import create_profile_noninteractive + from aiida.manage import configuration + from aiida.manage.configuration.utils import load_config + from aiida.manage.configuration import settings + from aiida.manage.configuration.setup import create_instance_directories + + # Store the current configuration instance and config directory path + current_config = configuration.CONFIG + current_config_path = current_config.dirpath + current_profile_name = backend_settings.AIIDADB_PROFILE + + # Create a temporary folder, set it as the current config directory path and reset the loaded configuration + profile_name = 'test_profile_1234' + temporary_config_directory = tempfile.mkdtemp() + configuration.CONFIG = None + settings.AIIDA_CONFIG_FOLDER = temporary_config_directory + backend_settings.AIIDADB_PROFILE = profile_name + + # Create the instance base directory structure, the config file and a dummy profile + create_instance_directories() + configuration.CONFIG = load_config(create=True) + profile_content = create_mock_profile(name=profile_name, repository_dirpath=temporary_config_directory) + profile = create_profile_noninteractive(configuration.CONFIG, profile_name=profile_name, **profile_content) + + # Add the created profile and set it as the default + configuration.CONFIG.add_profile(profile_name, profile) + configuration.CONFIG.set_default_profile(profile_name, overwrite=True) + configuration.CONFIG.store() + + yield configuration.CONFIG + finally: + # Reset the config folder path and the config instance + configuration.CONFIG = current_config + settings.AIIDA_CONFIG_FOLDER = current_config_path + backend_settings.AIIDADB_PROFILE = current_profile_name + + # Destroy the temporary instance directory + if temporary_config_directory and os.path.isdir(temporary_config_directory): + shutil.rmtree(temporary_config_directory) + + +def with_temporary_config_instance(function): + """Create a temporary AiiDA instance for the duration of the wrapped function.""" + + def decorated_function(*args, **kwargs): + with temporary_config_instance(): + function(*args, **kwargs) + + return decorated_function diff --git a/aiida/cmdline/commands/__init__.py b/aiida/cmdline/commands/__init__.py index f6a5d8156e..d961c02930 100644 --- a/aiida/cmdline/commands/__init__.py +++ b/aiida/cmdline/commands/__init__.py @@ -18,10 +18,11 @@ click_completion.init() # Import to populate the `verdi` sub commands -from aiida.cmdline.commands import ( - cmd_calcjob, cmd_calculation, cmd_code, cmd_comment, cmd_completioncommand, cmd_computer, cmd_data, cmd_database, - cmd_daemon, cmd_devel, cmd_export, cmd_graph, cmd_group, cmd_import, cmd_node, cmd_plugin, cmd_process, cmd_profile, - cmd_quicksetup, cmd_rehash, cmd_restapi, cmd_run, cmd_setup, cmd_shell, cmd_user, cmd_work, cmd_workflow) +from aiida.cmdline.commands import (cmd_calcjob, cmd_calculation, cmd_code, cmd_comment, cmd_completioncommand, + cmd_computer, cmd_config, cmd_data, cmd_database, cmd_daemon, cmd_devel, cmd_export, + cmd_graph, cmd_group, cmd_import, cmd_node, cmd_plugin, cmd_process, cmd_profile, + cmd_quicksetup, cmd_rehash, cmd_restapi, cmd_run, cmd_setup, cmd_shell, cmd_user, + cmd_work, cmd_workflow) # Import to populate the `verdi data` sub commands from aiida.cmdline.commands.cmd_data import (cmd_array, cmd_bands, cmd_cif, cmd_parameter, cmd_remote, cmd_structure, diff --git a/aiida/cmdline/commands/cmd_calculation.py b/aiida/cmdline/commands/cmd_calculation.py index 97da30a53a..bd9d2a0326 100644 --- a/aiida/cmdline/commands/cmd_calculation.py +++ b/aiida/cmdline/commands/cmd_calculation.py @@ -21,9 +21,9 @@ from aiida.cmdline.commands.cmd_process import verdi_process from aiida.cmdline.params import arguments, options from aiida.cmdline.utils import decorators -from aiida.common.setup import get_property +from aiida.manage import get_config_option -LIST_CMDLINE_PROJECT_DEFAULT = get_property('verdishell.calculation_list') +LIST_CMDLINE_PROJECT_DEFAULT = get_config_option('verdishell.calculation_list') LIST_CMDLINE_PROJECT_CHOICES = ('pk', 'ctime', 'process_state', 'job_state', 'scheduler_state', 'computer', 'type', 'description', 'label', 'uuid', 'mtime', 'user', 'sealed') diff --git a/aiida/cmdline/commands/cmd_config.py b/aiida/cmdline/commands/cmd_config.py new file mode 100644 index 0000000000..5535fdbbda --- /dev/null +++ b/aiida/cmdline/commands/cmd_config.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""`verdi config` command.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +import click + +from aiida.cmdline.commands.cmd_verdi import verdi +from aiida.cmdline.params import arguments +from aiida.cmdline.utils import echo + + +@verdi.command('config') +@arguments.CONFIG_OPTION(metavar='OPTION_NAME') +@click.argument('value', metavar='OPTION_VALUE', required=False) +@click.option('--global', 'globally', is_flag=True, help='Apply the option configuration wide.') +@click.option('--unset', is_flag=True, help='Remove the line matching the option name from the config file.') +@click.pass_context +def verdi_config(ctx, option, value, globally, unset): + """Set, unset and get profile specific or global configuration options.""" + config = ctx.obj.config + profile = ctx.obj.profile + + # Define the string that determines the scope: for specific profile or globally + scope = profile.name if not globally else None + scope_text = 'for {}'.format(profile.name) if not globally else 'globally' + + # Unset the specified option + if unset: + config.option_unset(option.name, scope=scope) + config.store() + echo.echo_success('{} unset {}'.format(option.name, scope_text)) + + # Get the specified option + elif value is None: + option_value = config.option_get(option.name, scope=scope, default=False) + if option_value: + echo.echo('{}'.format(option_value)) + + # Set the specified option + else: + config.option_set(option.name, value, scope=scope) + config.store() + echo.echo_success('{} set to {} {}'.format(option.name, value, scope_text)) diff --git a/aiida/cmdline/commands/cmd_daemon.py b/aiida/cmdline/commands/cmd_daemon.py index d2b5f44f03..1bb504c5e6 100644 --- a/aiida/cmdline/commands/cmd_daemon.py +++ b/aiida/cmdline/commands/cmd_daemon.py @@ -23,7 +23,7 @@ from aiida.cmdline.utils.common import get_env_with_venv_bin from aiida.cmdline.utils.daemon import get_daemon_status, print_client_response_status from aiida.daemon.client import get_daemon_client -from aiida.manage import load_config +from aiida.manage import get_config @verdi.group('daemon') @@ -69,7 +69,7 @@ def status(all_profiles): """ Print the status of the current daemon or all daemons """ - config = load_config() + config = get_config() if all_profiles is True: profiles = [profile for profile in config.profiles if not profile.is_test_profile] @@ -130,7 +130,7 @@ def stop(no_wait, all_profiles): """ Stop the daemon """ - config = load_config() + config = get_config() if all_profiles is True: profiles = [profile for profile in config.profiles if not profile.is_test_profile] diff --git a/aiida/cmdline/commands/cmd_devel.py b/aiida/cmdline/commands/cmd_devel.py index 6dbd10a873..795bb01435 100644 --- a/aiida/cmdline/commands/cmd_devel.py +++ b/aiida/cmdline/commands/cmd_devel.py @@ -65,100 +65,6 @@ def get_valid_test_paths(): return valid_test_paths -@verdi_devel.command('describeproperties') -def devel_describeproperties(): - """ - List all valid properties that can be stored in the AiiDA config file. - - Only properties listed in the ``_property_table`` of ``aida.common.setup`` can be used. - """ - from aiida.common.setup import _property_table, _NoDefaultValue - - for prop in sorted(_property_table.keys()): - - prop_name = _property_table[prop][1] - prop_type = _property_table[prop][2] - prop_default = _property_table[prop][3] - prop_values = _property_table[prop][4] - - if prop_values is None: - prop_values_str = '' - else: - prop_values_str = ' Valid values: {}.'.format(', '.join(str(_) for _ in prop_values)) - - if isinstance(prop_default, _NoDefaultValue): - prop_default_str = '' - else: - prop_default_str = ' (default: {})'.format(prop_default) - - echo.echo('* {} ({}): {}{}{}'.format(prop, prop_name, prop_type, prop_default_str, prop_values_str)) - - -@verdi_devel.command('listproperties') -@options.ALL(help='Show all properties, even if not explicitly defined in the configuration file') -def devel_listproperties(all_entries): - """List all the properties that are explicitly set for the current profile.""" - from aiida.common.setup import _property_table, exists_property, get_property - - for prop in sorted(_property_table.keys()): - try: - # To enforce the generation of an exception, even if there is a default value - if all_entries or exists_property(prop): - val = get_property(prop) - echo.echo('{} = {}'.format(prop, val)) - except KeyError: - pass - - -@verdi_devel.command('delproperty') -@click.argument('prop', type=click.STRING, metavar='PROPERTY') -def devel_delproperty(prop): - """Delete the global PROPERTY from the configuration file.""" - from aiida.common.setup import del_property - - try: - del_property(prop) - except ValueError: - echo.echo_critical('property {} not found'.format(prop)) - except Exception as exception: # pylint: disable=broad-except - echo.echo_critical('{} while getting the property: {}'.format(type(exception).__name__, exception)) - else: - echo.echo_success('deleted the {} property from the configuration file'.format(prop)) - - -@verdi_devel.command('getproperty') -@click.argument('prop', type=click.STRING, metavar='PROPERTY') -def devel_getproperty(prop): - """Get the global PROPERTY from the configuration file.""" - from aiida.common.setup import get_property - - try: - value = get_property(prop) - except ValueError: - echo.echo_critical('property {} not found'.format(prop)) - except Exception as exception: # pylint: disable=broad-except - echo.echo_critical('{} while getting the property: {}'.format(type(exception).__name__, exception)) - else: - echo.echo('{}'.format(value)) - - -@verdi_devel.command('setproperty') -@click.argument('prop', type=click.STRING, metavar='PROPERTY') -@click.argument('value', type=click.STRING) -def devel_setproperty(prop, value): - """Set the global PROPERTY to VALUE in the configuration file.""" - from aiida.common.setup import set_property - - try: - set_property(prop, value) - except ValueError: - echo.echo_critical('{} is not a recognized property, call describeproperties to see a list'.format(prop)) - except Exception as exception: # pylint: disable=broad-except - echo.echo_critical('{} while storing the property: {}'.format(type(exception).__name__, exception)) - else: - echo.echo_success('property {} set to {}'.format(prop, value)) - - @verdi_devel.command('run_daemon') @decorators.with_dbenv() def devel_run_daemon(): diff --git a/aiida/cmdline/commands/cmd_profile.py b/aiida/cmdline/commands/cmd_profile.py index eeb09af2d0..8dbf1dcb14 100644 --- a/aiida/cmdline/commands/cmd_profile.py +++ b/aiida/cmdline/commands/cmd_profile.py @@ -20,7 +20,7 @@ from aiida.cmdline.params import arguments, options from aiida.common import exceptions from aiida.control.postgres import Postgres -from aiida.manage import load_config +from aiida.manage import get_config @verdi.group('profile') @@ -33,7 +33,7 @@ def verdi_profile(): def profile_list(): """Displays list of all available profiles.""" try: - config = load_config() + config = get_config() except (exceptions.MissingConfigurationError, exceptions.ConfigurationError) as exception: echo.echo_critical(str(exception)) @@ -61,11 +61,11 @@ def profile_show(profile): def profile_setdefault(profile): """Set PROFILE as the default profile.""" try: - config = load_config() + config = get_config() except (exceptions.MissingConfigurationError, exceptions.ConfigurationError) as exception: echo.echo_critical(str(exception)) - config.set_default_profile(profile.name, overwrite=True) + config.set_default_profile(profile.name, overwrite=True).store() echo.echo_success('{} set as default profile'.format(profile.name)) @@ -82,7 +82,7 @@ def profile_delete(force, profiles): import aiida.utils.json as json try: - config = load_config() + config = get_config() except (exceptions.MissingConfigurationError, exceptions.ConfigurationError) as exception: echo.echo_critical(str(exception)) @@ -140,4 +140,4 @@ def profile_delete(force, profiles): "Delete configuration for profile '{}'?\n" "WARNING: Permanently removes profile from the list of AiiDA profiles.".format(profile_name)): echo.echo_info("Deleting configuration for profile '{}'.".format(profile_name)) - config.remove_profile(profile_name) + config.remove_profile(profile_name).store() diff --git a/aiida/cmdline/commands/cmd_quicksetup.py b/aiida/cmdline/commands/cmd_quicksetup.py index 5a3f1604ca..80b3c0a9f6 100644 --- a/aiida/cmdline/commands/cmd_quicksetup.py +++ b/aiida/cmdline/commands/cmd_quicksetup.py @@ -12,6 +12,7 @@ from __future__ import division from __future__ import print_function from __future__ import absolute_import + import os import sys import hashlib @@ -56,13 +57,13 @@ def _check_db_name(dbname, postgres): def quicksetup(profile_name, only_config, set_default, non_interactive, backend, db_host, db_port, db_name, db_username, db_password, repository, email, first_name, last_name, institution): """Set up a sane configuration with as little interaction as possible.""" - from aiida.manage import load_config + import getpass + from aiida.common.hashing import get_random_string + from aiida.manage.configuration.utils import load_config from aiida.manage.configuration.setup import create_instance_directories - from aiida.manage.configuration.settings import AIIDA_CONFIG_FOLDER create_instance_directories() - - aiida_dir = os.path.expanduser(AIIDA_CONFIG_FOLDER) + config = load_config(create=True) # access postgres postgres = Postgres(host=db_host, port=db_port, interactive=bool(not non_interactive), quiet=False) @@ -71,24 +72,20 @@ def quicksetup(profile_name, only_config, set_default, non_interactive, backend, if not success: sys.exit(1) - # default database name is _ - # this ensures that for profiles named test_... the database will also - # be named test_... - import getpass osuser = getpass.getuser() - aiida_base_dir_hash = hashlib.md5(AIIDA_CONFIG_FOLDER.encode('utf-8')).hexdigest() - dbname = db_name or profile_name + '_' + osuser + '_' + aiida_base_dir_hash + config_dir_hash = hashlib.md5(config.dirpath.encode('utf-8')).hexdigest() # default database user name is aiida_qs_ # default password is random - dbuser = db_username or 'aiida_qs_' + osuser + '_' + aiida_base_dir_hash - from aiida.common.hashing import get_random_string + # default database name is _ + # this ensures that for profiles named test_... the database will also be named test_... + dbuser = db_username or 'aiida_qs_' + osuser + '_' + config_dir_hash dbpass = db_password or get_random_string(length=50) # check if there is a profile that contains the db user already # and if yes, take the db user password from there # This is ok because a user can only see his own config files - config = load_config(create=True) + dbname = db_name or profile_name + '_' + osuser + '_' + config_dir_hash for profile in config.profiles: if profile.dictionary.get('AIIDADB_USER', '') == dbuser and not db_password: dbpass = profile.dictionary.get('AIIDADB_PASS') @@ -128,10 +125,9 @@ def quicksetup(profile_name, only_config, set_default, non_interactive, backend, dbhost = postgres.dbinfo.get('host', 'localhost') dbport = postgres.dbinfo.get('port', '5432') - from os.path import isabs - repo = repository or 'repository-{}/'.format(profile_name) - if not isabs(repo): - repo = os.path.join(aiida_dir, repo) + repo = repository or 'repository/{}/'.format(profile_name) + if not os.path.isabs(repo): + repo = os.path.join(config.dirpath, repo) setup_args = { 'backend': backend, diff --git a/aiida/cmdline/commands/cmd_setup.py b/aiida/cmdline/commands/cmd_setup.py index 08654c4adf..84bfa5af97 100644 --- a/aiida/cmdline/commands/cmd_setup.py +++ b/aiida/cmdline/commands/cmd_setup.py @@ -36,8 +36,14 @@ def setup(profile_name, only_config, set_default, non_interactive, backend, db_host, db_port, db_name, db_username, db_password, repository, email, first_name, last_name, institution): """Setup and configure a new profile.""" + from aiida.manage.configuration.utils import load_config + from aiida.manage.configuration.setup import create_instance_directories + + create_instance_directories() + load_config(create=True) + kwargs = dict( - profile=profile_name, + profile_name=profile_name, only_config=only_config, set_default=set_default, non_interactive=non_interactive, diff --git a/aiida/cmdline/commands/cmd_user.py b/aiida/cmdline/commands/cmd_user.py index f8d15cafb7..6e69fc4ffd 100644 --- a/aiida/cmdline/commands/cmd_user.py +++ b/aiida/cmdline/commands/cmd_user.py @@ -113,16 +113,12 @@ def user_list(color): List all the users. :param color: Show the list using colors """ - from aiida.common.exceptions import ConfigurationError from aiida.manage import get_manager from aiida import orm manager = get_manager() - try: - current_user = manager.get_profile().default_user_email - except ConfigurationError: - current_user = None + current_user = manager.get_profile().default_user_email if current_user is not None: pass diff --git a/aiida/cmdline/commands/cmd_verdi.py b/aiida/cmdline/commands/cmd_verdi.py index 0b96bcfa1c..ea8d4f69a5 100644 --- a/aiida/cmdline/commands/cmd_verdi.py +++ b/aiida/cmdline/commands/cmd_verdi.py @@ -16,6 +16,7 @@ from aiida.cmdline.params import options from aiida.common.extendeddicts import AttributeDict +from aiida.common import exceptions @click.group() @@ -28,6 +29,7 @@ def verdi(ctx, profile, version): import aiida from aiida.backends import settings from aiida.cmdline.utils import echo + from aiida.manage import get_config if version: echo.echo('AiiDA version {}'.format(aiida.__version__)) @@ -36,8 +38,17 @@ def verdi(ctx, profile, version): if ctx.obj is None: ctx.obj = AttributeDict() - if profile: + try: + config = get_config() + except exceptions.ConfigurationError: + config = None + else: + if not profile: + profile = config.get_profile() + settings.AIIDADB_PROFILE = profile.name - ctx.obj.profile = profile + + ctx.obj.config = config + ctx.obj.profile = profile ctx.help_option_names = ['-h', '--help'] diff --git a/aiida/cmdline/params/arguments/__init__.py b/aiida/cmdline/params/arguments/__init__.py index c1a4c1e96f..9333402245 100644 --- a/aiida/cmdline/params/arguments/__init__.py +++ b/aiida/cmdline/params/arguments/__init__.py @@ -66,3 +66,5 @@ USER = OverridableArgument('user', metavar='USER', type=types.UserParamType()) PROFILE_NAME = OverridableArgument('profile_name', type=click.STRING) + +CONFIG_OPTION = OverridableArgument('option', type=types.ConfigOptionParamType()) diff --git a/aiida/cmdline/params/types/__init__.py b/aiida/cmdline/params/types/__init__.py index a414c9bb0e..6db0e1b06b 100644 --- a/aiida/cmdline/params/types/__init__.py +++ b/aiida/cmdline/params/types/__init__.py @@ -16,6 +16,7 @@ from .choice import LazyChoice from .code import CodeParamType from .computer import ComputerParamType, ShebangParamType, MpirunCommandParamType +from .config import ConfigOptionParamType from .data import DataParamType from .group import GroupParamType from .identifier import IdentifierParamType @@ -32,8 +33,9 @@ from .workflow import WorkflowParamType __all__ = [ - 'LazyChoice', 'IdentifierParamType', 'CalculationParamType', 'CodeParamType', 'ComputerParamType', 'DataParamType', - 'GroupParamType', 'NodeParamType', 'MpirunCommandParamType', 'MultipleValueParamType', 'NonEmptyStringParamType', - 'PluginParamType', 'AbsolutePathParamType', 'ShebangParamType', 'LegacyWorkflowParamType', 'UserParamType', - 'TestModuleParamType', 'ProfileParamType', 'WorkflowParamType', 'ProcessParamType' + 'LazyChoice', 'IdentifierParamType', 'CalculationParamType', 'CodeParamType', 'ComputerParamType', + 'ConfigOptionParamType', 'DataParamType', 'GroupParamType', 'NodeParamType', 'MpirunCommandParamType', + 'MultipleValueParamType', 'NonEmptyStringParamType', 'PluginParamType', 'AbsolutePathParamType', 'ShebangParamType', + 'LegacyWorkflowParamType', 'UserParamType', 'TestModuleParamType', 'ProfileParamType', 'WorkflowParamType', + 'ProcessParamType' ] diff --git a/aiida/cmdline/params/types/config.py b/aiida/cmdline/params/types/config.py new file mode 100644 index 0000000000..b68ba7f18d --- /dev/null +++ b/aiida/cmdline/params/types/config.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Module to define the custom click type for code.""" +from __future__ import division +from __future__ import print_function +from __future__ import absolute_import + +import click + + +class ConfigOptionParamType(click.types.StringParamType): + """ParamType for configuration options.""" + + name = 'config option' + + def convert(self, value, param, ctx): + from aiida.manage.configuration.options import get_option, get_option_names + + if value not in get_option_names(): + raise click.BadParameter('{} is not a valid configuration option'.format(value)) + + return get_option(value) + + def complete(self, ctx, incomplete): # pylint: disable=unused-argument,no-self-use + """ + Return possible completions based on an incomplete value + + :returns: list of tuples of valid entry points (matching incomplete) and a description + """ + from aiida.manage.configuration.options import get_option_names + + return [(option_name, '') for option_name in get_option_names() if option_name.startswith(incomplete)] diff --git a/aiida/cmdline/params/types/profile.py b/aiida/cmdline/params/types/profile.py index d44372d43e..5f439dfc58 100644 --- a/aiida/cmdline/params/types/profile.py +++ b/aiida/cmdline/params/types/profile.py @@ -22,10 +22,10 @@ class ProfileParamType(click.ParamType): def convert(self, value, param, ctx): """Attempt to match the given value to a valid profile.""" from aiida.common.exceptions import MissingConfigurationError, ProfileConfigurationError - from aiida.manage import load_config + from aiida.manage import get_config try: - config = load_config() + config = get_config() profile = config.get_profile(value) except (MissingConfigurationError, ProfileConfigurationError) as exception: self.fail(str(exception)) @@ -39,10 +39,10 @@ def complete(self, ctx, incomplete): # pylint: disable=unused-argument,no-self- :returns: list of tuples of valid entry points (matching incomplete) and a description """ from aiida.common.exceptions import MissingConfigurationError - from aiida.manage import load_config + from aiida.manage import get_config try: - config = load_config() + config = get_config() except MissingConfigurationError: return [] diff --git a/aiida/cmdline/utils/common.py b/aiida/cmdline/utils/common.py index 44468ed7f2..93f05eb39e 100644 --- a/aiida/cmdline/utils/common.py +++ b/aiida/cmdline/utils/common.py @@ -19,15 +19,14 @@ def get_env_with_venv_bin(): - """ - Create a clone of the current running environment with the AIIDA_PATH variable set to the - value configured in the AIIDA_CONFIG_FOLDER variable - """ - from aiida.manage.configuration.settings import AIIDA_CONFIG_FOLDER + """Create a clone of the current running environment with the AIIDA_PATH variable set directory of the config.""" + from aiida.manage import get_config + + config = get_config() currenv = os.environ.copy() currenv['PATH'] = os.path.dirname(sys.executable) + ':' + currenv['PATH'] - currenv['AIIDA_PATH'] = os.path.abspath(os.path.expanduser(AIIDA_CONFIG_FOLDER)) + currenv['AIIDA_PATH'] = config.dirpath currenv['PYTHONUNBUFFERED'] = 'True' return currenv diff --git a/aiida/cmdline/utils/decorators.py b/aiida/cmdline/utils/decorators.py index 4164da388e..aa9e656ecf 100644 --- a/aiida/cmdline/utils/decorators.py +++ b/aiida/cmdline/utils/decorators.py @@ -208,11 +208,11 @@ def mycommand(): @wraps(function) def decorated_function(*args, **kwargs): """Echo a deprecation warning before doing anything else.""" - from aiida.manage import load_config from aiida.cmdline.utils import templates + from aiida.manage import get_config from textwrap import wrap - profile = load_config().current_profile + profile = get_config().current_profile if not profile.is_test_profile: template = templates.env.get_template('deprecated.tpl') diff --git a/aiida/cmdline/utils/defaults.py b/aiida/cmdline/utils/defaults.py index d8d08d6a8b..a417a8466a 100644 --- a/aiida/cmdline/utils/defaults.py +++ b/aiida/cmdline/utils/defaults.py @@ -12,18 +12,17 @@ from __future__ import print_function from __future__ import absolute_import -import click - +from aiida.cmdline.utils import echo from aiida.common import exceptions -from aiida.manage import load_config +from aiida.manage import get_config def get_default_profile(ctx, param, value): # pylint: disable=unused-argument """Try to get the default profile. This should be used if the default profile should be returned lazily, at a point for example when the config - is not created at import time. Otherwise, the preference should go to calling `load_config` to load the actual - config and using `config.default_profile` to get the default profile + is not created at import time. Otherwise, the preference should go to calling `get_config` to load the actual + config and using `config.default_profile_name` to get the default profile name :raises click.UsageError: if the config could not be loaded or no default profile exists :return: the default profile @@ -32,13 +31,13 @@ def get_default_profile(ctx, param, value): # pylint: disable=unused-argument return value try: - config = load_config() - except (exceptions.MissingConfigurationError, exceptions.ConfigurationError) as exception: - raise click.UsageError(str(exception)) + config = get_config() + except exceptions.ConfigurationError as exception: + echo.echo_critical(str(exception)) try: - default_profile = config.default_profile + default_profile = config.get_profile(config.default_profile_name) except exceptions.ProfileConfigurationError: - raise click.UsageError('no default profile is configured, please set it or specify one') + default_profile = None return default_profile diff --git a/aiida/cmdline/utils/shell.py b/aiida/cmdline/utils/shell.py index f1354f2b62..06dbbd86e6 100644 --- a/aiida/cmdline/utils/shell.py +++ b/aiida/cmdline/utils/shell.py @@ -83,7 +83,9 @@ def run_shell(interface=None): def get_start_namespace(): """Load all default and custom modules""" - from aiida.common.setup import get_property + from aiida.manage import get_config + + config = get_config() user_ns = {} # load default modules @@ -91,11 +93,9 @@ def get_start_namespace(): user_ns[alias] = getattr(__import__(app_mod, {}, {}, model_name), model_name) # load custom modules - custom_modules_list = [ - (str(e[0]), str(e[2])) - for e in [p.rpartition('.') for p in get_property('verdishell.modules', default="").split(':')] - if e[1] == '.' - ] + custom_modules_list = [(str(e[0]), str(e[2])) + for e in [p.rpartition('.') for p in config.option_get('verdishell.modules').split(':')] + if e[1] == '.'] for app_mod, model_name in custom_modules_list: try: diff --git a/aiida/common/additions/backup_script/backup_base.py b/aiida/common/additions/backup_script/backup_base.py index dd946af97f..cefe0feb10 100644 --- a/aiida/common/additions/backup_script/backup_base.py +++ b/aiida/common/additions/backup_script/backup_base.py @@ -315,10 +315,10 @@ def _find_files_to_backup(self): def _get_repository_path(self): from aiida.common.setup import parse_repository_uri from aiida.common.exceptions import MissingConfigurationError - from aiida.manage import load_config + from aiida.manage import get_config try: - config = load_config() + config = get_config() except MissingConfigurationError: raise MissingConfigurationError( "Please run the AiiDA Installation, no config found") diff --git a/aiida/common/caching.py b/aiida/common/caching.py index c28b6a56bf..07f6b0755f 100644 --- a/aiida/common/caching.py +++ b/aiida/common/caching.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- ########################################################################### # Copyright (c), The AiiDA team. All rights reserved. # @@ -8,29 +7,22 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### - from __future__ import division from __future__ import print_function from __future__ import absolute_import + import io import os import copy from functools import wraps from contextlib import contextmanager -try: - from collections import ChainMap -except ImportError: - from chainmap import ChainMap - import yaml import six -import aiida -from aiida.common.exceptions import ConfigurationError -from aiida.common.extendeddicts import Enumerate -from aiida.manage.configuration.settings import AIIDA_CONFIG_FOLDER from aiida.backends.utils import get_current_profile +from aiida.common import exceptions +from aiida.common.extendeddicts import Enumerate from aiida.common.utils import get_object_from_string __all__ = ['get_use_cache', 'enable_caching', 'disable_caching'] @@ -71,19 +63,23 @@ def _get_config(config_file): config[key] = [get_object_from_string(c) for c in config[key]] except (ValueError) as err: six.raise_from( - ConfigurationError("Unknown class given in 'cache_config.yml': '{}'".format(err)), - err - ) + exceptions.ConfigurationError("Unknown class given in 'cache_config.yml': '{}'".format(err)), err) return config _CONFIG = {} -def configure(config_file=os.path.join(os.path.expanduser(AIIDA_CONFIG_FOLDER), 'cache_config.yml')): +def configure(config_file=None): """ Reads the caching configuration file and sets the _CONFIG variable. """ + if config_file is None: + from aiida.manage import get_config + + config = get_config() + config_file = os.path.join(config.dirpath, 'cache_config.yml') + global _CONFIG _CONFIG.clear() _CONFIG.update(_get_config(config_file=config_file)) diff --git a/aiida/common/log.py b/aiida/common/log.py index 583388a218..f079450646 100644 --- a/aiida/common/log.py +++ b/aiida/common/log.py @@ -15,7 +15,7 @@ import copy import logging -from aiida.common.setup import get_property +from aiida.manage import get_config_option # Custom logging level, intended specifically for informative log messages # reported during WorkChains and Workflows. We want the level between INFO(20) @@ -115,49 +115,44 @@ def emit(self, record): 'filters': ['testing'] }, 'dblogger': { - # setup.get_property takes the property from the config json file - # The key used in the json, and the default value, are - # specified in the _property_table inside aiida.common.setup - # NOTE: To modify properties, use the 'verdi devel setproperty' - # command and similar ones (getproperty, describeproperties, ...) - 'level': get_property('logging.db_loglevel'), + 'level': get_config_option('logging.db_loglevel'), 'class': 'aiida.common.log.DBLogHandler', }, }, 'loggers': { 'aiida': { 'handlers': ['console', 'dblogger'], - 'level': get_property('logging.aiida_loglevel'), + 'level': get_config_option('logging.aiida_loglevel'), 'propagate': False, }, 'tornado': { 'handlers': ['console'], - 'level': get_property('logging.tornado_loglevel'), + 'level': get_config_option('logging.tornado_loglevel'), 'propagate': False, }, 'plumpy': { 'handlers': ['console'], - 'level': get_property('logging.plumpy_loglevel'), + 'level': get_config_option('logging.plumpy_loglevel'), 'propagate': False, }, 'kiwipy': { 'handlers': ['console'], - 'level': get_property('logging.kiwipy_loglevel'), + 'level': get_config_option('logging.kiwipy_loglevel'), 'propagate': False, }, 'paramiko': { 'handlers': ['console'], - 'level': get_property('logging.paramiko_loglevel'), + 'level': get_config_option('logging.paramiko_loglevel'), 'propagate': False, }, 'alembic': { 'handlers': ['console'], - 'level': get_property('logging.alembic_loglevel'), + 'level': get_config_option('logging.alembic_loglevel'), 'propagate': False, }, 'sqlalchemy': { 'handlers': ['console'], - 'level': get_property('logging.sqlalchemy_loglevel'), + 'level': get_config_option('logging.sqlalchemy_loglevel'), 'propagate': False, 'qualname': 'sqlalchemy.engine', }, diff --git a/aiida/common/setup.py b/aiida/common/setup.py index e424361478..2db82545bc 100644 --- a/aiida/common/setup.py +++ b/aiida/common/setup.py @@ -13,7 +13,6 @@ import os -import six from six.moves import input from aiida.common import exceptions @@ -26,7 +25,7 @@ TEST_KEYWORD = 'test_' -def create_config_noninteractive(profile_name='default', force_overwrite=False, **kwargs): +def create_profile_noninteractive(config, profile_name='default', force_overwrite=False, **kwargs): """ Non-interactively creates a profile. :raises: a ValueError if the profile exists. @@ -35,12 +34,9 @@ def create_config_noninteractive(profile_name='default', force_overwrite=False, :param values: The configuration inputs :return: The populated profile that was also stored """ - from aiida.manage import load_config from aiida.manage.configuration.settings import DEFAULT_UMASK - config = load_config(create=True) - - if config.profile_exists(profile_name) and not force_overwrite: + if profile_name in config.profile_names and not force_overwrite: raise ValueError(('profile {} exists, cannot non-interactively edit a profile.').format(profile_name)) profile = {} @@ -85,22 +81,17 @@ def create_config_noninteractive(profile_name='default', force_overwrite=False, # Generate the profile uuid profile[Profile.KEY_PROFILE_UUID] = Profile.generate_uuid() - # finalizing - config.add_profile(profile_name, profile) - config.set_default_profile(profile_name) - return profile -def create_configuration(profile_name='default'): +def create_profile(config, profile_name='default'): """ :param profile_name: The profile to be configured :return: The populated profile that was also stored. """ import readline from validate_email import validate_email - from aiida.manage import load_config - from aiida.manage.configuration.settings import AIIDA_CONFIG_FOLDER, DEFAULT_UMASK, DEFAULT_AIIDA_USER + from aiida.manage.configuration.settings import DEFAULT_UMASK, DEFAULT_AIIDA_USER from aiida.common.utils import query_yes_no print("Setting up profile {}.".format(profile_name)) @@ -112,8 +103,6 @@ def create_configuration(profile_name='default'): "modification (repository and database data).") is_test_profile = True - config = load_config(create=True) - try: profile = config.get_profile(profile_name).dictionary except exceptions.ProfileConfigurationError: @@ -281,8 +270,8 @@ def create_configuration(profile_name='default'): # This part for the time being is a bit oddly written # it should change in the future to add the possibility of having a # remote repository. Atm, I act as only a local repo is possible - existing_repo = profile.get('AIIDADB_REPOSITORY_URI', - os.path.join(AIIDA_CONFIG_FOLDER, "repository/{}/".format(profile_name))) + repository_dirpath = os.path.join(config.dirpath, 'repository/{}/'.format(profile_name)) + existing_repo = profile.get('AIIDADB_REPOSITORY_URI', repository_dirpath) default_protocol = 'file://' if existing_repo.startswith(default_protocol): existing_repo = existing_repo[len(default_protocol):] @@ -321,243 +310,11 @@ def create_configuration(profile_name='default'): # Add the profile uuid this_new_confs[Profile.KEY_PROFILE_UUID] = Profile.generate_uuid() - config.add_profile(profile_name, this_new_confs) - return this_new_confs finally: readline.set_startup_hook(lambda: readline.insert_text("")) -# A table of properties. -# The key is the property name to use in the code; -# The value is a tuple, where: -# - the first entry is a string used as the key in the -# JSON config file -# - the second is the expected data type for data -# conversion if the property is passed as a string. -# For valid data type strings, see the implementation of set_property -# - the third entry is the description of the field -# - the fourth entry is the default value. Use _NoDefaultValue() if you want -# an exception to be raised if no property is found. - - -class _NoDefaultValue(object): - pass - - -# Only properties listed here can be changed/set with the command line. -# These properties are stored in the aiida config file. -# Each entry key is the name of the property used on the command line; -# the value must be a 4-tuple, whose elements are -# 1. the key actually used in the config json file -# 2. the type -# 3. A human-readable description -# 4. The default value, if no setting is found -# 5. A list of valid values, or None if no such list makes sense -_property_table = { - "runner.poll.interval": ("runner_poll_interval", "int", - "The polling interval in seconds to be used by process runners", 1, None), - "daemon.timeout": ("daemon_timeout", "int", "The timeout in seconds for calls to the circus client", - 20, None), - "verdishell.modules": ("modules_for_verdi_shell", "string", - "Additional modules/functions/classes to be automaticaly loaded in the " - "verdi shell (but not in the runaiida environment); it should be a " - "string with the full paths for each module," - " function or class, separated by colons, e.g. " - "'aiida.backends.djsite.db.models:aiida.orm.querytool.Querytool'", "", None), - "verdishell.calculation_list": ("projections_for_calculation_list", "list_of_str", - "A list of the projections that should be shown by default " - "when typing 'verdi calculation list'. " - "Set by passing the projections space separated as a string, for example: " - "verdi devel setproperty verdishell.calculation_list 'pk time job_state'", - ('pk', 'ctime', 'process_state', 'type', 'job_state'), None), - "logging.aiida_loglevel": ("logging_aiida_log_level", "string", - "Minimum level to log to the file ~/.aiida/daemon/log/aiida_daemon.log " - "and to the DbLog table for the 'aiida' logger; for the DbLog, see " - "also the logging.db_loglevel variable to further filter messages going " - "to the database", "REPORT", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", - "DEBUG"]), - "logging.tornado_loglevel": - ("logging_tornado_log_level", "string", "Minimum level to log to the file ~/.aiida/daemon/log/aiida_daemon.log " - "for the 'tornado' loggers", "WARNING", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.plumpy_loglevel": - ("logging_plumpy_log_level", "string", "Minimum level to log to the file ~/.aiida/daemon/log/aiida_daemon.log " - "for the 'plumpy' logger", "WARNING", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.kiwipy_loglevel": - ("logging_kiwipy_log_level", "string", "Minimum level to log to the file ~/.aiida/daemon/log/aiida_daemon.log " - "for the 'kiwipy' logger", "WARNING", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.paramiko_loglevel": ("logging_paramiko_log_level", "string", - "Minimum level to log to the file ~/.aiida/daemon/log/aiida_daemon.log " - "for the 'paramiko' logger", "WARNING", - ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.alembic_loglevel": ("logging_alembic_log_level", "string", "Minimum level to log to the console", - "WARNING", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.sqlalchemy_loglevel": ("logging_sqlalchemy_loglevel", "string", "Minimum level to log to the console", - "WARNING", ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.circus_loglevel": ("logging_circus_log_level", "string", - "Minimum level to log to the circus daemon log file" - "for the 'circus' logger", "INFO", - ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "logging.db_loglevel": ("logging_db_log_level", "string", "Minimum level to log to the DbLog table", "REPORT", - ["CRITICAL", "ERROR", "WARNING", "REPORT", "INFO", "DEBUG"]), - "tcod.depositor_username": ("tcod_depositor_username", "string", "Username for TCOD deposition", None, None), - "tcod.depositor_password": ("tcod_depositor_password", "string", "Password for TCOD deposition", None, None), - "tcod.depositor_email": ("tcod_depositor_email", "string", "E-mail address for TCOD deposition", None, None), - "tcod.depositor_author_name": ("tcod_depositor_author_name", "string", "Author name for TCOD depositions", None, - None), - "tcod.depositor_author_email": ("tcod_depositor_author_email", "string", "E-mail address for TCOD depositions", - None, None), - "warnings.showdeprecations": ("show_deprecations", "bool", "Boolean whether to print AiiDA deprecation warnings", - True, None) -} - - -def exists_property(name): - """ - Check if a property exists in the DB. - - .. note:: this is useful if one wants explicitly to know if a property - is defined just because it has a default value, or because it is - explicitly defined in the config file. - - :param name: the name of the property to check. - - :raise ValueError: if the given name is not a valid property (as stored in - the _property_table dictionary). - """ - from aiida.manage import load_config - - try: - key, _, _, table_defval, _ = _property_table[name] - except KeyError: - raise ValueError("{} is not a recognized property".format(name)) - - try: - config = load_config() - return key in config.dictionary - except exceptions.MissingConfigurationError: # No file found - return False - - -def get_property(name, default=_NoDefaultValue()): - """ - Get a property from the json file. - - :param name: the name of the property to get. - :param default: if provided, this value is returned if no value is found - in the database. - - :raise ValueError: if the given name is not a valid property (as stored in - the _property_table dictionary). - :raise KeyError: if the given property is not found in the config file, and - no default value is given or provided in _property_table. - """ - from aiida.common.log import LOG_LEVELS - from aiida.manage import load_config - - try: - key, _, _, table_defval, _ = _property_table[name] - except KeyError: - raise ValueError("{} is not a recognized property".format(name)) - - value = None - try: - config = load_config() - value = config.dictionary[key] - except (KeyError, exceptions.MissingConfigurationError): - if isinstance(default, _NoDefaultValue): - if isinstance(table_defval, _NoDefaultValue): - raise - else: - value = table_defval - else: - value = default - - # This translation is necessary because the logging module can only - # accept numerical log levels (except for the predefined ones). - # A side-effect of this is that: - # verdi devel getproperty logging.[x]_loglevel - # will return the corresponding integer, even though a string is stored in - # the config. - if name.startswith('logging.') and name.endswith('loglevel'): - value = LOG_LEVELS[value] - - return value - - -def del_property(name): - """ - Delete a property in the json file. - - :param name: the name of the property to delete. - :raise: MissingConfigurationError if the key is not found in the configuration file. - """ - from aiida.manage import load_config - - try: - key, _, _, _, _ = _property_table[name] - except KeyError: - raise ValueError("{} is not a recognized property".format(name)) - - config = load_config() - del config.dictionary[key] - - config.store() - - -def set_property(name, value): - """ - Set a property in the json file. - - :param name: The name of the property value to set. - :param value: the value to set. If it is a string, it is possibly casted - to the correct type according to the information in the _property_table - dictionary. - - :raise ValueError: if the provided name is not among the set of valid - properties, or if the value provided as a string cannot be casted to the - correct type. - """ - from aiida.manage import load_config - - try: - key, type_string, _, _, valid_values = _property_table[name] - except KeyError: - raise ValueError("'{}' is not a recognized property".format(name)) - - actual_value = False - - if type_string == "bool": - if isinstance(value, six.string_types): - if value.strip().lower() in ["0", "false", "f"]: - actual_value = False - elif value.strip().lower() in ["1", "true", "t"]: - actual_value = True - else: - raise ValueError("Invalid bool value for property {}".format(name)) - else: - actual_value = bool(value) - elif type_string == "string": - actual_value = six.text_type(value) - elif type_string == "int": - actual_value = int(value) - elif type_string == 'list_of_str': - # I expect the results as a list of strings - actual_value = value.split() - else: - # Implement here other data types - raise NotImplementedError("Type string '{}' not implemented yet".format(type_string)) - - if valid_values is not None: - if actual_value not in valid_values: - raise ValueError("'{}' is not among the list of accepted values " - "for property {}".format(actual_value, name)) - - config = load_config() - config.dictionary[key] = actual_value - config.store() - - def parse_repository_uri(repository_uri): """ This function validates the REPOSITORY_URI, that should be in the diff --git a/aiida/common/warnings.py b/aiida/common/warnings.py index 74604a5c78..8d1bfcdc0a 100644 --- a/aiida/common/warnings.py +++ b/aiida/common/warnings.py @@ -23,6 +23,6 @@ class AiidaDeprecationWarning(Warning): this would be filtered out by default. Enabled by default, you can disable it by running in the shell:: - verdi devel setproperty warnings.showdeprecations False + verdi config warnings.showdeprecations False """ pass diff --git a/aiida/control/profile.py b/aiida/control/profile.py index f4ea38241b..35955cd8da 100644 --- a/aiida/control/profile.py +++ b/aiida/control/profile.py @@ -16,11 +16,11 @@ from aiida.cmdline.utils import echo -def setup_profile(profile, only_config, set_default=False, non_interactive=False, **kwargs): +def setup_profile(profile_name, only_config, set_default=False, non_interactive=False, **kwargs): """ Setup an AiiDA profile and AiiDA user (and the AiiDA default user). - :param profile: Profile name + :param profile_name: Profile name :param only_config: do not create a new user :param set_default: set the new profile as the default :param non_interactive: do not prompt for configuration values, fail if not all values are given as kwargs. @@ -37,10 +37,11 @@ def setup_profile(profile, only_config, set_default=False, non_interactive=False from aiida.cmdline.commands import cmd_user from aiida.common.exceptions import InvalidOperation from aiida.manage.configuration.setup import create_instance_directories - from aiida.common.setup import create_configuration, create_config_noninteractive - from aiida.manage import get_manager, load_config + from aiida.common.setup import create_profile, create_profile_noninteractive + from aiida.manage import get_config, get_manager from aiida.manage.configuration.settings import DEFAULT_AIIDA_USER + config = get_config() manager = get_manager() only_user_config = only_config @@ -49,14 +50,15 @@ def setup_profile(profile, only_config, set_default=False, non_interactive=False create_instance_directories() # we need to overwrite this variable for the following to work - settings.AIIDADB_PROFILE = profile + settings.AIIDADB_PROFILE = profile_name - created_conf = None + profile = None # ask and store the configuration of the DB if non_interactive: try: - created_conf = create_config_noninteractive( - profile_name=profile, + profile = create_profile_noninteractive( + config=config, + profile_name=profile_name, backend=kwargs['backend'], email=kwargs['email'], db_host=kwargs['db_host'], @@ -76,20 +78,21 @@ def setup_profile(profile, only_config, set_default=False, non_interactive=False exception.args[0])) else: try: - created_conf = create_configuration(profile_name=profile) + profile = create_profile(config=config, profile_name=profile_name) except ValueError as exception: echo.echo_critical("Error during configuration: {}".format(exception)) - # Set default DB profile - config = load_config() - config.set_default_profile(profile, overwrite=set_default) + # Add the created profile and set it as the new default profile + config.add_profile(profile_name, profile) + config.set_default_profile(profile_name, overwrite=set_default) + config.store() if only_user_config: echo.echo("Only user configuration requested, skipping the migrate command") else: echo.echo("Executing now a migrate command...") - backend_choice = created_conf['AIIDADB_BACKEND'] + backend_choice = profile['AIIDADB_BACKEND'] if backend_choice == BACKEND_DJANGO: echo.echo("...for Django backend") backend = manager._load_backend(schema_check=False) # pylint: disable=protected-access diff --git a/aiida/daemon/client.py b/aiida/daemon/client.py index ffeffa30cf..a7b530064d 100644 --- a/aiida/daemon/client.py +++ b/aiida/daemon/client.py @@ -23,8 +23,7 @@ import six -from aiida.common.setup import get_property -from aiida.manage import load_config +from aiida.manage import get_config from aiida.utils.which import which VERDI_BIN = which('verdi') @@ -51,7 +50,7 @@ def get_daemon_client(profile_name=None): :raises MissingConfigurationError: if the configuration file cannot be found :raises ProfileConfigurationError: if the given profile does not exist """ - config = load_config() + config = get_config() if profile_name: profile = config.get_profile(profile_name) @@ -80,9 +79,10 @@ def __init__(self, profile): :param profile: the profile instance :class:`aiida.manage.configuration.profile.Profile` """ + config = get_config() self._profile = profile self._SOCKET_DIRECTORY = None # pylint: disable=invalid-name - self._DAEMON_TIMEOUT = get_property('daemon.timeout') # pylint: disable=invalid-name + self._DAEMON_TIMEOUT = config.option_get('daemon.timeout') # pylint: disable=invalid-name @property def profile(self): diff --git a/aiida/manage/configuration/__init__.py b/aiida/manage/configuration/__init__.py index e0c1caa887..5a3dcb90d3 100644 --- a/aiida/manage/configuration/__init__.py +++ b/aiida/manage/configuration/__init__.py @@ -1,8 +1,31 @@ # -*- coding: utf-8 -*- -# pylint: disable=undefined-variable,wildcard-import +# pylint: disable=undefined-variable,wildcard-import,global-statement """Modules related to the configuration of an AiiDA instance.""" from .config import * from .profile import * +from .utils import * -__all__ = (config.__all__ + profile.__all__) +CONFIG = None + +__all__ = (config.__all__ + profile.__all__ + utils.__all__ + ('get_config',)) + + +def get_config(): + """Return the current configuration. + + If the configuration has not been loaded yet, it will be loaded first and then returned. + + Note: this function should only be called by parts of the code that expect that a complete AiiDA instance exists, + i.e. an AiiDA folder exists and contains a valid configuration file. + + :return: the config + :rtype: :class:`~aiida.manage.configuration.config.Config` + :raises ConfigurationError: if the configuration file could not be found, read or deserialized + """ + global CONFIG + + if not CONFIG: + CONFIG = load_config() + + return CONFIG diff --git a/aiida/manage/configuration/config.py b/aiida/manage/configuration/config.py index 2fe09ac180..16222ec044 100644 --- a/aiida/manage/configuration/config.py +++ b/aiida/manage/configuration/config.py @@ -9,80 +9,20 @@ from aiida.common import exceptions from aiida.utils import json -from .migrations import check_and_migrate_config, CURRENT_CONFIG_VERSION, OLDEST_COMPATIBLE_CONFIG_VERSION +from .migrations import CURRENT_CONFIG_VERSION, OLDEST_COMPATIBLE_CONFIG_VERSION +from .options import get_option, parse_option, NO_DEFAULT from .profile import Profile -from .settings import DEFAULT_CONFIG_FILE_NAME, DEFAULT_UMASK, DEFAULT_CONFIG_INDENT_SIZE +from .settings import DEFAULT_UMASK, DEFAULT_CONFIG_INDENT_SIZE -__all__ = ('load_config', 'Config') - -CONFIG = None - - -def create_config(): - """Create the template configuration file and store it. - - :raises ConfigurationError: if a configuration file already exists - """ - from .settings import AIIDA_CONFIG_FOLDER - from aiida.backends.settings import IN_RT_DOC_MODE, DUMMY_CONF_FILE - - if IN_RT_DOC_MODE: - return DUMMY_CONF_FILE - - dirpath = os.path.expanduser(AIIDA_CONFIG_FOLDER) - filepath = os.path.join(dirpath, DEFAULT_CONFIG_FILE_NAME) - - if os.path.isfile(filepath): - raise exceptions.ConfigurationError('configuration file {} already exists'.format(filepath)) - - config = Config(filepath, {}) - - return config - - -def load_config(create=False): - """Instantiate the Config object representing the configuration file of the current AiiDA instance. - - :param create: when set to True, will create the configuration file if it does not already exist - :return: the config - :rtype: :class:`~aiida.manage.configuration.config.Config` - :raises MissingConfigurationError: if the configuration file could not be found and create=False - """ - from .settings import AIIDA_CONFIG_FOLDER - from aiida.backends.settings import IN_RT_DOC_MODE, DUMMY_CONF_FILE - - if IN_RT_DOC_MODE: - return DUMMY_CONF_FILE - - global CONFIG # pylint: disable=global-statement - - if CONFIG is None: - dirpath = os.path.expanduser(AIIDA_CONFIG_FOLDER) - filepath = os.path.join(dirpath, DEFAULT_CONFIG_FILE_NAME) - - if not os.path.isfile(filepath): - if not create: - raise exceptions.MissingConfigurationError('configuration file {} does not exist'.format(filepath)) - else: - config = create_config() - else: - try: - with io.open(filepath, 'r', encoding='utf8') as handle: - config = Config(filepath, json.load(handle)) - except IOError: - raise exceptions.ConfigurationError('configuration file {} could not be read'.format(filepath)) - - CONFIG = check_and_migrate_config(config) - - return CONFIG +__all__ = ('Config',) class Config(object): """Object that represents the configuration file of an AiiDA instance.""" KEY_VERSION = 'CONFIG_VERSION' - KEY_CURRENT = 'CURRENT' - KEY_OLDEST_COMPATIBLE = 'OLDEST_COMPATIBLE' + KEY_VERSION_CURRENT = 'CURRENT' + KEY_VERSION_OLDEST_COMPATIBLE = 'OLDEST_COMPATIBLE' KEY_DEFAULT_PROFILE = 'default_profile' KEY_PROFILES = 'profiles' @@ -98,8 +38,8 @@ def __init__(self, filepath, dictionary): # Construct the default configuration template dictionary = { self.KEY_VERSION: { - self.KEY_CURRENT: CURRENT_CONFIG_VERSION, - self.KEY_OLDEST_COMPATIBLE: OLDEST_COMPATIBLE_CONFIG_VERSION, + self.KEY_VERSION_CURRENT: CURRENT_CONFIG_VERSION, + self.KEY_VERSION_OLDEST_COMPATIBLE: OLDEST_COMPATIBLE_CONFIG_VERSION, }, self.KEY_PROFILES: {}, } @@ -121,19 +61,19 @@ def dictionary(self): @property def version(self): - return self._version_settings.get(self.KEY_CURRENT, 0) + return self._version_settings.get(self.KEY_VERSION_CURRENT, 0) @version.setter def version(self, version): - self._version_settings[self.KEY_CURRENT] = version + self._version_settings[self.KEY_VERSION_CURRENT] = version @property def version_oldest_compatible(self): - return self._version_settings.get(self.KEY_OLDEST_COMPATIBLE, 0) + return self._version_settings.get(self.KEY_VERSION_OLDEST_COMPATIBLE, 0) @version_oldest_compatible.setter def version_oldest_compatible(self, version_oldest_compatible): - self._version_settings[self.KEY_OLDEST_COMPATIBLE] = version_oldest_compatible + self._version_settings[self.KEY_VERSION_OLDEST_COMPATIBLE] = version_oldest_compatible @property def _version_settings(self): @@ -147,15 +87,6 @@ def filepath(self): def dirpath(self): return os.path.dirname(self.filepath) - @property - def current_profile_name(self): - """Return the currently configured profile name or if not set, the default profile - - :return: the default profile or None if not defined - """ - from aiida.backends import settings - return settings.AIIDADB_PROFILE or self.default_profile_name - @property def default_profile_name(self): """Return the default profile name. @@ -173,15 +104,14 @@ def current_profile(self): :return: the current profile or None if not defined """ - return self.get_profile(self.current_profile_name) + from aiida.backends import settings - @property - def default_profile(self): - """Return the default profile. + current_profile_name = settings.AIIDADB_PROFILE - :return: the default profile or None if not defined - """ - return self.get_profile(self.default_profile_name) + if current_profile_name: + return self.get_profile(current_profile_name) + + return None @property def profile_names(self): @@ -194,7 +124,7 @@ def profile_names(self): except KeyError: return [] else: - return profiles.keys() + return list(profiles) @property def profiles(self): @@ -210,84 +140,134 @@ def profiles(self): else: return [Profile(name, profile) for name, profile in profiles.items()] - def profile_exists(self, name): - """Return whether the profile with the given name exists. - - :param name: name of the profile: - :return: boolean, True if the profile exists, False otherwise - """ - return name in self.profile_names - def validate_profile(self, name): """Validate that a profile exists. :param name: name of the profile: :raises ProfileConfigurationError: if the name is not found in the configuration file """ - if not self.profile_exists(name): + if name not in self.profile_names: raise exceptions.ProfileConfigurationError('profile `{}` does not exist'.format(name)) - def get_profile(self, name): + def get_profile(self, name=None): """Return the profile for the given name or the default one if not specified. :return: the profile instance or None if it does not exist :raises ProfileConfigurationError: if the name is not found in the configuration file """ + if not name: + name = self.default_profile_name + self.validate_profile(name) + return Profile(name, self.dictionary[self.KEY_PROFILES][name]) - def add_profile(self, name, profile, store=True): + def _get_profile_dictionary(self, name): + """Return the internal profile dictionary for the given name or the default one if not specified. + + :return: the profile instance or None if it does not exist + :raises ProfileConfigurationError: if the name is not found in the configuration file + """ + self.validate_profile(name) + return self.dictionary[self.KEY_PROFILES][name] + + def add_profile(self, name, profile): """Add a profile to the configuration. :param name: the name of the profile to remove :param profile: the profile configuration dictionary - :param store: boolean, if True will write the updated configuration to file + :return: self """ self.dictionary[self.KEY_PROFILES][name] = profile + return self - if store: - self.store() - - def update_profile(self, profile, store=True): + def update_profile(self, profile): """Update a profile in the configuration. :param profile: the profile instance to update - :param store: boolean, if True will write the updated configuration to file + :return: self """ self.dictionary[self.KEY_PROFILES][profile.name] = profile.dictionary + return self - if store: - self.store() - - def remove_profile(self, name, store=True): + def remove_profile(self, name): """Remove a profile from the configuration. :param name: the name of the profile to remove - :param store: boolean, if True will write the updated configuration to file :raises ProfileConfigurationError: if the given profile does not exist + :return: self """ self.validate_profile(name) self.dictionary[self.KEY_PROFILES].pop(name) + return self - if store: - self.store() - - def set_default_profile(self, name, overwrite=False, store=True): + def set_default_profile(self, name, overwrite=False): """Set the given profile as the new default. :param name: name of the profile to set as new default :param overwrite: when True, set the profile as the new default even if a default profile is already defined - :param store: boolean, if True will write the updated configuration to file :raises ProfileConfigurationError: if the given profile does not exist + :return: self """ if self.default_profile_name and not overwrite: - return + return self self.validate_profile(name) self.dictionary[self.KEY_DEFAULT_PROFILE] = name + return self - if store: - self.store() + def option_set(self, option_name, option_value, scope=None): + """Set a configuration option. + + :param option_name: the name of the configuration option + :param option_value: the option value + :param scope: set the option for this profile or globally if not specified + """ + option, parsed_value = parse_option(option_name, option_value) + + if scope is not None: + dictionary = self._get_profile_dictionary(scope) + else: + dictionary = self.dictionary + + if parsed_value: + dictionary[option.key] = parsed_value + elif option.default is not NO_DEFAULT: + dictionary[option.key] = option.default + else: + pass + + def option_unset(self, option_name, scope=None): + """Unset a configuration option. + + :param option_name: the name of the configuration option + :param scope: unset the option for this profile or globally if not specified + """ + option = get_option(option_name) + + if scope is not None: + dictionary = self._get_profile_dictionary(scope) + else: + dictionary = self.dictionary + + dictionary.pop(option.key, None) + + def option_get(self, option_name, scope=None, default=True): + """Get a configuration option. + + :param option_name: the name of the configuration option + :param scope: get the option for this profile or globally if not specified + :param default: boolean, If True will return the option default, even if not defined within the given scope + :return: the option value or None if not set for the given scope + """ + option = get_option(option_name) + + if scope is not None: + dictionary = self.get_profile(scope).dictionary + else: + dictionary = self.dictionary + + return dictionary.get(option.key, option.default if default else None) def store(self): """Write the current config to file.""" @@ -301,6 +281,8 @@ def store(self): finally: os.umask(umask) + return self + def _backup(self): """Create a backup of the current config as it exists on disk.""" if not os.path.isfile(self.filepath): diff --git a/aiida/manage/configuration/migrations/migrations.py b/aiida/manage/configuration/migrations/migrations.py index 3ab26b0813..1befc8c741 100644 --- a/aiida/manage/configuration/migrations/migrations.py +++ b/aiida/manage/configuration/migrations/migrations.py @@ -55,7 +55,7 @@ def _1_add_profile_uuid(config): """ for profile in config.profiles: profile.uuid = profile.generate_uuid() - config.update_profile(profile, store=False) + config.update_profile(profile) return config @@ -75,7 +75,7 @@ def _2_simplify_default_profiles(config): else: default_profile = settings.AIIDADB_PROFILE - config.set_default_profile(default_profile, store=False) + config.set_default_profile(default_profile) return config diff --git a/aiida/manage/configuration/options.py b/aiida/manage/configuration/options.py new file mode 100644 index 0000000000..9e7c31ffe8 --- /dev/null +++ b/aiida/manage/configuration/options.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +"""Definition of known configuration options and methods to parse and get option values.""" +from __future__ import absolute_import + +import collections +import six + +__all__ = ('get_option', 'get_option_names', 'parse_option') + + +class NO_DEFAULT_PLACEHOLDER(object): + """A dummy class to serve as a marker for no default being specified in the `get_option` function.""" + # pylint: disable=too-few-public-methods,invalid-name + pass + + +NO_DEFAULT = NO_DEFAULT_PLACEHOLDER() +DEFAULT_DAEMON_TIMEOUT = 20 # Default timeout in seconds for circus client calls +VALID_LOG_LEVELS = ['CRITICAL', 'ERROR', 'WARNING', 'REPORT', 'INFO', 'DEBUG'] + +Option = collections.namedtuple('Option', ['name', 'key', 'valid_type', 'valid_values', 'default', 'description']) + + +def get_option(option_name): + """Return a configuration option.configuration + + :param option_name: the name of the configuration option + :return: the configuration option + :raises ValueError: if the configuration option does not exist + """ + try: + option = Option(option_name, **CONFIG_OPTIONS[option_name]) + except KeyError: + raise ValueError('the option {} does not exist'.format(option_name)) + else: + return option + + +def get_option_names(): + """Return a list of available option names. + + :return: list of available option names + """ + return CONFIG_OPTIONS.keys() + + +def parse_option(option_name, option_value): + """Parse and validate a value for a configuration option. + + :param option_name: the name of the configuration option + :param option_value: the option value + :return: a tuple of the option and the parsed value + """ + option = get_option(option_name) + + value = False + + if option.valid_type == 'bool': + if isinstance(option_value, six.string_types): + if option_value.strip().lower() in ['0', 'false', 'f']: + value = False + elif option_value.strip().lower() in ['1', 'true', 't']: + value = True + else: + raise ValueError('option {} expects a boolean value'.format(option.name)) + else: + value = bool(option_value) + elif option.valid_type == 'string': + value = six.text_type(option_value) + elif option.valid_type == 'int': + value = int(option_value) + elif option.valid_type == 'list_of_str': + value = option_value.split() + else: + raise NotImplementedError('Type string {} not implemented yet'.format(option.valid_type)) + + if option.valid_values is not None: + if value not in option.valid_values: + raise ValueError('{} is not among the list of accepted values for option {}'.format(value, option.name)) + + return option, value + + +CONFIG_OPTIONS = { + 'runner.poll.interval': { + 'key': 'runner_poll_interval', + 'valid_type': 'int', + 'valid_values': None, + 'default': 1, + 'description': 'The polling interval in seconds to be used by process runners', + }, + 'daemon.timeout': { + 'key': 'daemon_timeout', + 'valid_type': 'int', + 'valid_values': None, + 'default': DEFAULT_DAEMON_TIMEOUT, + 'description': 'The timeout in seconds for calls to the circus client', + }, + 'verdishell.modules': { + 'key': 'modules_for_verdi_shell', + 'valid_type': 'string', + 'valid_values': None, + 'default': '', + 'description': 'Additional modules/functions/classes to be automatically loaded in `verdi shell`', + }, + 'verdishell.calculation_list': { + 'key': 'projections_for_calculation_list', + 'valid_type': 'list_of_str', + 'valid_values': None, + 'default': ('pk', 'ctime', 'process_state', 'type', 'job_state'), + 'description': 'List of default projections for `verdi calculation list`', + }, + 'logging.aiida_loglevel': { + 'key': 'logging_aiida_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'REPORT', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `aiida` logger', + }, + 'logging.db_loglevel': { + 'key': 'logging_db_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'REPORT', + 'description': 'Minimum level to log to the DbLog table', + }, + 'logging.tornado_loglevel': { + 'key': 'logging_tornado_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `tornado` logger', + }, + 'logging.plumpy_loglevel': { + 'key': 'logging_plumpy_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `plumpy` logger', + }, + 'logging.kiwipy_loglevel': { + 'key': 'logging_kiwipy_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `kiwipy` logger', + }, + 'logging.paramiko_loglevel': { + 'key': 'logging_paramiko_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `paramiko` logger', + }, + 'logging.alembic_loglevel': { + 'key': 'logging_alembic_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `alembic` logger', + }, + 'logging.sqlalchemy_loglevel': { + 'key': 'logging_sqlalchemy_loglevel', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'WARNING', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `sqlalchemy` logger', + }, + 'logging.circus_loglevel': { + 'key': 'logging_circus_log_level', + 'valid_type': 'string', + 'valid_values': VALID_LOG_LEVELS, + 'default': 'INFO', + 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `circus` logger', + }, + 'tcod.depositor_username': { + 'key': 'tcod_depositor_username', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Username for TCOD deposition', + }, + 'tcod.depositor_password': { + 'key': 'tcod_depositor_password', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Password for TCOD deposition', + }, + 'tcod.depositor_email': { + 'key': 'tcod_depositor_email', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'E-mail address for TCOD deposition', + }, + 'tcod.depositor_author_name': { + 'key': 'tcod_depositor_author_name', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Author name for TCOD depositions', + }, + 'tcod.depositor_author_email': { + 'key': 'tcod_depositor_author_email', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'E-mail address for TCOD depositions', + }, + 'warnings.showdeprecations': { + 'key': 'show_deprecations', + 'valid_type': 'bool', + 'valid_values': None, + 'default': True, + 'description': 'Boolean whether to print AiiDA deprecation warnings', + }, +} diff --git a/aiida/manage/configuration/profile.py b/aiida/manage/configuration/profile.py index 2790e4d406..27c6a5d135 100644 --- a/aiida/manage/configuration/profile.py +++ b/aiida/manage/configuration/profile.py @@ -14,7 +14,6 @@ import os -from aiida.common import exceptions from aiida.common import extendeddicts from .settings import DAEMON_DIR, DAEMON_LOG_DIR @@ -59,12 +58,6 @@ def generate_uuid(): from uuid import uuid4 return uuid4().hex - @staticmethod - def get_option(option): - """Return the value of an option of this profile.""" - from aiida.common.setup import get_property - return get_property(option) - @property def dictionary(self): """ @@ -128,14 +121,13 @@ def default_user_email(self): """ Return the email (that is used as the username) configured during the first verdi setup. - :return: the currently configured user email address - :rtype: str + :return: the currently configured user email address or None if not defined + :rtype: str or None """ try: email = self.dictionary[self.KEY_DEFAULT_USER] except KeyError: - raise exceptions.ConfigurationError('profile "{}" does not define "{}"'.format( - self.name, self.KEY_DEFAULT_USER)) + email = None return email diff --git a/aiida/manage/configuration/utils.py b/aiida/manage/configuration/utils.py new file mode 100644 index 0000000000..f4cc4105dc --- /dev/null +++ b/aiida/manage/configuration/utils.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +"""Definition of known configuration options and methods to parse and get option values.""" +from __future__ import absolute_import + +import io +import os + +from aiida.common import exceptions +from aiida.utils import json + +from .config import Config +from .migrations import check_and_migrate_config +from .options import get_option +from .settings import DEFAULT_CONFIG_FILE_NAME + +__all__ = ('get_config_option', 'load_config') + + +def get_config_option(option_name): + """Return the value for the given configuration option. + + This function will attempt to load the value of the option as defined for the current profile or otherwise as + defined configuration wide. If no configuration is yet loaded, this function will fall back on the default that may + be defined for the option itself. This is useful for options that need to be defined at loading time of AiiDA when + no configuration is yet loaded or may not even yet exist. In cases where one expects a profile to be loaded, + preference should be given to retrieving the option through the Config instance and its `option_get` method. + + :param option_name: the name of the configuration option + :return: option value as specified for the profile/configuration if loaded, otherwise option default + """ + option = get_option(option_name) + + try: + config = load_config() + except exceptions.ConfigurationError: + value = option.default + else: + if config.current_profile: + value = config.option_get(option_name, scope=config.current_profile.name) + else: + value = config.option_get(option_name) + + return value + + +def load_config(create=False): + """Instantiate the Config object representing the configuration file of the current AiiDA instance. + + :param create: when set to True, will create the configuration file if it does not already exist + :return: the config + :rtype: :class:`~aiida.manage.configuration.config.Config` + :raises MissingConfigurationError: if the configuration file could not be found and create=False + """ + from .settings import AIIDA_CONFIG_FOLDER + from aiida.backends.settings import IN_RT_DOC_MODE, DUMMY_CONF_FILE + + if IN_RT_DOC_MODE: + return DUMMY_CONF_FILE + + filepath = os.path.join(AIIDA_CONFIG_FOLDER, DEFAULT_CONFIG_FILE_NAME) + + if not os.path.isfile(filepath): + if not create: + raise exceptions.MissingConfigurationError('configuration file {} does not exist'.format(filepath)) + else: + config = Config(filepath, {}).store() + else: + try: + with io.open(filepath, 'r', encoding='utf8') as handle: + config = Config(filepath, json.load(handle)) + except ValueError: + raise exceptions.ConfigurationError('configuration file {} contains invalid JSON'.format(filepath)) + + return check_and_migrate_config(config, store=True) diff --git a/aiida/manage/manager.py b/aiida/manage/manager.py index 089d272481..8c30db2a41 100644 --- a/aiida/manage/manager.py +++ b/aiida/manage/manager.py @@ -16,7 +16,7 @@ from aiida import utils -from .configuration import load_config +from .configuration import get_config __all__ = 'get_manager', 'reset_manager' @@ -90,7 +90,7 @@ def get_profile(self): :rtype: :class:`~aiida.manage.configuration.profile.Profile` """ if self._profile is None: - config = load_config() + config = get_config() self._profile = config.current_profile return self._profile @@ -206,8 +206,9 @@ def create_runner(self, with_persistence=True, **kwargs): """ from aiida.work import runners - profile = self.get_profile() - poll_interval = 0.0 if profile.is_test_profile else profile.get_option('runner.poll.interval') + config = get_config() + profile = config.current_profile + poll_interval = 0.0 if profile.is_test_profile else config.option_get('runner.poll.interval') settings = {'rmq_submit': False, 'poll_interval': poll_interval} settings.update(kwargs) diff --git a/aiida/orm/querybuilder.py b/aiida/orm/querybuilder.py index 892b7c0912..25e218d16a 100644 --- a/aiida/orm/querybuilder.py +++ b/aiida/orm/querybuilder.py @@ -351,10 +351,10 @@ def __str__(self): When somebody hits: print(QueryBuilder) or print(str(QueryBuilder)) I want to print the SQL-query. Because it looks cool... """ - from aiida.manage import load_config + from aiida.manage import get_config - config = load_config() - engine = config.current_profile.dicctionary['AIIDADB_ENGINE'] + config = get_config() + engine = config.current_profile.dictionary['AIIDADB_ENGINE'] if engine.startswith("mysql"): from sqlalchemy.dialects import mysql as mydialect diff --git a/aiida/plugins/utils.py b/aiida/plugins/utils.py index c9f5998cbc..bbd2978080 100644 --- a/aiida/plugins/utils.py +++ b/aiida/plugins/utils.py @@ -33,9 +33,8 @@ def registry_cache_folder_path(): return the fully resolved path to the cache folder """ from os import path as osp - from aiida.manage import load_config - config = load_config() - aiida_dir = get_aiida_dir() + from aiida.manage import get_config + config = get_config() cache_dir = registry_cache_folder_name() return osp.join(config.dirpath, cache_dir) diff --git a/aiida/settings.py b/aiida/settings.py index 936eb4b95e..3b2562860a 100644 --- a/aiida/settings.py +++ b/aiida/settings.py @@ -14,13 +14,13 @@ from aiida.backends import settings from aiida.common.exceptions import ConfigurationError, MissingConfigurationError from aiida.common.setup import parse_repository_uri -from aiida.manage import load_config +from aiida.manage import get_config TESTING_MODE = False try: - config = load_config() + config = get_config() except MissingConfigurationError: raise MissingConfigurationError("Please run the AiiDA installation, no config found") diff --git a/aiida/tools/dbexporters/tcod.py b/aiida/tools/dbexporters/tcod.py index 2b2132718b..bc2d2d9a84 100644 --- a/aiida/tools/dbexporters/tcod.py +++ b/aiida/tools/dbexporters/tcod.py @@ -1075,8 +1075,9 @@ def deposit(what, type, author_name=None, author_email=None, url=None, instance. :raises ValueError: if any of the required parameters are not given. """ - from aiida.common.setup import get_property + from aiida.manage import get_config + config = get_config() parameters = {} if not what: @@ -1086,15 +1087,15 @@ def deposit(what, type, author_name=None, author_email=None, url=None, "one of the following: 'published', " "'prepublication' or 'personal'") if not username: - username = get_property('tcod.depositor_username') + username = config.option_get('tcod.depositor_username') if not username: raise ValueError("Depositor username is not supplied") if not password: - parameters['password'] = get_property('tcod.depositor_password') + parameters['password'] = config.option_get('tcod.depositor_password') if not parameters['password']: raise ValueError("Depositor password is not supplied") if not user_email: - user_email = get_property('tcod.depositor_email') + user_email = config.option_get('tcod.depositor_email') if not user_email: raise ValueError("Depositor email is not supplied") @@ -1106,11 +1107,11 @@ def deposit(what, type, author_name=None, author_email=None, url=None, pass elif type in ['prepublication', 'personal']: if not author_name: - author_name = get_property('tcod.depositor_author_name') + author_name = config.option_get('tcod.depositor_author_name') if not author_name: raise ValueError("Author name is not supplied") if not author_email: - author_email = get_property('tcod.depositor_author_email') + author_email = config.option_get('tcod.depositor_author_email') if not author_email: raise ValueError("Author email is not supplied") if not title: diff --git a/aiida/utils/fixtures.py b/aiida/utils/fixtures.py index 1623514f58..a31c9eeca6 100644 --- a/aiida/utils/fixtures.py +++ b/aiida/utils/fixtures.py @@ -46,8 +46,9 @@ from aiida.backends.profile import BACKEND_DJANGO, BACKEND_SQLA from aiida.common import exceptions from aiida.control.postgres import Postgres -from aiida.manage import load_config, get_manager, reset_manager +from aiida.manage import get_manager, reset_manager from aiida.manage.configuration.setup import create_instance_directories +from aiida.manage.configuration.utils import load_config class FixtureError(Exception): @@ -115,7 +116,7 @@ def test_my_stuff(test_data): _test_case = None def __init__(self): - from aiida.manage.configuration import config as config_module + from aiida.manage import configuration from aiida.manage.configuration import settings as configuration_settings self.db_params = {} self.fs_env = {'repo': 'test_repo', 'config': '.aiida'} @@ -134,7 +135,7 @@ def __init__(self): self.__is_running_on_test_db = False self.__is_running_on_test_profile = False self._backup = {} - self._backup['config'] = config_module.CONFIG + self._backup['config'] = configuration.CONFIG self._backup['config_dir'] = configuration_settings.AIIDA_CONFIG_FOLDER self._backup['profile'] = backend_settings.AIIDADB_PROFILE self.__backend = None @@ -183,21 +184,20 @@ def create_profile(self): raise FixtureError('AiiDA dbenv can not be loaded while creating a test profile') if not self.__is_running_on_test_db: self.create_aiida_db() - from aiida.manage.configuration import config as config_module + from aiida.manage import configuration from aiida.manage.configuration import settings as configuration_settings from aiida.control.profile import setup_profile if not self.root_dir: self.create_root_dir() - config_module.CONFIG = None + configuration.CONFIG = None configuration_settings.AIIDA_CONFIG_FOLDER = self.config_dir backend_settings.AIIDADB_PROFILE = None create_instance_directories() + config = load_config(create=True) profile_name = 'test_profile' - print(configuration_settings.AIIDA_CONFIG_FOLDER) - print(config_module.CONFIG) - setup_profile(profile=profile_name, only_config=False, non_interactive=True, **self.profile) + setup_profile(profile_name=profile_name, only_config=False, non_interactive=True, **self.profile) config = load_config() - config.set_default_profile(profile_name) + config.set_default_profile(profile_name).store() self.__is_running_on_test_profile = True self._create_test_case() self.init_db() @@ -370,7 +370,7 @@ def root_dir_ok(self): def destroy_all(self): """Remove all traces of the test run""" - from aiida.manage.configuration import config as config_module + from aiida.manage import configuration from aiida.manage.configuration import settings as configuration_settings if self.root_dir: shutil.rmtree(self.root_dir) @@ -381,7 +381,7 @@ def destroy_all(self): self.__is_running_on_test_db = False self.__is_running_on_test_profile = False if 'config' in self._backup: - config_module.CONFIG = self._backup['config'] + configuration.CONFIG = self._backup['config'] if 'config_dir' in self._backup: configuration_settings.AIIDA_CONFIG_FOLDER = self._backup['config_dir'] if 'profile' in self._backup: diff --git a/aiida/utils/json.py b/aiida/utils/json.py index 9d1b852698..4c68298155 100644 --- a/aiida/utils/json.py +++ b/aiida/utils/json.py @@ -53,21 +53,21 @@ def load(fhandle, **kwargs): For Py2/Py3 compatibility, io.open(filename, 'r', encoding='utf8') should be used. - :raises IOError: if no valid JSON object could be decoded + :raises ValueError: if no valid JSON object could be decoded """ try: return simplejson.load(fhandle, encoding='utf8', **kwargs) except simplejson.errors.JSONDecodeError: - raise IOError + raise ValueError def loads(json_string, **kwargs): """ Deserialise a JSON string. - :raises IOError: if no valid JSON object could be decoded + :raises ValueError: if no valid JSON object could be decoded """ try: return simplejson.loads(json_string, encoding='utf8', **kwargs) except simplejson.errors.JSONDecodeError: - raise IOError + raise ValueError diff --git a/aiida/work/rmq.py b/aiida/work/rmq.py index a1b97e02e9..69766454e2 100644 --- a/aiida/work/rmq.py +++ b/aiida/work/rmq.py @@ -68,9 +68,9 @@ def get_rmq_prefix(): :returns: string prefix for the RMQ communicators """ - from aiida.manage import load_config + from aiida.manage import get_config - profile = load_config().current_profile + profile = get_config().current_profile prefix = profile.rmq_prefix return prefix diff --git a/docs/source/developer_guide/core/internals.rst b/docs/source/developer_guide/core/internals.rst index e4dec81a2b..67c2fb2090 100644 --- a/docs/source/developer_guide/core/internals.rst +++ b/docs/source/developer_guide/core/internals.rst @@ -441,7 +441,7 @@ In case a method is renamed or removed, this is the procedure to follow: - User can disable our warnings (and only those) by using AiiDA properties with:: - verdi devel setproperty warnings.showdeprecations False + verdi config warnings.showdeprecations False Changing the config.json structure ++++++++++++++++++++++++++++++++++ diff --git a/docs/source/verdi/verdi_user_guide.rst b/docs/source/verdi/verdi_user_guide.rst index de453b4b69..7f4c18502c 100644 --- a/docs/source/verdi/verdi_user_guide.rst +++ b/docs/source/verdi/verdi_user_guide.rst @@ -9,6 +9,7 @@ But ``verdi`` is very versatile and provides a wealth of other functionalities; * :ref:`code`: Setup and manage codes. * :ref:`comment`: Inspect, create and manage comments. * :ref:`computer`: Setup and manage computers. +* :ref:`config`: Set, unset and get profile specific or global configuration options. * :ref:`daemon`: Inspect and manage the daemon. * :ref:`data`: Inspect, create and manage data nodes. * :ref:`devel`: Commands for developers. @@ -277,6 +278,18 @@ Setup and manage computer objects. * **update**: [deprecated: use ``verdi computer duplicate`` instead] change configuration of a computer. Works only if the computer node is a disconnected node in the database (has not been used yet) +.. _config: + +``verdi config`` +---------------- +This command allows you to set various configuration options that change how AiiDA works. +The options can be set for specific profiles or globally. +The command works just like ``git config`` does. +Only passing the option name as an argument will print its value, if it is set. +Passing a value as a second argument, will set that value for the given option. +With the ``--unset`` flag an option can be unset and by using ``--global`` the get, set or unset operation is applied globally instead of the default profile. + + .. _daemon: ``verdi daemon`` @@ -355,12 +368,7 @@ Manage ``Data`` nodes. --------------- Commands intended for developers, such as setting :doc:`config properties` and running the unit test suite. - * **delproperty**: delete a property from the configuration - * **describeproperties**: print a list of available configuration properties - * **getproperty**: get the value of a property set for the configuration - * **listproperties**: print the properties defined in the configuration * **run_daemon**: run an instance of the daemon runner in the current interpreter - * **setproperty**: set a property with a given value for the configuration * **tests**: run the unittest suite diff --git a/docs/source/working_with_aiida/scripting.rst b/docs/source/working_with_aiida/scripting.rst index a725e25fb7..c14c5c0acd 100644 --- a/docs/source/working_with_aiida/scripting.rst +++ b/docs/source/working_with_aiida/scripting.rst @@ -19,7 +19,7 @@ modules/classes are already loaded and available:: from aiida.backends.djsite.db import models .. note:: It is possible to customize the shell by adding modules to be loaded - automatically, thanks to the ``verdi devel setproperty verdishell.modules`` command. + automatically, thanks to the ``verdi config verdishell.modules`` command. See :doc:`here<../verdi/properties>` for more information. A further advantage is that bash completion is enabled, allowing to press the diff --git a/docs/source/working_with_aiida/troubleshooting.rst b/docs/source/working_with_aiida/troubleshooting.rst index 0030920cbd..1f353ace70 100644 --- a/docs/source/working_with_aiida/troubleshooting.rst +++ b/docs/source/working_with_aiida/troubleshooting.rst @@ -19,8 +19,8 @@ level of AiiDA messages (and circus messages -- circus is the library that we use to manage the daemon process) using, on the command line, the two following commands:: - verdi devel setproperty logging.circus_loglevel DEBUG - verdi devel setproperty logging.aiida_loglevel DEBUG + verdi config logging.circus_loglevel DEBUG + verdi config logging.aiida_loglevel DEBUG For each profile that runs a daemon, there will be two unique logfiles, one for AiiDA log messages and one from the circus daemonizer. These files can be found