Beispiel #1
0
    def _add_dblink_from(self,
                         src,
                         label=None,
                         link_type=LinkType.UNSPECIFIED):
        from aiida.backends.sqlalchemy import session
        if not isinstance(src, Node):
            raise ValueError("src must be a Node instance")
        if self.uuid == src.uuid:
            raise ValueError("Cannot link to itself")

        if self._to_be_stored:
            raise ModificationNotAllowed(
                "Cannot call the internal _add_dblink_from if the "
                "destination node is not stored")
        if src._to_be_stored:
            raise ModificationNotAllowed(
                "Cannot call the internal _add_dblink_from if the "
                "source node is not stored")

        # Check for cycles. This works if the transitive closure is enabled; if
        # it isn't, this test will never fail, but then having a circular link
        # is not meaningful but does not pose a huge threat
        #
        # I am linking src->self; a loop would be created if a DbPath exists
        # already in the TC table from self to src
        c = session.query(literal(True)).filter(
            DbPath.query.filter_by(parent_id=self.dbnode.id,
                                   child_id=src.dbnode.id).exists()).scalar()
        if c:
            raise ValueError(
                "The link you are attempting to create would generate a loop")

        if label is None:
            autolabel_idx = 1

            existing_from_autolabels = session.query(DbLink.label).filter(
                DbLink.output_id == self.dbnode.id, DbLink.label.like("link%"))

            while "link_{}".format(autolabel_idx) in existing_from_autolabels:
                autolabel_idx += 1

            safety_counter = 0
            while True:
                safety_counter += 1
                if safety_counter > 3:
                    # Well, if you have more than 100 concurrent addings
                    # to the same node, you are clearly doing something wrong...
                    raise InternalError(
                        "Hey! We found more than 100 concurrent"
                        " adds of links "
                        "to the same nodes! Are you really doing that??")
                try:
                    self._do_create_link(src, "link_{}".format(autolabel_idx),
                                         link_type)
                    break
                except UniquenessError:
                    # Retry loop until you find a new loop
                    autolabel_idx += 1
        else:
            self._do_create_link(src, label, link_type)
Beispiel #2
0
    def _add_dblink_from(self, src, label=None, link_type=LinkType.UNSPECIFIED):
        from aiida.orm.querybuilder import QueryBuilder
        if not isinstance(src, Node):
            raise ValueError("src must be a Node instance")
        if self.uuid == src.uuid:
            raise ValueError("Cannot link to itself")

        if self._to_be_stored:
            raise ModificationNotAllowed(
                "Cannot call the internal _add_dblink_from if the "
                "destination node is not stored")
        if src._to_be_stored:
            raise ModificationNotAllowed(
                "Cannot call the internal _add_dblink_from if the "
                "source node is not stored")

        if link_type is LinkType.CREATE or link_type is LinkType.INPUT:
            # Check for cycles. This works if the transitive closure is enabled; if it
            # isn't, this test will never fail, but then having a circular link is not
            # meaningful but does not pose a huge threat
            #
            # I am linking src->self; a loop would be created if a DbPath exists already
            # in the TC table from self to src
            if QueryBuilder().append(
                    Node, filters={'id':self.pk}, tag='parent').append(
                    Node, filters={'id':src.pk}, tag='child', descendant_of='parent').count() > 0:
                raise ValueError(
                    "The link you are attempting to create would generate a loop")

        if label is None:
            autolabel_idx = 1

            existing_from_autolabels = list(DbLink.objects.filter(
                output=self.dbnode,
                label__startswith="link_").values_list('label', flat=True))
            while "link_{}".format(autolabel_idx) in existing_from_autolabels:
                autolabel_idx += 1

            safety_counter = 0
            while True:
                safety_counter += 1
                if safety_counter > 100:
                    # Well, if you have more than 100 concurrent addings
                    # to the same node, you are clearly doing something wrong...
                    raise InternalError("Hey! We found more than 100 concurrent"
                                        " adds of links "
                                        "to the same nodes! Are you really doing that??")
                try:
                    self._do_create_link(src, "link_{}".format(autolabel_idx), link_type)
                    break
                except UniquenessError:
                    # Retry loop until you find a new loop
                    autolabel_idx += 1
        else:
            self._do_create_link(src, label, link_type)
Beispiel #3
0
    def _set_state(self, state):
        """
        Set the state of the calculation.

        Set it in the DbCalcState to have also the uniqueness check.
        Moreover (except for the IMPORTED state) also store in the 'state'
        attribute, useful to know it also after importing, and for faster
        querying.

        .. todo:: Add further checks to enforce that the states are set
           in order?

        :param state: a string with the state. This must be a valid string,
          from ``aiida.common.datastructures.calc_states``.
        :raise: ModificationNotAllowed if the given state was already set.
        """
        super(JobCalculation, self)._set_state(state)

        from aiida.common.datastructures import sort_states
        from aiida.backends.djsite.db.models import DbCalcState

        if not self.is_stored:
            raise ModificationNotAllowed("Cannot set the calculation state "
                                         "before storing")

        if state not in calc_states:
            raise ValueError(
                "'{}' is not a valid calculation status".format(state))

        old_state = self.get_state()
        if old_state:
            state_sequence = [state, old_state]

            # sort from new to old: if they are equal, then it is a valid
            # advance in state (otherwise, we are going backwards...)
            if sort_states(state_sequence) != state_sequence:
                raise ModificationNotAllowed("Cannot change the state from {} "
                                             "to {}".format(old_state, state))

        try:
            with transaction.atomic():
                new_state = DbCalcState(dbnode=self.dbnode, state=state).save()
        except IntegrityError:
            raise ModificationNotAllowed(
                "Calculation pk= {} already transited through "
                "the state {}".format(self.pk, state))

        # For non-imported states, also set in the attribute (so that, if we
        # export, we can still see the original state the calculation had.
        if state != calc_states.IMPORTED:
            self._set_attr('state', state)
Beispiel #4
0
    def _del_attr(self, key):
        """
        Delete an attribute

        :param key: attribute name
        :raise AttributeError: if key does not exist
        :raise ModificationNotAllowed: if the node is already sealed or if the node is already stored
            and the attribute is not updatable
        """
        if self.is_sealed:
            raise ModificationNotAllowed('Cannot change the attributes of a sealed node')

        if self.is_stored and key not in self._updatable_attributes:
            raise ModificationNotAllowed('Cannot change the immutable attributes of a stored node')

        super(Sealable, self)._del_attr(key, stored_check=False)
Beispiel #5
0
    def _set_attr(self, key, value, **kwargs):
        """
        Set a new attribute

        :param key: attribute name
        :param value: attribute value
        :raise ModificationNotAllowed: if the node is already sealed or if the node is already stored
            and the attribute is not updatable
        """
        if self.is_sealed:
            raise ModificationNotAllowed('Cannot change the attributes of a sealed node')

        if self.is_stored and key not in self._updatable_attributes:
            raise ModificationNotAllowed('Cannot change the immutable attributes of a stored node')

        super(Sealable, self)._set_attr(key, value, stored_check=False, **kwargs)
Beispiel #6
0
    def remove_nodes(self, nodes):
        from aiida.backends.djsite.db.models import DbNode
        if not self.is_stored:
            raise ModificationNotAllowed("Cannot remove nodes from a group "
                                         "before storing")

        # First convert to a list
        if isinstance(nodes, (Node, DbNode)):
            nodes = [nodes]

        if isinstance(
                nodes,
                basestring) or not isinstance(nodes, collections.Iterable):
            raise TypeError("Invalid type passed as the 'nodes' parameter to "
                            "remove_nodes, can only be a Node, DbNode, or a "
                            "list of such objects, it is instead {}".format(
                                str(type(nodes))))

        list_pk = []
        for node in nodes:
            if not isinstance(node, (Node, DbNode)):
                raise TypeError("Invalid type of one of the elements passed "
                                "to add_nodes, it should be either a Node or "
                                "a DbNode, it is instead {}".format(
                                    str(type(node))))
            if node.pk is None:
                raise ValueError("At least one of the provided nodes is "
                                 "unstored, stopping...")
            list_pk.append(node.pk)

        self.dbgroup.dbnodes.remove(*list_pk)
Beispiel #7
0
 def set_computer(self, computer):
     from aiida.backends.djsite.db.models import DbComputer
     if self._to_be_stored:
         self.dbnode.dbcomputer = DbComputer.get_dbcomputer(computer)
     else:
         raise ModificationNotAllowed(
             "Node with uuid={} was already stored".format(self.uuid))
Beispiel #8
0
    def add_path(self, src_abs, dst_filename=None):
        """
        Disable adding files or directories to a RemoteData
        """
        from aiida.common.exceptions import ModificationNotAllowed

        raise ModificationNotAllowed("Cannot add files or directories to a RemoteData object")
Beispiel #9
0
 def set_computer(self, computer):
     if self._to_be_stored:
         computer = DbComputer.get_dbcomputer(computer)
         self.dbnode.dbcomputer = computer
     else:
         raise ModificationNotAllowed(
             "Node with uuid={} was already stored".format(self.uuid))
Beispiel #10
0
    def add_comment(self, content, user=None):
        from aiida.backends.sqlalchemy import session
        if self._to_be_stored:
            raise ModificationNotAllowed("Comments can be added only after "
                                         "storing the node")

        comment = DbComment(dbnode=self._dbnode, user=user, content=content)
        session.add(comment)
        session.commit()
Beispiel #11
0
    def add_comment(self, content, user=None):
        from aiida.backends.djsite.db.models import DbComment
        if self._to_be_stored:
            raise ModificationNotAllowed("Comments can be added only after "
                                         "storing the node")

        DbComment.objects.create(dbnode=self._dbnode,
                                 user=user,
                                 content=content)
Beispiel #12
0
 def add_attribute(self, _name, _value):
     """
     Add one attributes to the Workflow. If another attribute is present with the same name it will
     be overwritten.
     :param name: a string with the attribute name to store
     :param value: a storable object to store
     """
     if self._to_be_stored:
         raise ModificationNotAllowed("You cannot add attributes before storing")
     self.dbworkflowinstance.add_attribute(_name, _value)
Beispiel #13
0
 def del_extra(self, key):
     from aiida.backends.djsite.db.models import DbExtra
     if self._to_be_stored:
         raise ModificationNotAllowed(
             "The extras of a node can be set and deleted "
             "only after storing the node")
     if not DbExtra.has_key(self.dbnode, key):
         raise AttributeError("DbExtra {} does not exist".format(key))
     return DbExtra.del_value_for_node(self.dbnode, key)
     self._increment_version_number_db()
Beispiel #14
0
    def set_extra(self, key, value, exclusive=False):
        # TODO SP: validate key
        # TODO SP: handle exclusive (what to do in case the key already exist
        # ?)
        if self._to_be_stored:
            raise ModificationNotAllowed(
                "The extras of a node can be set only after "
                "storing the node")

        self.dbnode.set_extra(key, value)
        self._increment_version_number_db()
Beispiel #15
0
    def store_all(self, with_transaction=True):
        """
        Store the node, together with all input links, if cached, and also the
        linked nodes, if they were not stored yet.

        :parameter with_transaction: if False, no transaction is used. This
          is meant to be used ONLY if the outer calling function has already
          a transaction open!
        """
        from django.db import transaction
        from aiida.common.utils import EmptyContextManager

        if with_transaction:
            context_man = transaction.commit_on_success()
        else:
            context_man = EmptyContextManager()

        if not self._to_be_stored:
            raise ModificationNotAllowed(
                "Node with pk= {} was already stored".format(self.pk))

        # For each parent, check that all its inputs are stored
        for label in self._inputlinks_cache:
            try:
                parent_node = self._inputlinks_cache[label][0]
                parent_node._check_are_parents_stored()
            except ModificationNotAllowed:
                raise ModificationNotAllowed(
                    "Parent node (UUID={}) has "
                    "unstored parents, cannot proceed (only direct parents "
                    "can be unstored and will be stored by store_all, not "
                    "grandparents or other ancestors".format(parent_node.uuid))

        with context_man:
            # Always without transaction: either it is the context_man here,
            # or it is managed outside
            self._store_input_nodes()
            self.store(with_transaction=False)
            self._store_cached_input_links(with_transaction=False)

        return self
Beispiel #16
0
    def _del_attr(self, key):
        """
        Delete an attribute.

        :param key: attribute to delete.
        :raise AttributeError: if key does not exist.
        :raise ModificationNotAllowed: if the Node was already stored.
        """
        if self.is_stored:
            raise ModificationNotAllowed(
                "Cannot delete the attributes of a stored data node.")
        super(Data, self)._del_attr(key)
Beispiel #17
0
    def reset_extras(self, new_extras):

        if type(new_extras) is not dict:
            raise ValueError("The new extras have to be a dictionary")

        if self._to_be_stored:
            raise ModificationNotAllowed(
                "The extras of a node can be set only after "
                "storing the node")

        self.dbnode.reset_extras(new_extras)
        self._increment_version_number_db()
Beispiel #18
0
    def _del_attr(self, key):
        """
        Delete an attribute.

        :param key: attribute to delete.
        :raise AttributeError: if key does not exist.
        :raise ModificationNotAllowed: if the Node was already stored.
        """
        if self.is_sealed and key not in self._updatable_attributes:
            raise ModificationNotAllowed(
                "Cannot delete the attributes of a sealed calculation.")
        super(SealableWithUpdatableAttributes, self)._del_attr(key)
Beispiel #19
0
    def store_all(self, with_transaction=True):
        """
        Store the node, together with all input links, if cached, and also the
        linked nodes, if they were not stored yet.

        :parameter with_transaction: if False, no transaction is used. This
          is meant to be used ONLY if the outer calling function has already
          a transaction open!
        """

        if not self._to_be_stored:
            raise ModificationNotAllowed(
                "Node with pk= {} was already stored".format(self.id))

        # For each parent, check that all its inputs are stored
        for link in self._inputlinks_cache:
            try:
                parent_node = self._inputlinks_cache[link][0]
                parent_node._check_are_parents_stored()
            except ModificationNotAllowed:
                raise ModificationNotAllowed(
                    "Parent node (UUID={}) has "
                    "unstored parents, cannot proceed (only direct parents "
                    "can be unstored and will be stored by store_all, not "
                    "grandparents or other ancestors".format(parent_node.uuid))

        self._store_input_nodes()
        self.store(with_transaction=False)
        self._store_cached_input_links(with_transaction=False)
        from aiida.backends.sqlalchemy import get_scoped_session
        session = get_scoped_session()

        if with_transaction:
            try:
                session.commit()
            except SQLAlchemyError as e:
                session.rollback()
                raise

        return self
Beispiel #20
0
    def add_nodes(self, nodes):
        from sqlalchemy.exc import IntegrityError

        if not self.is_stored:
            raise ModificationNotAllowed("Cannot add nodes to a group before "
                                         "storing")
        from aiida.orm.implementation.sqlalchemy.node import Node
        from aiida.backends.sqlalchemy import get_scoped_session

        # First convert to a list
        if isinstance(nodes, (Node, DbNode)):
            nodes = [nodes]

        if isinstance(
                nodes,
                basestring) or not isinstance(nodes, collections.Iterable):
            raise TypeError("Invalid type passed as the 'nodes' parameter to "
                            "add_nodes, can only be a Node, DbNode, or a list "
                            "of such objects, it is instead {}".format(
                                str(type(nodes))))

        with utils.disable_expire_on_commit(get_scoped_session()) as session:
            # Get dbnodes here ONCE, otherwise each call to _dbgroup.dbnodes will
            # re-read the current value in the database
            dbnodes = self._dbgroup.dbnodes
            for node in nodes:
                if not isinstance(node, (Node, DbNode)):
                    raise TypeError(
                        "Invalid type of one of the elements passed "
                        "to add_nodes, it should be either a Node or "
                        "a DbNode, it is instead {}".format(str(type(node))))

                if node.id is None:
                    raise ValueError("At least one of the provided nodes is "
                                     "unstored, stopping...")
                if isinstance(node, Node):
                    to_add = node.dbnode
                else:
                    to_add = node

                # Use pattern as suggested here:
                # http://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#using-savepoint
                try:
                    with session.begin_nested():
                        dbnodes.append(to_add)
                        session.flush()
                except IntegrityError:
                    # Duplicate entry, skip
                    pass

            # Commit everything as up till now we've just flushed
            session.commit()
Beispiel #21
0
    def release(self, owner="None"):
        if self.dblock == None:
            raise InternalError("No dblock present.")

        try:
            if (self.dblock.owner == owner):
                self.dblock.delete()
                self.dblock = None
            else:
                raise ModificationNotAllowed("Only the owner can release the lock.")

        except:
            raise InternalError("Cannot release a lock, Reload the Daemon to fix the problem.")
Beispiel #22
0
    def _set_pbc(self, value):
        """
        validate the pbc, then store them
        """
        from aiida.common.exceptions import ModificationNotAllowed
        from aiida.orm.nodes.data.structure import get_valid_pbc

        if self.is_stored:
            raise ModificationNotAllowed('The KpointsData object cannot be modified, it has already been stored')
        the_pbc = get_valid_pbc(value)
        self.set_attribute('pbc1', the_pbc[0])
        self.set_attribute('pbc2', the_pbc[1])
        self.set_attribute('pbc3', the_pbc[2])
Beispiel #23
0
    def set_extra_exclusive(self, key, value):
        from aiida.backends.djsite.db.models import DbExtra
        DbExtra.validate_key(key)

        if self._to_be_stored:
            raise ModificationNotAllowed(
                "The extras of a node can be set only after "
                "storing the node")
        DbExtra.set_value_for_node(self.dbnode,
                                   key,
                                   value,
                                   stop_if_existing=True)
        self._increment_version_number_db()
Beispiel #24
0
    def add_comment(self, content, user=None):
        from aiida.backends.djsite.db.models import DbComment

        if not self.is_stored:
            raise ModificationNotAllowed("Comments can be added only after "
                                         "storing the node")

        if user is None:
            user = self.backend.users.get_automatic_user()

        return DbComment.objects.create(dbnode=self._dbnode,
                                        user=user.dbuser,
                                        content=content).id
Beispiel #25
0
    def _store_cached_input_links(self, with_transaction=True):
        """
        Store all input links that are in the local cache, transferring them
        to the DB.

        :note: This can be called only if all parents are already stored.

        :note: Links are stored only after the input nodes are stored. Moreover,
            link storage is done in a transaction, and if one of the links
            cannot be stored, an exception is raised and *all* links will remain
            in the cache.

        :note: This function can be called only after the node is stored.
           After that, it can be called multiple times, and nothing will be
           executed if no links are still in the cache.

        :parameter with_transaction: if False, no transaction is used. This
          is meant to be used ONLY if the outer calling function has already
          a transaction open!
        """

        if self._to_be_stored:
            raise ModificationNotAllowed(
                "Node with pk= {} is not stored yet".format(self.id))

        # This raises if there is an unstored node.
        self._check_are_parents_stored()
        # I have to store only those links where the source is already
        # stored
        links_to_store = list(self._inputlinks_cache.keys())

        for label in links_to_store:
            src, link_type = self._inputlinks_cache[label]
            self._add_dblink_from(src, label, link_type)
        # If everything went smoothly, clear the entries from the cache.
        # I do it here because I delete them all at once if no error
        # occurred; otherwise, links will not be stored and I
        # should not delete them from the cache (but then an exception
        # would have been raised, and the following lines are not executed)
        self._inputlinks_cache.clear()

        from aiida.backends.sqlalchemy import get_scoped_session
        session = get_scoped_session()

        if with_transaction:
            try:
                session.commit()
            except SQLAlchemyError as e:
                session.rollback()
                raise
Beispiel #26
0
    def _set_cell(self, value):
        """
        Validate if 'value' is a allowed crystal unit cell
        :param value: something compatible with a 3x3 tuple of floats
        """
        from aiida.common.exceptions import ModificationNotAllowed
        from aiida.orm.nodes.data.structure import _get_valid_cell

        if self.is_stored:
            raise ModificationNotAllowed('KpointsData cannot be modified, it has already been stored')

        the_cell = _get_valid_cell(value)

        self.set_attribute('cell', the_cell)
Beispiel #27
0
    def _check_are_parents_stored(self):
        """
        Check if all parents are already stored, otherwise raise.

        :raise ModificationNotAllowed: if one of the input nodes in not already
          stored.
        """
        # Preliminary check to verify that inputs are stored already
        for label in self._inputlinks_cache:
            if not self._inputlinks_cache[label][0].is_stored:
                raise ModificationNotAllowed(
                    "Cannot store the input link '{}' because the "
                    "source node is not stored. Either store it first, "
                    "or call _store_input_links with the store_parents "
                    "parameter set to True".format(label))
Beispiel #28
0
    def remove_path(self, path):
        """
        Remove a file or directory from the repository directory.
        Can be called only before storing.

        :param str path: relative path to file/directory.
        """
        if self.is_stored:
            raise ModificationNotAllowed(
                "Cannot delete a path after storing the node")

        if os.path.isabs(path):
            raise ValueError("The destination path in remove_path "
                             "must be a relative path")
        self._get_folder_pathsubfolder.remove_path(path)
Beispiel #29
0
    def _set_attr(self, key, value):
        """
        Set a new attribute to the Node (in the DbAttribute table).

        :param str key: key name
        :param value: its value
        :raise ModificationNotAllowed: if such attribute cannot be added (e.g.
            because the node was already stored)

        :raise ValidationError: if the key is not valid (e.g. it contains the
            separator symbol).
        """
        if self.is_stored:
            raise ModificationNotAllowed(
                "Cannot change the attributes of a stored data node.")
        super(Data, self)._set_attr(key, value)
Beispiel #30
0
    def add_link_from(self, src, label=None, link_type=LinkType.UNSPECIFIED):
        """
        Add a link from a node

        You can use the parameters of the base Node class, in particular the
        label parameter to label the link.

        :param src: the node to add a link from
        :param str label: name of the link
        :param link_type: type of the link, must be one of the enum values from
          :class:`~aiida.common.links.LinkType`
        """
        if self.is_sealed:
            raise ModificationNotAllowed('Cannot add a link from a sealed node')

        super(Sealable, self).add_link_from(src, label=label, link_type=link_type)