Skip to content

Commit

Permalink
CLI: Add the command verdi presto
Browse files Browse the repository at this point in the history
This command aims making setting up a new profile as easy as possible.
It intentionally provides a limited amount of options to customize the
profile and by default does not require any options to be specified at
all. For full control, the command `verdi profile setup` should be used
instead.

The main goal for this command is to setup a profile with minimal
requirements to make it easy to install AiiDA and get started as quickly
as possible. Therefore, by default, the created profile uses the
`core.sqlite_dos` storage plugin which does not require any services,
such as PostgreSQL and RabbitMQ are not required. This _does_ mean,
however, that not all functionality of AiiDA is available, most notably
running the daemon and submitting processes to said daemon.

The command performs the following actions:

* Create a new profile that is set as the new default
* Create a default user for the profile (can be configured through options)
* Set up the localhost as a `Computer` and configure it
* Set a number of configuration options with sensible defaults

In the future, it may be possible to incorporate the functionality of
the `verdi quicksetup` command that automatically creates the role and
database in PostgreSQL necessary for a profile with the `core.psql_dos`
storage plugin. This would allow `verdi quicksetup` to be retired
leaving just `verdi presto` and `verdi profile setup` to provide all the
profile setup needs.
  • Loading branch information
sphuber committed Apr 27, 2024
1 parent 5460a04 commit 65f53e9
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 4 deletions.
37 changes: 37 additions & 0 deletions docs/source/reference/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,43 @@ Below is a list with all available subcommands.
list Display a list of all available plugins.
.. _reference:command-line:verdi-presto:

``verdi presto``
----------------

.. code:: console
Usage: [OPTIONS]
Set up a new profile as automatically as possible.
This command aims making setting up a new profile as easy as possible. It intentionally
provides a limited amount of options to customize the profile and by default does not
require any options to be specified at all. For full control, please use the command
`verdi profile setup` instead.
The main goal for this command is to setup a profile with minimal requirements to make
it easy to install AiiDA and get started as quickly as possible. Therefore, by default,
the created profile uses the `core.sqlite_dos` storage plugin which does not require any
services, such as PostgreSQL and RabbitMQ are not required. This _does_ mean, however,
that not all functionality of AiiDA is available, most notably running the daemon and
submitting processes to said daemon.
The command performs the following actions:
* Create a new profile that is set as the new default
* Create a default user for the profile (email can be configured through options)
* Set up the localhost as a `Computer` and configure it
* Set a number of configuration options with sensible defaults
Options:
--profile-name TEXT The name of the profile. By default generates a name that does not
already exist and starts with `presto`. [default: (dynamic)]
--email TEXT The email of the default user. [default: aiida@localhost]
--help Show this message and exit.
.. _reference:command-line:verdi-process:

``verdi process``
Expand Down
21 changes: 21 additions & 0 deletions src/aiida/brokers/rabbitmq/defaults.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Defaults related to RabbitMQ."""

from __future__ import annotations

import typing as t

from aiida.common.extendeddicts import AttributeDict

__all__ = ('BROKER_DEFAULTS',)
Expand All @@ -19,3 +23,20 @@
'heartbeat': 600,
}
)


def detect_rabbitmq_config() -> dict[str, t.Any] | None:
"""Try to connect to a RabbitMQ server with the default connection parameters.
:returns: The connection parameters if the RabbitMQ server was successfully connected to, or ``None`` otherwise.
"""
from kiwipy.rmq.threadcomms import connect

connection_params = dict(BROKER_DEFAULTS)

try:
connect(connection_params=connection_params)
except ConnectionError:
return None

return connection_params
1 change: 1 addition & 0 deletions src/aiida/cmdline/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
cmd_help,
cmd_node,
cmd_plugin,
cmd_presto,
cmd_process,
cmd_profile,
cmd_rabbitmq,
Expand Down
133 changes: 133 additions & 0 deletions src/aiida/cmdline/commands/cmd_presto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
###########################################################################
# 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 presto`` command."""

from __future__ import annotations

import pathlib
import re
import typing as t

import click

from aiida.cmdline.commands.cmd_verdi import verdi
from aiida.cmdline.utils import echo
from aiida.manage.configuration import get_config_option

DEFAULT_PROFILE_NAME_PREFIX: str = 'presto'


def default_profile_name():
from aiida.manage import get_config

profile_names = get_config().profile_names
indices = []

for profile_name in profile_names:
if match := re.search(r'presto[-]?(\d+)?', profile_name):
indices.append(match.group(1) or '0')

if not indices:
return DEFAULT_PROFILE_NAME_PREFIX

last_index = int(sorted(indices)[-1])

return f'{DEFAULT_PROFILE_NAME_PREFIX}-{last_index + 1}'


@verdi.command('presto')
@click.option(
'--profile-name',
default=lambda: default_profile_name(),
show_default=True,
help='The name of the profile. By default generates a name that does not already exist and starts with '
f'`{DEFAULT_PROFILE_NAME_PREFIX}`.',
)
@click.option(
'--email',
default=get_config_option('autofill.user.email') or 'aiida@localhost',
show_default=True,
help='The email of the default user.',
)
@click.pass_context
def verdi_presto(ctx, profile_name, email):
"""Set up a new profile as automatically as possible.
This command aims making setting up a new profile as easy as possible. It intentionally provides a limited amount of
options to customize the profile and by default does not require any options to be specified at all. For full
control, please use the command `verdi profile setup` instead.
The main goal for this command is to setup a profile with minimal requirements to make it easy to install AiiDA and
get started as quickly as possible. Therefore, by default, the created profile uses the `core.sqlite_dos` storage
plugin which does not require any services, such as PostgreSQL and RabbitMQ are not required. This _does_ mean,
however, that not all functionality of AiiDA is available, most notably running the daemon and submitting processes
to said daemon.
The command performs the following actions:
\b
* Create a new profile that is set as the new default
* Create a default user for the profile (email can be configured through options)
* Set up the localhost as a `Computer` and configure it
* Set a number of configuration options with sensible defaults
"""
from aiida.brokers.rabbitmq.defaults import detect_rabbitmq_config
from aiida.common import exceptions
from aiida.manage.configuration import create_profile, load_profile
from aiida.orm import Computer

storage_config: dict[str, t.Any] = {}
storage_backend = 'core.sqlite_dos'

broker_config = detect_rabbitmq_config()
broker_backend = 'core.rabbitmq' if broker_config is not None else None

if broker_config is None:
echo.echo_report('RabbitMQ server not found: configuring the profile without a broker.')
else:
echo.echo_report('RabbitMQ server detected: configuring the profile with a broker.')

try:
profile = create_profile(
ctx.obj.config,
name=profile_name,
email=email,
storage_backend=storage_backend,
storage_config=storage_config,
broker_backend=broker_backend,
broker_config=broker_config,
)
except (ValueError, TypeError, exceptions.EntryPointError, exceptions.StorageMigrationError) as exception:
echo.echo_critical(str(exception))

echo.echo_success(f'Created new profile `{profile.name}`.')

ctx.obj.config.set_option('runner.poll.interval', 1, scope=profile.name)
ctx.obj.config.set_default_profile(profile.name, overwrite=True)
ctx.obj.config.store()

echo.echo_info(f'Loaded newly created profile `{profile.name}`.')
load_profile(profile.name, allow_switch=True)

filepath_scratch = pathlib.Path(ctx.obj.config.dirpath) / 'scratch' / profile.name

computer = Computer(
label='localhost',
hostname='localhost',
description='Localhost automatically created by `verdi presto`',
transport_type='core.local',
scheduler_type='core.direct',
workdir=str(filepath_scratch),
).store()
computer.configure(safe_interval=0)
computer.set_minimum_job_poll_interval(1)
computer.set_default_mpiprocs_per_machine(1)

echo.echo_success('Configured the localhost as a computer.')
2 changes: 1 addition & 1 deletion src/aiida/cmdline/commands/cmd_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def profile_list():
echo.echo_report(f'configuration folder: {config.dirpath}')

if not config.profiles:
echo.echo_warning('no profiles configured: run `verdi setup` to create one')
echo.echo_warning('no profiles configured: run `verdi presto` to create one')
else:
sort = lambda profile: profile.name # noqa: E731
highlight = lambda profile: profile.name == config.default_profile_name # noqa: E731
Expand Down
5 changes: 4 additions & 1 deletion src/aiida/cmdline/commands/cmd_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ def verdi_status(print_traceback, no_rmq):

if profile is None:
print_status(ServiceStatus.WARNING, 'profile', 'no profile configured yet')
echo.echo_report('Configure a profile by running `verdi quicksetup` or `verdi setup`.')
echo.echo_report(
'Run `verdi presto` to automatically setup a profile using all defaults or use `verdi profile setup` '
'for more control.'
)
return

print_status(ServiceStatus.UP, 'profile', profile.name)
Expand Down
2 changes: 1 addition & 1 deletion src/aiida/manage/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def create_default_user(
if user:
manager.set_default_user_email(profile, user.email)

return
return user


def create_profile(
Expand Down
2 changes: 1 addition & 1 deletion src/aiida/storage/sqlite_dos/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class Model(BaseModel):
filepath: str = Field(
title='Directory of the backend',
description='Filepath of the directory in which to store data for this backend.',
default_factory=lambda: AIIDA_CONFIG_FOLDER / 'repository' / f'sqlite_dos_{uuid4().hex}',
default_factory=lambda: str(AIIDA_CONFIG_FOLDER / 'repository' / f'sqlite_dos_{uuid4().hex}'),
)

@field_validator('filepath')
Expand Down
31 changes: 31 additions & 0 deletions tests/cmdline/commands/test_presto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Tests for ``verdi presto``."""

import pytest
from aiida.cmdline.commands.cmd_presto import default_profile_name, verdi_presto
from aiida.manage.configuration.config import Config


@pytest.mark.parametrize(
'profile_names, expected',
(
([], 'presto'),
(['main', 'sqlite'], 'presto'),
(['presto'], 'presto-1'),
(['presto', 'presto-5', 'presto-2'], 'presto-6'),
(['presto', 'main', 'presto-2', 'sqlite'], 'presto-3'),
),
)
def test_default_profile_name(monkeypatch, profile_names, expected):
"""Test the dynamic default profile function."""

def get_profile_names(self):
return profile_names

monkeypatch.setattr(Config, 'profile_names', property(get_profile_names))
assert default_profile_name() == expected


def test_presto(run_cli_command):
"""Test the ``verdi presto``."""
result = run_cli_command(verdi_presto, use_subprocess=False)
assert 'Created new profile `presto`.' in result.output

0 comments on commit 65f53e9

Please sign in to comment.