Source code for aiida.orm.groups

# -*- 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               #
###########################################################################
""" AiiDA Group entites"""

from enum import Enum
import warnings

from aiida.common import exceptions
from aiida.common.lang import type_check
from aiida.common.warnings import AiidaDeprecationWarning
from aiida.manage.manager import get_manager

from . import convert
from . import entities
from . import users

__all__ = ('Group', 'GroupTypeString')


[docs]class GroupTypeString(Enum): """A simple enum of allowed group type strings.""" UPFGROUP_TYPE = 'data.upf' IMPORTGROUP_TYPE = 'auto.import' VERDIAUTOGROUP_TYPE = 'auto.run' USER = 'user'
[docs]class Group(entities.Entity): """An AiiDA ORM implementation of group of nodes."""
[docs] class Collection(entities.Collection): """Collection of Groups"""
[docs] def get_or_create(self, label=None, **kwargs): """ Try to retrieve a group from the DB with the given arguments; create (and store) a new group if such a group was not present yet. :param label: group label :type label: str :return: (group, created) where group is the group (new or existing, in any case already stored) and created is a boolean saying :rtype: (:class:`aiida.orm.Group`, bool) """ if not label: raise ValueError('Group label must be provided') filters = {'label': label} if 'type_string' in kwargs: if not isinstance(kwargs['type_string'], str): raise exceptions.ValidationError( 'type_string must be {}, you provided an object of type ' '{}'.format(str, type(kwargs['type_string'])) ) filters['type_string'] = kwargs['type_string'] res = self.find(filters=filters) if not res: return Group(label, backend=self.backend, **kwargs).store(), True if len(res) > 1: raise exceptions.MultipleObjectsError('More than one groups found in the database') return res[0], False
[docs] def delete(self, id): # pylint: disable=invalid-name, redefined-builtin """ Delete a group :param id: the id of the group to delete """ self._backend.groups.delete(id)
[docs] def __init__(self, label=None, user=None, description='', type_string=GroupTypeString.USER.value, backend=None): """ Create a new group. Either pass a dbgroup parameter, to reload a group from the DB (and then, no further parameters are allowed), or pass the parameters for the Group creation. :param label: The group label, required on creation :type label: str :param description: The group description (by default, an empty string) :type description: str :param user: The owner of the group (by default, the automatic user) :type user: :class:`aiida.orm.User` :param type_string: a string identifying the type of group (by default, an empty string, indicating an user-defined group. :type type_string: str """ if not label: raise ValueError('Group label must be provided') # Check that chosen type_string is allowed if not isinstance(type_string, str): raise exceptions.ValidationError( 'type_string must be {}, you provided an object of type ' '{}'.format(str, type(type_string)) ) backend = backend or get_manager().get_backend() user = user or users.User.objects(backend).get_default() type_check(user, users.User) model = backend.groups.create( label=label, user=user.backend_entity, description=description, type_string=type_string ) super().__init__(model)
[docs] def __repr__(self): return '<{}: {}>'.format(self.__class__.__name__, str(self))
[docs] def __str__(self): if self.type_string: return '"{}" [type {}], of user {}'.format(self.label, self.type_string, self.user.email) return '"{}" [user-defined], of user {}'.format(self.label, self.user.email)
@property def label(self): """ :return: the label of the group as a string """ return self._backend_entity.label @label.setter def label(self, label): """ Attempt to change the label of the group instance. If the group is already stored and the another group of the same type already exists with the desired label, a UniquenessError will be raised :param label: the new group label :type label: str :raises aiida.common.UniquenessError: if another group of same type and label already exists """ self._backend_entity.label = label @property def description(self): """ :return: the description of the group as a string :rtype: str """ return self._backend_entity.description @description.setter def description(self, description): """ :param description: the description of the group as a string :type description: str """ self._backend_entity.description = description @property def type_string(self): """ :return: the string defining the type of the group """ return self._backend_entity.type_string @property def user(self): """ :return: the user associated with this group """ return users.User.from_backend_entity(self._backend_entity.user) @user.setter def user(self, user): """Set the user. :param user: the user :type user: :class:`aiida.orm.User` """ type_check(user, users.User) self._backend_entity.user = user.backend_entity @property def uuid(self): """ :return: a string with the uuid :rtype: str """ return self._backend_entity.uuid
[docs] def count(self): """Return the number of entities in this group. :return: integer number of entities contained within the group :rtype: int """ return self._backend_entity.count()
@property def nodes(self): """ Return a generator/iterator that iterates over all nodes and returns the respective AiiDA subclasses of Node, and also allows to ask for the number of nodes in the group using len(). :rtype: :class:`aiida.orm.convert.ConvertIterator` """ return convert.ConvertIterator(self._backend_entity.nodes) @property def is_empty(self): """Return whether the group is empty, i.e. it does not contain any nodes. :return: True if it contains no nodes, False otherwise :rtype: bool """ try: self.nodes[0] except IndexError: return True else: return False
[docs] def clear(self): """Remove all the nodes from this group.""" return self._backend_entity.clear()
[docs] def add_nodes(self, nodes): """Add a node or a set of nodes to the group. :note: all the nodes *and* the group itself have to be stored. :param nodes: a single `Node` or a list of `Nodes` :type nodes: :class:`aiida.orm.Node` or list """ from .nodes import Node if not self.is_stored: raise exceptions.ModificationNotAllowed('cannot add nodes to an unstored group') # Cannot use `collections.Iterable` here, because that would also match iterable `Node` sub classes like `List` if not isinstance(nodes, (list, tuple)): nodes = [nodes] for node in nodes: type_check(node, Node) self._backend_entity.add_nodes([node.backend_entity for node in nodes])
[docs] def remove_nodes(self, nodes): """Remove a node or a set of nodes to the group. :note: all the nodes *and* the group itself have to be stored. :param nodes: a single `Node` or a list of `Nodes` :type nodes: :class:`aiida.orm.Node` or list """ from .nodes import Node if not self.is_stored: raise exceptions.ModificationNotAllowed('cannot add nodes to an unstored group') # Cannot use `collections.Iterable` here, because that would also match iterable `Node` sub classes like `List` if not isinstance(nodes, (list, tuple)): nodes = [nodes] for node in nodes: type_check(node, Node) self._backend_entity.remove_nodes([node.backend_entity for node in nodes])
[docs] @classmethod def get(cls, **kwargs): """ Custom get for group which can be used to get a group with the given attributes :param kwargs: the attributes to match the group to :return: the group :type nodes: :class:`aiida.orm.Node` or list """ from aiida.orm import QueryBuilder filters = {} if 'type_string' in kwargs: if not isinstance(kwargs['type_string'], str): raise exceptions.ValidationError( 'type_string must be {}, you provided an object of type ' '{}'.format(str, type(kwargs['type_string'])) ) query = QueryBuilder() for key, val in kwargs.items(): filters[key] = val query.append(cls, filters=filters) results = query.all() if len(results) > 1: raise exceptions.MultipleObjectsError("Found {} groups matching criteria '{}'".format(len(results), kwargs)) if not results: raise exceptions.NotExistent("No group found matching criteria '{}'".format(kwargs)) return results[0][0]
[docs] def is_user_defined(self): """ :return: True if the group is user defined, False otherwise :rtype: bool """ return not self.type_string
[docs] @staticmethod def get_schema(): """ Every node property contains: - display_name: display name of the property - help text: short help text of the property - is_foreign_key: is the property foreign key to other type of the node - type: type of the property. e.g. str, dict, int :return: schema of the group :rtype: dict .. deprecated:: 1.0.0 Will be removed in `v2.0.0`. Use :meth:`~aiida.restapi.translator.base.BaseTranslator.get_projectable_properties` instead. """ message = 'method is deprecated, use' \ '`aiida.restapi.translator.base.BaseTranslator.get_projectable_properties` instead' warnings.warn(message, AiidaDeprecationWarning) # pylint: disable=no-member return { 'description': { 'display_name': 'Description', 'help_text': 'Short description of the group', 'is_foreign_key': False, 'type': 'str' }, 'id': { 'display_name': 'Id', 'help_text': 'Id of the object', 'is_foreign_key': False, 'type': 'int' }, 'label': { 'display_name': 'Label', 'help_text': 'Name of the object', 'is_foreign_key': False, 'type': 'str' }, 'type_string': { 'display_name': 'Type_string', 'help_text': 'Type of the group', 'is_foreign_key': False, 'type': 'str' }, 'user_id': { 'display_name': 'Id of creator', 'help_text': 'Id of the user that created the node', 'is_foreign_key': True, 'related_column': 'id', 'related_resource': '_dbusers', 'type': 'int' }, 'uuid': { 'display_name': 'Unique ID', 'help_text': 'Universally Unique Identifier', 'is_foreign_key': False, 'type': 'unicode' } }