Skip to content

Commit

Permalink
Add a configuration default user configurable through verdi config
Browse files Browse the repository at this point in the history
This will be required to implement a smarter `verdi quicksetup` and
`verdi setup` that can read these configuration options to pre-populate
the corresponding fields. This will simplify the setup procedure
significantly.
  • Loading branch information
sphuber committed Apr 9, 2019
1 parent 4fa10bc commit 7d1b1d0
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 10 deletions.
19 changes: 19 additions & 0 deletions aiida/backends/tests/cmdline/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
16 changes: 16 additions & 0 deletions aiida/backends/tests/manage/configuration/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions aiida/cmdline/commands/cmd_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion aiida/manage/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions aiida/manage/configuration/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down
55 changes: 48 additions & 7 deletions aiida/manage/configuration/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -96,89 +92,134 @@ 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',
'valid_type': 'int',
'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',
'valid_type': 'string',
'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',
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'global_only': False,
},
'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',
'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',
'valid_type': 'bool',
'valid_values': None,
'default': True,
'description': 'Boolean whether to print AiiDA deprecation warnings',
'global_only': False,
},
}

0 comments on commit 7d1b1d0

Please sign in to comment.