def start(foreground, number): """Start the daemon with NUMBER workers [default=1].""" from aiida.engine.daemon.client import get_daemon_client client = get_daemon_client() echo.echo('Starting the daemon... ', nl=False) if foreground: command = [ 'verdi', '-p', client.profile.name, 'daemon', _START_CIRCUS_COMMAND, '--foreground', str(number) ] else: command = [ 'verdi', '-p', client.profile.name, 'daemon', _START_CIRCUS_COMMAND, str(number) ] try: currenv = get_env_with_venv_bin() subprocess.check_output(command, env=currenv, stderr=subprocess.STDOUT) # pylint: disable=unexpected-keyword-arg except subprocess.CalledProcessError as exception: click.secho('FAILED', fg='red', bold=True) echo.echo_critical(exception.output) # We add a small timeout to give the pid-file a chance to be created with spinner(): time.sleep(1) response = client.get_status() print_client_response_status(response)
def cmd_list(version, functional, protocol, project, raw): """List installed configurations of the SSSP.""" from tabulate import tabulate mapping_project = { 'count': lambda family: family.count(), 'version': lambda family: family.label.split('/')[1], 'functional': lambda family: family.label.split('/')[2], 'protocol': lambda family: family.label.split('/')[3], } rows = [] for [group] in get_sssp_families_builder(version, functional, protocol).iterall(): row = [] for projection in project: try: projected = mapping_project[projection](group) except KeyError: projected = getattr(group, projection) row.append(projected) rows.append(row) if not rows: echo.echo_info('SSSP has not yet been installed: use `aiida-sssp install` to install it.') return if raw: echo.echo(tabulate(rows, disable_numparse=True, tablefmt='plain')) else: echo.echo(tabulate(rows, headers=[projection.capitalize() for projection in project], disable_numparse=True))
def restart(ctx, reset, no_wait): """Restart the daemon. By default will only reset the workers of the running daemon. After the restart the same amount of workers will be running. If the `--reset` flag is passed, however, the full circus daemon will be stopped and restarted with just a single worker. """ from aiida.engine.daemon.client import get_daemon_client client = get_daemon_client() wait = not no_wait if reset: ctx.invoke(stop) ctx.invoke(start) else: if wait: echo.echo('Restarting the daemon... ', nl=False) else: echo.echo('Restarting the daemon') response = client.restart_daemon(wait) if wait: print_client_response_status(response)
def node_label(nodes, label, raw, force): """View or set the label of one or more nodes.""" table = [] if label is None: for node in nodes: if raw: table.append([node.label]) else: table.append([node.pk, node.label]) if raw: echo.echo(tabulate.tabulate(table, tablefmt='plain')) else: echo.echo(tabulate.tabulate(table, headers=['ID', 'Label'])) else: if not force: warning = f'Are you sure you want to set the label for {len(nodes)} nodes?' click.confirm(warning, abort=True) for node in nodes: node.label = label echo.echo_success(f"Set label '{label}' for {len(nodes)} nodes")
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))
def echo_node_dict(nodes, keys, fmt, identifier, raw, use_attrs=True): """Show the attributes or extras of one or more nodes.""" all_nodes = [] for node in nodes: if identifier == 'pk': id_name = 'PK' id_value = node.pk else: id_name = 'UUID' id_value = node.uuid if use_attrs: node_dict = node.attributes dict_name = 'attributes' else: node_dict = node.extras dict_name = 'extras' if keys is not None: node_dict = {k: v for k, v in node_dict.items() if k in keys} if raw: all_nodes.append({id_name: id_value, dict_name: node_dict}) else: echo.echo(f'{id_name}: {id_value}', bold=True) echo.echo_dictionary(node_dict, fmt=fmt) if raw: echo.echo_dictionary(all_nodes, fmt=fmt)
def node_description(nodes, description, force, raw): """View or set the description of one or more nodes.""" table = [] if description is None: for node in nodes: if raw: table.append([node.description]) else: table.append([node.pk, node.description]) if raw: echo.echo(tabulate.tabulate(table, tablefmt='plain')) else: echo.echo(tabulate.tabulate(table, headers=['ID', 'Description'])) else: if not force: warning = f'Are you sure you want to set the description for {len(nodes)} nodes?' click.confirm(warning, abort=True) for node in nodes: node.description = description echo.echo_success(f'Set description for {len(nodes)} nodes')
def status(all_profiles): """Print the status of the current daemon or all daemons. Returns exit code 0 if all requested daemons are running, else exit code 3. """ from aiida.engine.daemon.client import get_daemon_client config = get_config() if all_profiles is True: profiles = [ profile for profile in config.profiles if not profile.is_test_profile ] else: profiles = [config.current_profile] daemons_running = [] for profile in profiles: client = get_daemon_client(profile.name) delete_stale_pid_file(client) click.secho('Profile: ', fg='red', bold=True, nl=False) click.secho('{}'.format(profile.name), bold=True) result = get_daemon_status(client) echo.echo(result) daemons_running.append(client.is_daemon_running) if not all(daemons_running): sys.exit(3)
def singlefile_content(datum): """Show the content of the file.""" try: echo.echo(datum.get_content()) except (IOError, OSError) as exception: echo.echo_critical( 'could not read the content for SinglefileData<{}>: {}'.format( datum.pk, str(exception)))
def profile_show(profile): """Show details for a profile.""" if profile is None: echo.echo_critical('no profile to show') echo.echo_info('Configuration for: {}'.format(profile.name)) data = sorted([(k.lower(), v) for k, v in profile.dictionary.items()]) echo.echo(tabulate.tabulate(data))
def tree(nodes, depth): """ Show trees of nodes. """ for node in nodes: NodeTreePrinter.print_node_tree(node, depth) if len(nodes) > 1: echo.echo("")
def decorated_function(*args, **kwargs): """Echo a deprecation warning before doing anything else.""" from aiida.cmdline.utils import echo, templates from textwrap import wrap template = templates.env.get_template('deprecated.tpl') width = 80 echo.echo(template.render(msg=wrap(message, width), width=width)) return function(*args, **kwargs)
def cif_content(data): """Show the content of the CIF file.""" for node in data: try: echo.echo(node.get_content()) except IOError as exception: echo.echo_warning( 'could not read the content for CifData<{}>: {}'.format( node.pk, str(exception)))
def repo_cat(node, relative_path): """Output the content of a file in the node repository folder.""" try: content = node.get_object_content(relative_path) except Exception as exception: # pylint: disable=broad-except echo.echo_critical('failed to get the content of file `{}`: {}'.format( relative_path, exception)) else: echo.echo(content)
def work_status(calculations): """ Print the status of work calculations """ from aiida.utils.ascii_vis import format_call_graph for calculation in calculations: graph = format_call_graph(calculation) echo.echo(graph)
def _print(communicator, body, sender, subject, correlation_id): # pylint: disable=unused-argument """Format the incoming broadcast data into a message and echo it to stdout.""" if body is None: body = 'No message specified' if correlation_id is None: correlation_id = '--' echo.echo(f'Process<{sender}> [{subject}|{correlation_id}]: {body}')
def export_workflow_data(apps, _): """Export existing legacy workflow data to a JSON file.""" from tempfile import NamedTemporaryFile DbWorkflow = apps.get_model('db', 'DbWorkflow') DbWorkflowData = apps.get_model('db', 'DbWorkflowData') DbWorkflowStep = apps.get_model('db', 'DbWorkflowStep') count_workflow = DbWorkflow.objects.count() count_workflow_data = DbWorkflowData.objects.count() count_workflow_step = DbWorkflowStep.objects.count() # Nothing to do if all tables are empty if count_workflow == 0 and count_workflow_data == 0 and count_workflow_step == 0: return if not configuration.PROFILE.is_test_profile: echo.echo('\n') echo.echo_warning( 'The legacy workflow tables contain data but will have to be dropped to continue.' ) echo.echo_warning( 'If you continue, the content will be dumped to a JSON file, before dropping the tables.' ) echo.echo_warning( 'This serves merely as a reference and cannot be used to restore the database.' ) echo.echo_warning( 'If you want a proper backup, make sure to dump the full database and backup your repository' ) if not click.confirm('Are you sure you want to continue', default=True): sys.exit(1) delete_on_close = configuration.PROFILE.is_test_profile data = { 'workflow': serializers.serialize('json', DbWorkflow.objects.all()), 'workflow_data': serializers.serialize('json', DbWorkflowData.objects.all()), 'workflow_step': serializers.serialize('json', DbWorkflowStep.objects.all()), } with NamedTemporaryFile(prefix='legacy-workflows', suffix='.json', dir='.', delete=delete_on_close, mode='wb') as handle: filename = handle.name json.dump(data, handle) # If delete_on_close is False, we are running for the user and add additional message of file location if not delete_on_close: echo.echo_info(f'Exported workflow data to {filename}')
def group_description(group, description): """Change the description of a group. If no DESCRIPTION is defined, the current description will simply be echoed. """ if description: group.description = description echo.echo_success(f'Changed the description of Group<{group.label}>') else: echo.echo(group.description)
def tree(nodes, depth): """Show a tree of nodes starting from a given node.""" from aiida.common import LinkType from aiida.cmdline.utils.ascii_vis import NodeTreePrinter for node in nodes: NodeTreePrinter.print_node_tree(node, depth, tuple(LinkType.__members__.values())) if len(nodes) > 1: echo.echo('')
def database_migrate(force): """Migrate the database to the latest schema version.""" from aiida.manage.manager import get_manager from aiida.engine.daemon.client import get_daemon_client client = get_daemon_client() if client.is_daemon_running: echo.echo_critical( 'Migration aborted, the daemon for the profile is still running.') manager = get_manager() profile = manager.get_profile() backend = manager._load_backend(schema_check=False) # pylint: disable=protected-access if force: try: backend.migrate() except exceptions.ConfigurationError as exception: echo.echo_critical(str(exception)) return echo.echo_warning( 'Migrating your database might take a while and is not reversible.') echo.echo_warning( 'Before continuing, make sure you have completed the following steps:') echo.echo_warning('') echo.echo_warning( ' 1. Make sure you have no active calculations and workflows.') echo.echo_warning( ' 2. If you do, revert the code to the previous version and finish running them first.' ) echo.echo_warning(' 3. Stop the daemon using `verdi daemon stop`') echo.echo_warning(' 4. Make a backup of your database and repository') echo.echo_warning('') echo.echo_warning('', nl=False) expected_answer = 'MIGRATE NOW' confirm_message = 'If you have completed the steps above and want to migrate profile "{}", type {}'.format( profile.name, expected_answer) try: response = click.prompt(confirm_message) while response != expected_answer: response = click.prompt(confirm_message) except click.Abort: echo.echo('\n') echo.echo_critical( 'Migration aborted, the data has not been affected.') else: try: backend.migrate() except exceptions.ConfigurationError as exception: echo.echo_critical(str(exception)) else: echo.echo_success('migration completed')
def _computer_test_no_unexpected_output(transport, scheduler, authinfo): # pylint: disable=unused-argument """ Test that there is no unexpected output from the connection. This can happen if e.g. there is some spurious command in the .bashrc or .bash_profile that is not guarded in case of non-interactive shells. :param transport: an open transport :param scheduler: the corresponding scheduler class :param authinfo: the AuthInfo object (from which one can get computer and aiidauser) :return: True if the test succeeds, False if it fails. """ # Execute a command that should not return any error echo.echo('> Checking that no spurious output is present...') retval, stdout, stderr = transport.exec_command_wait('echo -n') if retval != 0: echo.echo_error("* ERROR! The command 'echo -n' returned a non-zero return code ({})!".format(retval)) return False if stdout: echo.echo_error( u"""* ERROR! There is some spurious output in the standard output, that we report below between the === signs: ========================================================= {} ========================================================= Please check that you don't have code producing output in your ~/.bash_profile (or ~/.bashrc). If you don't want to remove the code, but just to disable it for non-interactive shells, see comments in issue #1980 on GitHub: https://github.com/aiidateam/aiida-core/issues/1890 (and in the AiiDA documentation, linked from that issue) """.format(stdout) ) return False if stderr: echo.echo_error( u"""* ERROR! There is some spurious output in the stderr, that we report below between the === signs: ========================================================= {} ========================================================= Please check that you don't have code producing output in your ~/.bash_profile (or ~/.bashrc). If you don't want to remove the code, but just to disable it for non-interactive shells, see comments in issue #1980 on GitHub: https://github.com/aiidateam/aiida-core/issues/1890 (and in the AiiDA documentation, linked from that issue) """.format(stderr) ) return False echo.echo(' [OK]') return True
def computer_config_show(computer, user, defaults, as_option_string): """Show the current configuration for a computer.""" import tabulate from aiida.common.escaping import escape_for_bash transport_cls = computer.get_transport_class() option_list = [ param for param in transport_cli.create_configure_cmd( computer.get_transport_type()).params if isinstance(param, click.core.Option) ] option_list = [ option for option in option_list if option.name in transport_cls.get_valid_auth_params() ] if defaults: config = { option.name: transport_cli.transport_option_default(option.name, computer) for option in option_list } else: config = computer.get_configuration(user) option_items = [] if as_option_string: for option in option_list: t_opt = transport_cls.auth_options[option.name] if config.get(option.name) or config.get(option.name) is False: if t_opt.get('switch'): option_value = option.opts[-1] if config.get( option.name) else '--no-{}'.format( option.name.replace('_', '-')) elif t_opt.get('is_flag'): is_default = config.get( option.name) == transport_cli.transport_option_default( option.name, computer) option_value = option.opts[-1] if is_default else '' else: option_value = '{}={}'.format( option.opts[-1], option.type(config[option.name])) option_items.append(option_value) opt_string = ' '.join(option_items) echo.echo(escape_for_bash(opt_string)) else: table = [] for name in transport_cls.get_valid_auth_params(): if name in config: table.append(('* ' + name, config[name])) else: table.append(('* ' + name, '-')) echo.echo(tabulate.tabulate(table, tablefmt='plain'))
def cmd_show(family, raw): """Show details of pseudo potential family.""" from tabulate import tabulate rows = [[pseudo.element, pseudo.filename, pseudo.md5] for pseudo in family.nodes] headers = ['Element', 'Pseudo', 'MD5'] if raw: echo.echo(tabulate(sorted(rows), tablefmt='plain')) else: echo.echo(tabulate(sorted(rows), headers=headers))
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
def cmd_uniques( group, databases, not_elements, elements, max_atoms, number_species, partial_occupancies, no_cod_hydrogen, verbose ): """Pass.""" from tabulate import tabulate from aiida import orm filters = {'and': []} if not group and not databases: raise click.BadParameter('need at least a GROUP or `--databases` to be specified') if not group: if len(databases) >= 1: raise click.BadParameter('can only specify one database when not specifying a GROUP') group = orm.load_group('{}/structure/unique'.format(databases[0])) if no_cod_hydrogen: filters['and'].append({'id': {'!in': get_cod_hydrogen_structure_ids()}}) if max_atoms is not None: filters['and'].append({'attributes.sites': {'shorter': max_atoms + 1}}) if number_species is not None: filters['and'].append({'attributes.kinds': {'of_length': number_species}}) if elements: filters['and'].append({'extras.chemical_system': {'like': '%-{}-%'.format('-%-'.join(sorted(elements)))}}) if not_elements: for element in not_elements: filters['and'].append({'extras.chemical_system': {'!like': '%-{}-%'.format(element)}}) if partial_occupancies is not None: filters['and'].append({'extras.{}'.format(KEY_PARTIAL_OCCUPANCIES): partial_occupancies}) if databases: for name in DATABASES: key = 'has_key' if name in databases else '!has_key' filters['and'].append({'extras.duplicates': {key: name}}) builder = orm.QueryBuilder().append( orm.Group, filters={'id': group.id}, tag='group').append( orm.StructureData, with_group='group', filters=filters) if not verbose: echo.echo('{}'.format(builder.count())) else: rows = [] for [structure] in builder.iterall(): rows.append((structure.get_formula(), len(structure.kinds), len(structure.sites), structure.uuid, structure.get_extra('source')['id'])) echo.echo(tabulate(rows, headers=['Formula', '# species', '# atoms', 'UUID', 'Source identifier']))
def _computer_test_get_jobs(transport, scheduler, authinfo): # pylint: disable=unused-argument """ Internal test to check if it is possible to check the queue state. :param transport: an open transport :param scheduler: the corresponding scheduler class :param authinfo: the AuthInfo object (from which one can get computer and aiidauser) :return: True if the test succeeds, False if it fails. """ echo.echo("> Getting job list...") found_jobs = scheduler.getJobs(as_dict=True) echo.echo(" `-> OK, {} jobs found in the queue.".format(len(found_jobs))) return True
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.message)) else: echo.echo('{}'.format(value))
def print_node_tree(cls, node, max_depth, follow_links=()): """Top-level function for printing node tree.""" from ete3 import Tree from aiida.cmdline.utils.common import get_node_summary echo.echo(get_node_summary(node)) tree_string = '({});'.format( cls._build_tree(node, max_depth=max_depth, follow_links=follow_links)) tmp = Tree(tree_string, format=1) echo.echo(tmp.get_ascii(show_internal=True))
def verdi_config_caching(disabled): """List caching-enabled process types for the current profile.""" from aiida.plugins.entry_point import ENTRY_POINT_STRING_SEPARATOR, get_entry_point_names from aiida.manage.caching import get_use_cache for group in ['aiida.calculations', 'aiida.workflows']: for entry_point in get_entry_point_names(group): identifier = ENTRY_POINT_STRING_SEPARATOR.join([group, entry_point]) if get_use_cache(identifier=identifier): if not disabled: echo.echo(identifier) elif disabled: echo.echo(identifier)
def delete_db(profile, non_interactive=True, verbose=False): """ Delete an AiiDA database associated with an AiiDA profile. :param profile: AiiDA Profile :type profile: :class:`aiida.manage.configuration.profile.Profile` :param non_interactive: do not prompt for configuration values, fail if not all values are given as kwargs. :type non_interactive: bool :param verbose: if True, print parameters of DB connection :type verbose: bool """ from aiida.manage.configuration import get_config from aiida.manage.external.postgres import Postgres from aiida.common import json postgres = Postgres.from_profile(profile, interactive=not non_interactive, quiet=False) if verbose: echo.echo_info('Parameters used to connect to postgres:') echo.echo(json.dumps(postgres.dbinfo, indent=4)) database_name = profile.database_name if not postgres.db_exists(database_name): echo.echo_info( "Associated database '{}' does not exist.".format(database_name)) elif non_interactive or click.confirm( "Delete associated database '{}'?\n" 'WARNING: All data will be lost.'.format(database_name)): echo.echo_info("Deleting database '{}'.".format(database_name)) postgres.drop_db(database_name) user = profile.database_username config = get_config() users = [ available_profile.database_username for available_profile in config.profiles ] if not postgres.dbuser_exists(user): echo.echo_info( "Associated database user '{}' does not exist.".format(user)) elif users.count(user) > 1: echo.echo_info( "Associated database user '{}' is used by other profiles " 'and will not be deleted.'.format(user)) elif non_interactive or click.confirm( "Delete database user '{}'?".format(user)): echo.echo_info("Deleting user '{}'.".format(user)) postgres.drop_dbuser(user)