diff --git a/aiida/backends/tests/cmdline/commands/test_config.py b/aiida/backends/tests/cmdline/commands/test_config.py index 5c6c229b5d..ce938b848a 100644 --- a/aiida/backends/tests/cmdline/commands/test_config.py +++ b/aiida/backends/tests/cmdline/commands/test_config.py @@ -81,3 +81,22 @@ def test_config_option_unset(self): result = self.cli_runner.invoke(cmd_verdi.verdi, options) self.assertClickSuccess(result) self.assertEqual(result.output, '') + + @with_temporary_config_instance + def test_config_option_set_global_only(self): + """Test that `global_only` options are only set globally even if the `--global` flag is not set.""" + config = get_config() + option_name = 'user.email' + option_value = 'some@email.com' + + 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) + + # Check that the current profile name is not in the output + self.assertClickSuccess(result) + self.assertIn(option_value, result.output.strip()) + self.assertNotIn(config.current_profile.name, result.output.strip()) diff --git a/aiida/backends/tests/manage/configuration/test_config.py b/aiida/backends/tests/manage/configuration/test_config.py index 3ced5fb87a..3e4c3b8970 100644 --- a/aiida/backends/tests/manage/configuration/test_config.py +++ b/aiida/backends/tests/manage/configuration/test_config.py @@ -311,6 +311,22 @@ def test_option(self): config.option_set(option_name, option_value) self.assertEqual(config.option_get(option_name, scope=None, default=False), option_value) + def test_option_global_only(self): + """Test that `global_only` options are only set globally even if a profile specific scope is set.""" + option_name = 'user.email' + option_value = 'some@email.com' + + config = Config(self.config_filepath, self.config_dictionary) + + # Setting an option globally should be fine + config.option_set(option_name, option_value, scope=None) + self.assertEqual(config.option_get(option_name, scope=None, default=False), option_value) + + # Setting an option profile specific should actually not set it on the profile since it is `global_only` + config.option_set(option_name, option_value, scope=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), option_value) + def test_store(self): """Test that the store method writes the configuration properly to disk.""" config = Config(self.config_filepath, self.config_dictionary) diff --git a/aiida/cmdline/commands/cmd_config.py b/aiida/cmdline/commands/cmd_config.py index a27329db01..40a821100c 100644 --- a/aiida/cmdline/commands/cmd_config.py +++ b/aiida/cmdline/commands/cmd_config.py @@ -30,6 +30,9 @@ def verdi_config(ctx, option, value, globally, unset): config = ctx.obj.config profile = ctx.obj.profile + if option.global_only: + globally = True + # Define the string that determines the scope: for specific profile or globally scope = profile.name if (not globally and profile) else None scope_text = 'for {}'.format(profile.name) if (not globally and profile) else 'globally' diff --git a/aiida/manage/configuration/__init__.py b/aiida/manage/configuration/__init__.py index 5b832ec7d0..86266d0b4f 100644 --- a/aiida/manage/configuration/__init__.py +++ b/aiida/manage/configuration/__init__.py @@ -62,7 +62,7 @@ def get_config_option(option_name): try: config = get_config() except exceptions.ConfigurationError: - value = option.default + value = option.default if option.default is not options.NO_DEFAULT else None else: if config.current_profile: # Try to get the option for the profile, but do not return the option default diff --git a/aiida/manage/configuration/config.py b/aiida/manage/configuration/config.py index 9da7bd5a54..4a71b137fd 100644 --- a/aiida/manage/configuration/config.py +++ b/aiida/manage/configuration/config.py @@ -241,7 +241,7 @@ def option_set(self, option_name, option_value, scope=None): """ option, parsed_value = parse_option(option_name, option_value) - if scope is not None: + if not option.global_only and scope is not None: dictionary = self._get_profile_dictionary(scope) else: dictionary = self.dictionary @@ -283,7 +283,10 @@ def option_get(self, option_name, scope=None, default=True): else: dictionary = self.dictionary - return dictionary.get(option.key, option.default if default else None) + # Default value is `None` unless `default=True` and the `option.default` is not `NO_DEFAULT` + default_value = option.default if default and option.default is not NO_DEFAULT else None + + return dictionary.get(option.key, default_value) def store(self): """Write the current config to file.""" diff --git a/aiida/manage/configuration/options.py b/aiida/manage/configuration/options.py index 2b7ab17c65..d3442a16f7 100644 --- a/aiida/manage/configuration/options.py +++ b/aiida/manage/configuration/options.py @@ -17,16 +17,12 @@ __all__ = ('get_option', 'get_option_names', 'parse_option') - -class NO_DEFAULT_PLACEHOLDER(object): # pylint: disable=too-few-public-methods,invalid-name,useless-object-inheritance - """A dummy class to serve as a marker for no default being specified in the `get_option` function.""" - - -NO_DEFAULT = NO_DEFAULT_PLACEHOLDER() +NO_DEFAULT = () 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']) +Option = collections.namedtuple('Option', + ['name', 'key', 'valid_type', 'valid_values', 'default', 'description', 'global_only']) def get_option(option_name): @@ -96,6 +92,7 @@ def parse_option(option_name, option_value): 'valid_values': None, 'default': 1, 'description': 'The polling interval in seconds to be used by process runners', + 'global_only': False, }, 'daemon.timeout': { 'key': 'daemon_timeout', @@ -103,6 +100,7 @@ def parse_option(option_name, option_value): 'valid_values': None, 'default': DEFAULT_DAEMON_TIMEOUT, 'description': 'The timeout in seconds for calls to the circus client', + 'global_only': False, }, 'verdi.shell.auto_import': { 'key': 'verdi_shell_auto_import', @@ -110,6 +108,7 @@ def parse_option(option_name, option_value): 'valid_values': None, 'default': '', 'description': 'Additional modules/functions/classes to be automatically loaded in `verdi shell`', + 'global_only': False, }, 'logging.aiida_loglevel': { 'key': 'logging_aiida_log_level', @@ -117,6 +116,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'REPORT', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `aiida` logger', + 'global_only': False, }, 'logging.db_loglevel': { 'key': 'logging_db_log_level', @@ -124,6 +124,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'REPORT', 'description': 'Minimum level to log to the DbLog table', + 'global_only': False, }, 'logging.tornado_loglevel': { 'key': 'logging_tornado_log_level', @@ -131,6 +132,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `tornado` logger', + 'global_only': False, }, 'logging.plumpy_loglevel': { 'key': 'logging_plumpy_log_level', @@ -138,6 +140,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `plumpy` logger', + 'global_only': False, }, 'logging.kiwipy_loglevel': { 'key': 'logging_kiwipy_log_level', @@ -145,6 +148,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `kiwipy` logger', + 'global_only': False, }, 'logging.paramiko_loglevel': { 'key': 'logging_paramiko_log_level', @@ -152,6 +156,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `paramiko` logger', + 'global_only': False, }, 'logging.alembic_loglevel': { 'key': 'logging_alembic_log_level', @@ -159,6 +164,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `alembic` logger', + 'global_only': False, }, 'logging.sqlalchemy_loglevel': { 'key': 'logging_sqlalchemy_loglevel', @@ -166,6 +172,7 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'WARNING', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `sqlalchemy` logger', + 'global_only': False, }, 'logging.circus_loglevel': { 'key': 'logging_circus_log_level', @@ -173,6 +180,39 @@ def parse_option(option_name, option_value): 'valid_values': VALID_LOG_LEVELS, 'default': 'INFO', 'description': 'Minimum level to log to daemon log and the `DbLog` table for the `circus` logger', + 'global_only': False, + }, + 'user.email': { + 'key': 'user_email', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Default user email to use when creating new profiles.', + 'global_only': True, + }, + 'user.first_name': { + 'key': 'user_first_name', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Default user first name to use when creating new profiles.', + 'global_only': True, + }, + 'user.last_name': { + 'key': 'user_last_name', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Default user last name to use when creating new profiles.', + 'global_only': True, + }, + 'user.institution': { + 'key': 'user_institution', + 'valid_type': 'string', + 'valid_values': None, + 'default': NO_DEFAULT, + 'description': 'Default user institution to use when creating new profiles.', + 'global_only': True, }, 'warnings.showdeprecations': { 'key': 'show_deprecations', @@ -180,5 +220,6 @@ def parse_option(option_name, option_value): 'valid_values': None, 'default': True, 'description': 'Boolean whether to print AiiDA deprecation warnings', + 'global_only': False, }, }