From 759477dd065c3dbb25d5d69b431b7d87c35effe0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Thu, 9 Nov 2023 17:08:33 +0100 Subject: [PATCH 1/5] ORM: Add the `User.is_default` property This is a useful shortcut to determine whether a `User` instance is the current default user. The previous way of determing this was to retrieve the default user from the collection `User.collection.get_default()` and manually compare it with the `User` instance. --- aiida/orm/users.py | 10 +++++++++- aiida/transports/cli.py | 3 +-- tests/orm/test_users.py | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/orm/test_users.py diff --git a/aiida/orm/users.py b/aiida/orm/users.py index 3f2b49c288..fab3517d58 100644 --- a/aiida/orm/users.py +++ b/aiida/orm/users.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, Optional, Tuple, Type from aiida.common import exceptions -from aiida.common.lang import classproperty from aiida.manage import get_manager from . import entities @@ -84,6 +83,15 @@ def normalize_email(email: str) -> str: email = '@'.join([email_name, domain_part.lower()]) return email + @property + def is_default(self) -> bool: + """Return whether the user is the default user. + + :returns: Boolean, ``True`` if the user is the default, ``False`` otherwise. + """ + default_user = self.collection.get_default() + return default_user is not None and self.pk == default_user.pk + @property def email(self) -> str: return self._backend_entity.email diff --git a/aiida/transports/cli.py b/aiida/transports/cli.py index 037c0391d9..1d8daec205 100644 --- a/aiida/transports/cli.py +++ b/aiida/transports/cli.py @@ -17,7 +17,6 @@ from aiida.cmdline.utils import echo from aiida.cmdline.utils.decorators import with_dbenv from aiida.common.exceptions import NotExistent -from aiida.manage import get_manager TRANSPORT_PARAMS = [] @@ -40,7 +39,7 @@ def configure_computer_main(computer, user, **kwargs): user = user or orm.User.collection.get_default() echo.echo_report(f'Configuring computer {computer.label} for user {user.email}.') - if user.email != get_manager().get_profile().default_user_email: + if not user.is_default: echo.echo_report('Configuring different user, defaults may not be appropriate.') computer.configure(user=user, **kwargs) diff --git a/tests/orm/test_users.py b/tests/orm/test_users.py new file mode 100644 index 0000000000..4c82d43fa0 --- /dev/null +++ b/tests/orm/test_users.py @@ -0,0 +1,18 @@ +# -*- 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 :mod:`aiida.orm.users`.""" +from aiida.orm.users import User + + +def test_user_is_default(default_user): + """Test the :meth:`aiida.orm.users.User.is_default` property.""" + assert default_user.is_default + user = User('other@localhost').store() + assert not user.is_default From bd7fc2f97ed917cbc168cab24186d35497463d27 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Thu, 9 Nov 2023 17:26:04 +0100 Subject: [PATCH 2/5] CLI: Improve the formatting of `verdi user list` Uses the `tabulate` package to create a nicely formatted table as is used in many other `verdi` commands already. The results are ordered by the users emails. --- aiida/cmdline/commands/cmd_user.py | 22 ++++++++++++++-------- aiida/cmdline/utils/echo.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/aiida/cmdline/commands/cmd_user.py b/aiida/cmdline/commands/cmd_user.py index 28b4173e4a..210a35eef3 100644 --- a/aiida/cmdline/commands/cmd_user.py +++ b/aiida/cmdline/commands/cmd_user.py @@ -8,7 +8,6 @@ # For further information please visit http://www.aiida.net # ########################################################################### """`verdi user` command.""" - from functools import partial import click @@ -58,15 +57,22 @@ def user_list(): """Show a list of all users.""" from aiida.orm import User - default_user = User.collection.get_default() + table = [] + + for user in sorted(User.collection.all(), key=lambda user: user.email): + row = ['*' if user.is_default else '', user.email, user.first_name, user.last_name, user.institution] + if user.is_default: + table.append(list(map(echo.highlight_string, row))) + else: + table.append(row) - if default_user is None: - echo.echo_warning('no default user has been configured') + echo.echo_tabulate(table, headers=['', 'Email', 'First name', 'Last name', 'Institution']) + echo.echo('') - attributes = ['email', 'first_name', 'last_name'] - sort = lambda user: user.email - highlight = lambda x: x.email == default_user.email if default_user else None - echo.echo_formatted_list(User.collection.all(), attributes, sort=sort, highlight=highlight) + if User.collection.get_default() is None: + echo.echo_warning('No default user has been configured') + else: + echo.echo_report('The user highlighted and marked with a * is the default user.') @verdi_user.command('configure') diff --git a/aiida/cmdline/utils/echo.py b/aiida/cmdline/utils/echo.py index 5cef7732dc..887fcba732 100644 --- a/aiida/cmdline/utils/echo.py +++ b/aiida/cmdline/utils/echo.py @@ -46,6 +46,18 @@ class ExitCode(enum.IntEnum): } +def highlight_string(string: str, color: str = 'highlight') -> str: + """Highlight a string with a certain color. + + Uses ``click.style`` to highlight the string. + + :param string: The string to highlight. + :param color: The color to use. + :returns: The highlighted string. + """ + return click.style(string, fg=COLORS[color]) + + def echo(message: Any, fg: Optional[str] = None, bold: bool = False, nl: bool = True, err: bool = False) -> None: """Log a message to the cmdline logger. From 0463e577c3a53eff4737700603ef6cf4513e9503 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 10 Nov 2023 01:07:47 +0100 Subject: [PATCH 3/5] Manager: Add the `set_default_user_email` Each profile can define which user in its storage backend should be considered the default. This is necessary because each ORM entity, when created, needs to specify a `User` object and we don't want the user to always have to explicitly define this manuallly. The default user for a profile is stored in the configuration file by the email of the `User` object. However, in a loaded storage backend, this default user is also cached as the `User` object. This means that when the default user is changed, it should be changed both in the configuration file, but if a storage backend is loaded, the cache should also be invalidated, such that the next time the default user is requested, the new one is properly loaded from the database. Since this change affects both the configuration as well as the currently loaded storage, the `set_default_user_email` is added to the `Manager` class, since that controls both. It calls through to the same method on the `Config` class, which is responsible for updating the `Config` instance in memory and writing the changes to disk. Then the manager resets the default user on the storage backend, if any is loaded. The `verdi user set-default` command is updated to use the new method. A test is added for the command, which didn't exist yet. The command is updated to use `Manager.set_default_user_email` even though it could use `Config.set_default_user_email` since the Python interpreter will shut down immediately after anyway. However, the test would fail if the latter would be used, since the loaded storage backend would not have been updated, which is used by `User.collection.get_default()`. This demonstrates why in active Python interpreters only the method on the manager should be used. A warning is added to the docstring on the configuration class. --- aiida/cmdline/commands/cmd_setup.py | 3 +- aiida/cmdline/commands/cmd_user.py | 16 +- aiida/manage/configuration/__init__.py | 7 +- aiida/manage/configuration/config.py | 15 ++ aiida/manage/manager.py | 10 ++ aiida/orm/implementation/storage_backend.py | 8 + tests/cmdline/commands/test_user.py | 155 +++++++++----------- tests/manage/configuration/test_config.py | 12 ++ 8 files changed, 123 insertions(+), 103 deletions(-) diff --git a/aiida/cmdline/commands/cmd_setup.py b/aiida/cmdline/commands/cmd_setup.py index 3f46b83e7d..b715cf6fd5 100644 --- a/aiida/cmdline/commands/cmd_setup.py +++ b/aiida/cmdline/commands/cmd_setup.py @@ -108,8 +108,7 @@ def setup( ) if created: user.store() - profile.default_user_email = user.email - config.update_profile(profile) + config.set_default_user_email(profile, user.email) # store the updated configuration config.store() diff --git a/aiida/cmdline/commands/cmd_user.py b/aiida/cmdline/commands/cmd_user.py index 210a35eef3..66e62ab289 100644 --- a/aiida/cmdline/commands/cmd_user.py +++ b/aiida/cmdline/commands/cmd_user.py @@ -17,19 +17,6 @@ from aiida.cmdline.utils import decorators, echo -def set_default_user(profile, user): - """Set the user as the default user for the given profile. - - :param profile: the profile - :param user: the user - """ - from aiida.manage.configuration import get_config - config = get_config() - profile.default_user_email = user.email - config.update_profile(profile) - config.store() - - def get_user_attribute_default(attribute, ctx): """Return the default value for the given attribute of the user passed in the context. @@ -146,5 +133,6 @@ def user_configure(ctx, user, first_name, last_name, institution, set_default): @decorators.with_dbenv() def user_set_default(ctx, user): """Set a user as the default user for the profile.""" - set_default_user(ctx.obj.profile, user) + from aiida.manage import get_manager + get_manager().set_default_user_email(ctx.obj.profile, user.email) echo.echo_success(f'set `{user.email}` as the new default user for profile `{ctx.obj.profile.name}`') diff --git a/aiida/manage/configuration/__init__.py b/aiida/manage/configuration/__init__.py index 4a5873c88e..f5d35ebb6c 100644 --- a/aiida/manage/configuration/__init__.py +++ b/aiida/manage/configuration/__init__.py @@ -213,10 +213,9 @@ def create_profile( with profile_context(profile.name, allow_switch=True): user = User(email=email, first_name=first_name, last_name=last_name, institution=institution).store() - profile.default_user_email = user.email - - config.update_profile(profile) - config.store() + # We can safely use ``Config.set_default_user_email`` here instead of ``Manager.set_default_user_email`` since + # the storage backend of this new profile is not loaded yet. + config.set_default_user_email(profile, user.email) return profile diff --git a/aiida/manage/configuration/config.py b/aiida/manage/configuration/config.py index 89014670bb..5ecc64bce2 100644 --- a/aiida/manage/configuration/config.py +++ b/aiida/manage/configuration/config.py @@ -600,6 +600,21 @@ def set_default_profile(self, name, overwrite=False): self._default_profile = name return self + def set_default_user_email(self, profile: Profile, user_email: str) -> None: + """Set the default user for the given profile. + + .. warning:: + + This does not update the cached default user on the storage backend associated with the profile. To do so, + use :meth:`aiida.manage.manager.Manager.set_default_user_email` instead. + + :param profile: The profile to update. + :param user_email: The email of the user to set as the default user. + """ + profile.default_user_email = user_email + self.update_profile(profile) + self.store() + @property def options(self): return self._options diff --git a/aiida/manage/manager.py b/aiida/manage/manager.py index 3e6959d064..3e33f822c3 100644 --- a/aiida/manage/manager.py +++ b/aiida/manage/manager.py @@ -179,6 +179,16 @@ def unload_profile(self) -> None: self.reset_profile() self._profile = None + def set_default_user_email(self, profile: 'Profile', user_email: str) -> None: + """Set the default user for the given profile. + + :param profile: The profile to update. + :param user_email: The email of the user to set as the default user. + """ + self.get_config().set_default_user_email(profile, user_email) + if self.profile_storage_loaded: + self.get_profile_storage().reset_default_user() + @property def profile_storage_loaded(self) -> bool: """Return whether a storage backend has been loaded. diff --git a/aiida/orm/implementation/storage_backend.py b/aiida/orm/implementation/storage_backend.py index 54f2b6b803..d16bccc901 100644 --- a/aiida/orm/implementation/storage_backend.py +++ b/aiida/orm/implementation/storage_backend.py @@ -141,7 +141,15 @@ def _clear(self) -> None: .. warning:: This is a destructive operation, and should only be used for testing purposes. """ from aiida.orm.autogroup import AutogroupManager + self.reset_default_user() self._autogroup = AutogroupManager(self) + + def reset_default_user(self) -> None: + """Reset the default user. + + This should be done when the default user of the storage backend is changed on the corresponding profile because + the old default user is cached on this instance. + """ self._default_user = None @property diff --git a/tests/cmdline/commands/test_user.py b/tests/cmdline/commands/test_user.py index d10ca0f3c1..6d49cf5393 100644 --- a/tests/cmdline/commands/test_user.py +++ b/tests/cmdline/commands/test_user.py @@ -7,92 +7,81 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### +# pylint: disable=redefined-outer-name """Tests for `verdi user`.""" +import itertools +import secrets + import pytest from aiida import orm from aiida.cmdline.commands import cmd_user -USER_1 = { # pylint: disable=invalid-name - 'email': 'testuser1@localhost', - 'first_name': 'Max', - 'last_name': 'Mueller', - 'institution': 'Testing Instiute' -} -USER_2 = { # pylint: disable=invalid-name - 'email': 'testuser2@localhost', - 'first_name': 'Sabine', - 'last_name': 'Garching', - 'institution': 'Second testing instiute' -} - - -class TestVerdiUserCommand: - """Test verdi user.""" - - @pytest.fixture(autouse=True) - def init_profile(self): # pylint: disable=unused-argument - """Initialize the profile.""" - # pylint: disable=attribute-defined-outside-init - created, user = orm.User.collection.get_or_create(email=USER_1['email']) - for key, value in USER_1.items(): - if key != 'email': - setattr(user, key, value) - if created: - orm.User(**USER_1).store() - - def test_user_list(self, run_cli_command): - """Test `verdi user list`.""" - from aiida.cmdline.commands.cmd_user import user_list as list_user - - result = run_cli_command(list_user, []) - assert USER_1['email'] in result.output - - def test_user_create(self, run_cli_command): - """Create a new user with `verdi user configure`.""" - cli_options = [ - '--email', - USER_2['email'], - '--first-name', - USER_2['first_name'], - '--last-name', - USER_2['last_name'], - '--institution', - USER_2['institution'], - '--set-default', - ] - - result = run_cli_command(cmd_user.user_configure, cli_options) - assert USER_2['email'] in result.output - assert 'created' in result.output - assert 'updated' not in result.output - - user_obj = orm.User.collection.get(email=USER_2['email']) - for key, val in USER_2.items(): - assert val == getattr(user_obj, key) - - def test_user_update(self, run_cli_command): - """Reconfigure an existing user with `verdi user configure`.""" - email = USER_1['email'] - - cli_options = [ - '--email', - USER_1['email'], - '--first-name', - USER_2['first_name'], - '--last-name', - USER_2['last_name'], - '--institution', - USER_2['institution'], - '--set-default', - ] - - result = run_cli_command(cmd_user.user_configure, cli_options) - assert email in result.output - assert 'updated' in result.output - assert 'created' not in result.output - - # Check it's all been changed to user2's attributes except the email - for key, _ in USER_2.items(): - if key != 'email': - setattr(cmd_user, key, USER_1[key]) + +@pytest.fixture +def create_user(): + """Create a dictionary with random attributes for a new user.""" + return { + 'email': f'{secrets.token_hex(2)}@localhost', + 'first_name': secrets.token_hex(2), + 'last_name': secrets.token_hex(2), + 'institution': secrets.token_hex(2), + } + + +@pytest.mark.usefixtures('aiida_profile') +def test_user_list(run_cli_command): + """Test `verdi user list`.""" + default_user = orm.User.collection.get_default() + result = run_cli_command(cmd_user.user_list) + assert default_user.email in result.output + + +@pytest.mark.usefixtures('aiida_profile') +def test_user_configure_create(run_cli_command, create_user): + """Create a new user with `verdi user configure`.""" + new_user = create_user + options = list( + itertools.chain(*zip(['--email', '--first-name', '--last-name', '--institution'], list(new_user.values()))) + ) + + result = run_cli_command(cmd_user.user_configure, options) + assert new_user['email'] in result.output + assert 'created' in result.output + assert 'updated' not in result.output + + user = orm.User.collection.get(email=new_user['email']) + for key, val in new_user.items(): + assert val == getattr(user, key) + + +@pytest.mark.usefixtures('aiida_profile') +def test_user_configure_update(run_cli_command, create_user): + """Reconfigure an existing user with `verdi user configure`.""" + new_user = create_user + default_user = orm.User.collection.get_default() + new_user['email'] = default_user + options = list( + itertools.chain(*zip(['--email', '--first-name', '--last-name', '--institution'], list(new_user.values()))) + ) + + result = run_cli_command(cmd_user.user_configure, options) + assert default_user.email in result.output + assert 'updated' in result.output + assert 'created' not in result.output + + for key, val in new_user.items(): + if key == 'email': + continue + assert val == getattr(default_user, key) + + +@pytest.mark.usefixtures('aiida_profile') +def test_set_default(run_cli_command, create_user): + """Reconfigure an existing user with `verdi user configure`.""" + new_user = orm.User(**create_user).store() + assert orm.User.collection.get_default().email != new_user.email + + result = run_cli_command(cmd_user.user_set_default, [new_user.email]) + assert f'set `{new_user.email}` as the new default user' in result.output + assert orm.User.collection.get_default().email == new_user.email diff --git a/tests/manage/configuration/test_config.py b/tests/manage/configuration/test_config.py index 35786ff683..13f9bd54b7 100644 --- a/tests/manage/configuration/test_config.py +++ b/tests/manage/configuration/test_config.py @@ -274,6 +274,18 @@ def test_default_profile(empty_config, profile_factory): assert config.default_profile_name == alternative_profile_name +def test_set_default_user_email(config_with_profile): + """Test the :meth:`aiida.manage.configuration.config.Config.set_default_user_email`.""" + config = config_with_profile + profile = config.get_profile() + default_user_email = profile.default_user_email + default_user_email_new = uuid.uuid4().hex + assert default_user_email != default_user_email_new + config.set_default_user_email(profile, default_user_email_new) + assert profile.default_user_email == default_user_email_new + assert config.get_profile(profile.name).default_user_email == default_user_email_new + + def test_profiles(config_with_profile, profile_factory): """Test the properties related to retrieving, creating, updating and removing profiles.""" config = config_with_profile From 46cb3cec434235d0447ad0d597b6e4e8f1748221 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 10 Nov 2023 01:38:45 +0100 Subject: [PATCH 4/5] CLI: Reuse options in `verdi user configure` from setup This way it is guaranteed that the same types are being used, which were actually different. The `--set-default` flag now also gets its default from the current value on the selected user, just as is done for the other properties. --- aiida/cmdline/commands/cmd_user.py | 67 ++++++----------------------- tests/cmdline/commands/test_user.py | 2 +- 2 files changed, 13 insertions(+), 56 deletions(-) diff --git a/aiida/cmdline/commands/cmd_user.py b/aiida/cmdline/commands/cmd_user.py index 66e62ab289..8f8dbeff3c 100644 --- a/aiida/cmdline/commands/cmd_user.py +++ b/aiida/cmdline/commands/cmd_user.py @@ -8,31 +8,14 @@ # For further information please visit http://www.aiida.net # ########################################################################### """`verdi user` command.""" -from functools import partial - import click from aiida.cmdline.commands.cmd_verdi import verdi from aiida.cmdline.params import arguments, options, types +from aiida.cmdline.params.options.commands import setup as options_setup from aiida.cmdline.utils import decorators, echo -def get_user_attribute_default(attribute, ctx): - """Return the default value for the given attribute of the user passed in the context. - - :param attribute: attribute for which to get the current value - :param ctx: click context which should contain the selected user - :return: user attribute default value if set, or None - """ - default = getattr(ctx.params['user'], attribute) - - # None or empty string means there is no default - if not default: - return None - - return default - - @verdi.group('user') def verdi_user(): """Inspect and manage users.""" @@ -71,57 +54,31 @@ def user_list(): type=types.UserParamType(create=True), cls=options.interactive.InteractiveOption ) -@click.option( - '--first-name', - prompt='First name', - help='First name of the user.', - type=click.STRING, - contextual_default=partial(get_user_attribute_default, 'first_name'), - cls=options.interactive.InteractiveOption -) -@click.option( - '--last-name', - prompt='Last name', - help='Last name of the user.', - type=click.STRING, - contextual_default=partial(get_user_attribute_default, 'last_name'), - cls=options.interactive.InteractiveOption -) -@click.option( - '--institution', - prompt='Institution', - help='Institution of the user.', - type=click.STRING, - contextual_default=partial(get_user_attribute_default, 'institution'), - cls=options.interactive.InteractiveOption -) +@options_setup.SETUP_USER_FIRST_NAME(contextual_default=lambda ctx: ctx.params['user'].first_name) +@options_setup.SETUP_USER_LAST_NAME(contextual_default=lambda ctx: ctx.params['user'].last_name) +@options_setup.SETUP_USER_INSTITUTION(contextual_default=lambda ctx: ctx.params['user'].institution) @click.option( '--set-default', prompt='Set as default?', help='Set the user as the default user for the current profile.', is_flag=True, - cls=options.interactive.InteractiveOption + cls=options.interactive.InteractiveOption, + contextual_default=lambda ctx: ctx.params['user'].is_default ) @click.pass_context @decorators.with_dbenv() -def user_configure(ctx, user, first_name, last_name, institution, set_default): +def user_configure(ctx, user, first_name, last_name, institution, set_default): # pylint: disable=too-many-arguments """Configure a new or existing user. An e-mail address is used as the user name. """ - # pylint: disable=too-many-arguments - if first_name is not None: - user.first_name = first_name - if last_name is not None: - user.last_name = last_name - if institution is not None: - user.institution = institution - action = 'updated' if user.is_stored else 'created' - + user.first_name = first_name + user.last_name = last_name + user.institution = institution user.store() - echo.echo_success(f'{user.email} successfully {action}') + echo.echo_success(f'Successfully {action} `{user.email}`.') if set_default: ctx.invoke(user_set_default, user=user) @@ -135,4 +92,4 @@ def user_set_default(ctx, user): """Set a user as the default user for the profile.""" from aiida.manage import get_manager get_manager().set_default_user_email(ctx.obj.profile, user.email) - echo.echo_success(f'set `{user.email}` as the new default user for profile `{ctx.obj.profile.name}`') + echo.echo_success(f'Set `{user.email}` as the default user for profile `{ctx.obj.profile.name}.`') diff --git a/tests/cmdline/commands/test_user.py b/tests/cmdline/commands/test_user.py index 6d49cf5393..b31a481cca 100644 --- a/tests/cmdline/commands/test_user.py +++ b/tests/cmdline/commands/test_user.py @@ -83,5 +83,5 @@ def test_set_default(run_cli_command, create_user): assert orm.User.collection.get_default().email != new_user.email result = run_cli_command(cmd_user.user_set_default, [new_user.email]) - assert f'set `{new_user.email}` as the new default user' in result.output + assert f'Set `{new_user.email}` as the default user' in result.output assert orm.User.collection.get_default().email == new_user.email From 2bb6128c68a8788ebb892f79b720d8c123bdbafb Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Fri, 10 Nov 2023 02:27:52 +0100 Subject: [PATCH 5/5] CLI: Set defaults for user details in profile setup The user options `--first-name`, `--last-name` and `--institution` in the `verdi quicksetup/setup` commands were recently made required but did not provide a default. This would make creating profiles significantly more complex than always needed. For simple test and demo profiles the user might not necessarily care about these user details. Here we add defaults for these options. Even for production profiles this is a sensible approach since these details can always be freely updated later on with `verdi user configure`. This is also the reason that the `--email` does not provide a default because that can not be changed later on. --- aiida/cmdline/params/options/commands/setup.py | 6 +++--- tests/cmdline/commands/test_setup.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/aiida/cmdline/params/options/commands/setup.py b/aiida/cmdline/params/options/commands/setup.py index 88d7216072..1c66b27793 100644 --- a/aiida/cmdline/params/options/commands/setup.py +++ b/aiida/cmdline/params/options/commands/setup.py @@ -186,21 +186,21 @@ def get_quicksetup_password(ctx, param, value): # pylint: disable=unused-argume SETUP_USER_FIRST_NAME = options.USER_FIRST_NAME.clone( prompt='First name', - default=functools.partial(get_config_option, 'autofill.user.first_name'), + default=lambda: get_config_option('autofill.user.first_name') or 'John', required=True, cls=options.interactive.InteractiveOption ) SETUP_USER_LAST_NAME = options.USER_LAST_NAME.clone( prompt='Last name', - default=functools.partial(get_config_option, 'autofill.user.last_name'), + default=lambda: get_config_option('autofill.user.last_name') or 'Doe', required=True, cls=options.interactive.InteractiveOption ) SETUP_USER_INSTITUTION = options.USER_INSTITUTION.clone( prompt='Institution', - default=functools.partial(get_config_option, 'autofill.user.institution'), + default=lambda: get_config_option('autofill.user.institution') or 'Unknown', required=True, cls=options.interactive.InteractiveOption ) diff --git a/tests/cmdline/commands/test_setup.py b/tests/cmdline/commands/test_setup.py index 4818a220ca..fc4fb9a637 100644 --- a/tests/cmdline/commands/test_setup.py +++ b/tests/cmdline/commands/test_setup.py @@ -84,6 +84,22 @@ def test_quicksetup(self, tmp_path): backend = profile.storage_cls(profile) assert backend.get_global_variable('repository|uuid') == backend.get_repository().uuid + def test_quicksetup_default_user(self, tmp_path): + """Test `verdi quicksetup` and ensure that user details (apart from the email) are optional.""" + profile_name = 'testing-default-user-details' + user_email = 'some@email.com' + + options = [ + '--non-interactive', '--profile', profile_name, '--email', user_email, '--db-port', + self.pg_test.dsn['port'], '--db-backend', self.storage_backend_name, '--repository', + str(tmp_path) + ] + + self.cli_runner(cmd_setup.quicksetup, options, use_subprocess=False) + + config = configuration.get_config() + assert profile_name in config.profile_names + def test_quicksetup_from_config_file(self, tmp_path): """Test `verdi quicksetup` from configuration file.""" with tempfile.NamedTemporaryFile('w') as handle: