コード例 #1
0
ファイル: models.py プロジェクト: tsgit/inspirehep
class InstitutionLiterature(db.Model):
    """Keeps track of papers linked to a Institution Records."""

    __tablename__ = "institution_literature"
    __table_args__ = (
        db.Index("ix_institution_literature_institution_uuid",
                 "institution_uuid"),
        db.Index("ix_institution_literature_literature_uuid",
                 "literature_uuid"),
    )

    institution_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_institution_literature_institution_uuid"),
        nullable=False,
        primary_key=True,
    )
    literature_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_institution_literature_literature_uuid"),
        nullable=False,
        primary_key=True,
    )

    institution = db.relationship(RecordMetadata,
                                  backref="institution_papers",
                                  foreign_keys=[institution_uuid])

    institution_paper = db.relationship(RecordMetadata,
                                        backref="institutions",
                                        foreign_keys=[literature_uuid])
コード例 #2
0
ファイル: models.py プロジェクト: tsgit/inspirehep
class ExperimentLiterature(db.Model):
    """Keeps track of papers linked to Experiment Records."""

    __tablename__ = "experiment_literature"
    __table_args__ = (
        db.Index("ix_experiment_literature_experiment_uuid",
                 "experiment_uuid"),
        db.Index("ix_experiment_literature_literature_uuid",
                 "literature_uuid"),
    )

    experiment_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_experiment_literature_experiment_uuid"),
        nullable=False,
        primary_key=True,
    )
    literature_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_experiment_literature_literature_uuid"),
        nullable=False,
        primary_key=True,
    )

    experiment = db.relationship(RecordMetadata,
                                 backref="experiment_papers",
                                 foreign_keys=[experiment_uuid])

    experiment_paper = db.relationship(RecordMetadata,
                                       backref="experiments",
                                       foreign_keys=[literature_uuid])
コード例 #3
0
ファイル: models.py プロジェクト: benjamin-bergia/inspirehep
class RecordsAuthors(db.Model):
    __tablename__ = "records_authors"
    __table_args__ = (
        db.Index("ix_authors_records_record_id", "record_id"),
        db.Index(
            "ix_authors_records_author_id_id_type_record_id",
            "author_id",
            "id_type",
            "record_id",
        ),
        db.Index("ix_records_authors_id_type_authors_id", "id_type",
                 "author_id"),
    )
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(Text, nullable=False)
    id_type = db.Column(
        ENUM(*[key.value for key in AuthorSchemaType],
             name="enum_author_schema_type"),
        nullable=False,
    )
    record_id = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_authors_records_record_id"),
        nullable=False,
    )
コード例 #4
0
ファイル: models.py プロジェクト: tsgit/inspirehep
class RecordCitations(db.Model):
    """Adds Citation table which holds all references
       which are also eligible citations"""

    __tablename__ = "records_citations"

    __table_args__ = (
        db.Index("ix_records_citations_cited_id_citer_id", "cited_id",
                 "citer_id"),
        db.Index(
            "ix_records_citations_cited_id_citation_type",
            "cited_id",
            "is_self_citation",
        ),
        db.Index(
            "ix_records_citations_citer_id_citation_type",
            "citer_id",
            "is_self_citation",
        ),
    )

    citer_id = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_records_citations_citer_id"),
        nullable=False,
        primary_key=True,
    )
    cited_id = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_records_citations_cited_id"),
        nullable=False,
        primary_key=True,
    )
    citation_date = db.Column(Date)
    # Relationship: Relation to record which cites
    # Backref: List of all references of this record
    # which are counted as citations in other records.
    citer = db.relationship(RecordMetadata,
                            backref="references",
                            foreign_keys=[citer_id])
    # Relationship: Relation to cited article
    # Backref: List of all citations of this record.
    cited = db.relationship(RecordMetadata,
                            backref="citations",
                            foreign_keys=[cited_id])
    is_self_citation = db.Column(Boolean, nullable=False, default=False)
コード例 #5
0
ファイル: models.py プロジェクト: benjamin-bergia/inspirehep
class StudentsAdvisors(db.Model):
    """Links students with their thesis advisors"""

    __tablename__ = "students_advisors"
    __table_args__ = (db.Index("ix_students_advisors_student_id",
                               "student_id"), )

    advisor_id = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_students_advisors_advisor_id"),
        nullable=False,
        primary_key=True,
    )

    student_id = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_students_advisors_student_id"),
        nullable=False,
        primary_key=True,
    )

    degree_type = db.Column(
        ENUM(*[key.value for key in DegreeType], name="enum_degree_type"))
コード例 #6
0
ファイル: models.py プロジェクト: inspirehep/inspirehep
class CDSRun(db.Model):
    __tablename__ = "cds_runs"
    __table_args__ = (db.Index("ix_cds_runs_status_date", "status", "date"), )

    task_id = db.Column(UUIDType, primary_key=True)
    date = db.Column(db.DateTime)
    runtime = db.Column(db.Interval)
    status = db.Column(db.Enum(CDSRunStatus, name="enum_cds_run_status"))
    message = db.Column(db.UnicodeText, default="")

    @classmethod
    def get_last_successful_run(cls):
        return (cls.query.filter_by(status=CDSRunStatus.FINISHED).order_by(
            cls.date.desc()).first())

    @classmethod
    def new_run(cls):
        task_id = uuid.uuid4()
        cds_run = CDSRun(date=datetime.now(),
                         status=CDSRunStatus.RUNNING,
                         task_id=task_id)
        db.session.add(cds_run)
        return task_id

    @classmethod
    def update_status(cls, task_id, status, message=None):
        cds_run = cls.query.filter_by(task_id=task_id).one()
        if message:
            cds_run.message = message

        cds_run.status = status
        cds_run.runtime = datetime.now() - cds_run.date
コード例 #7
0
class ItemTypeName(db.Model, Timestamp):
    """Represent an item type name.

    The ItemTypeName object contains a ``created`` and  a ``updated``
    properties that are automatically updated.
    """

    __tablename__ = 'item_type_name'

    __table_args__ = (db.Index('uq_item_type_name_name',
                               'name',
                               unique=True,
                               postgresql_where=db.Column('is_active')), )

    id = db.Column(db.Integer(), primary_key=True, autoincrement=True)
    """Name identifier of item type."""

    name = db.Column(db.Text, nullable=False)
    """Name of item type."""

    has_site_license = db.Column(db.Boolean(name='has_site_license'),
                                 default=True,
                                 nullable=False)
    """site license identify."""

    is_active = db.Column(db.Boolean(name='active'),
                          nullable=False,
                          default=True)
    """Status of item type."""
コード例 #8
0
class UserIdentity(db.Model, Timestamp):
    """Represent a UserIdentity record."""

    __tablename__ = "accounts_useridentity"

    id = db.Column(db.String(255), primary_key=True, nullable=False)
    method = db.Column(db.String(255), primary_key=True, nullable=False)
    id_user = db.Column(db.Integer(), db.ForeignKey(User.id), nullable=False)

    user = db.relationship(User, backref="external_identifiers")

    __table_args__ = (db.Index("accounts_useridentity_id_user_method",
                               id_user,
                               method,
                               unique=True), )

    @classmethod
    def get_user(cls, method, external_id):
        """Get the user for a given identity."""
        identity = cls.query.filter_by(id=external_id,
                                       method=method).one_or_none()
        if identity is not None:
            return identity.user
        return None

    @classmethod
    def create(cls, user, method, external_id):
        """Link a user to an external id.

        :param user: A :class:`invenio_accounts.models.User` instance.
        :param method: The identity source (e.g. orcid, github)
        :param method: The external identifier.
        :raises AlreadyLinkedError: Raised if already exists a link.
        """
        try:
            with db.session.begin_nested():
                db.session.add(
                    cls(id=external_id, method=method, id_user=user.id))
        except IntegrityError:
            raise AlreadyLinkedError(
                # dict used for backward compatibility (came from oauthclient)
                user,
                {
                    "id": external_id,
                    "method": method
                },
            )

    @classmethod
    def delete_by_external_id(cls, method, external_id):
        """Unlink a user from an external id."""
        with db.session.begin_nested():
            cls.query.filter_by(id=external_id, method=method).delete()

    @classmethod
    def delete_by_user(cls, method, user):
        """Unlink a user from an external id."""
        with db.session.begin_nested():
            cls.query.filter_by(id_user=user.id, method=method).delete()
コード例 #9
0
 def __table_args__(cls):
     """Table args."""
     return (db.Index(
         f'uidx_{cls.__tablename__}_id_key',
         'id',
         'key',
         unique=True,
     ), )
コード例 #10
0
ファイル: models.py プロジェクト: tsgit/inspirehep
class ConferenceLiterature(db.Model):
    """Keeps track of proceedings and contributions linked to a Conference Record."""

    __tablename__ = "conference_literature"
    __table_args__ = (
        db.Index(
            "ix_conference_literature_conference_uuid",
            "conference_uuid",
            "relationship_type",
        ),
        db.Index("ix_conference_literature_literature_uuid",
                 "literature_uuid"),
    )

    conference_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_conference_literature_conference_uuid"),
        nullable=False,
        primary_key=True,
    )
    literature_uuid = db.Column(
        UUIDType,
        db.ForeignKey("records_metadata.id",
                      name="fk_conference_literature_literature_uuid"),
        nullable=False,
        primary_key=True,
    )
    relationship_type = db.Column(
        Enum(
            ConferenceToLiteratureRelationshipType,
            name="enum_conference_to_literature_relationship_type",
        ),
        primary_key=True,
    )

    conference = db.relationship(RecordMetadata,
                                 backref="conference_documents",
                                 foreign_keys=[conference_uuid])

    conference_document = db.relationship(RecordMetadata,
                                          backref="conferences",
                                          foreign_keys=[literature_uuid])
コード例 #11
0
ファイル: models.py プロジェクト: turtle321/inspire-next
class LegacyRecordsMirror(db.Model):
    __tablename__ = 'legacy_records_mirror'

    __table_args__ = (
        db.Index('ix_legacy_records_mirror_valid_collection', 'valid', 'collection'),
    )

    recid = db.Column(db.Integer, primary_key=True)
    last_updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, index=True)
    _marcxml = db.Column('marcxml', db.LargeBinary, nullable=False)
    valid = db.Column(db.Boolean, default=None, nullable=True)
    _errors = db.Column('errors', db.Text(), nullable=True)
    collection = db.Column(db.Text(), default='')

    re_recid = re.compile('<controlfield.*?tag=.001.*?>(?P<recid>\d+)</controlfield>')

    @hybrid_property
    def marcxml(self):
        """marcxml column wrapper to compress/decompress on the fly."""
        try:
            return decompress(self._marcxml)
        except error:
            # Legacy uncompress data?
            return self._marcxml

    @marcxml.setter
    def marcxml(self, value):
        self._marcxml = compress(value)

    @hybrid_property
    def error(self):
        return self._errors

    @error.setter
    def error(self, value):
        """Errors column setter that stores an Exception and sets the ``valid`` flag."""
        self.valid = False
        self.collection = get_collection_from_marcxml(self.marcxml)
        self._errors = u'{}: {}'.format(type(value).__name__, value)

    @classmethod
    def from_marcxml(cls, raw_record):
        """Create an instance from a MARCXML record.

        The record must have a ``001`` tag containing the recid, otherwise it raises a ValueError.
        """
        try:
            recid = int(cls.re_recid.search(raw_record).group('recid'))
        except AttributeError:
            raise ValueError('The MARCXML record contains no recid or recid is malformed')
        # FIXME also get last_updated from marcxml
        record = cls(recid=recid)
        record.marcxml = raw_record
        record.valid = None
        return record
コード例 #12
0
    class UserIdentity(db.Model, Timestamp):
        """Represent a UserIdentity record."""

        __tablename__ = "accounts_useridentity"

        id = db.Column(db.String(255), primary_key=True, nullable=False)
        method = db.Column(db.String(255), primary_key=True, nullable=False)
        id_user = db.Column(db.Integer(), db.ForeignKey(User.id), nullable=False)

        user = db.relationship(User, backref="external_identifiers")

        __table_args__ = (
            db.Index("useridentity_id_user_method", id_user, method, unique=True),
        )
コード例 #13
0
class UserIdentity(db.Model):
    """Represent a UserIdentity record."""

    __tablename__ = 'oauthclient_useridentity'

    id = db.Column(db.String(255), primary_key=True, nullable=False)
    method = db.Column(db.String(255), primary_key=True, nullable=False)
    id_user = db.Column(db.Integer(), db.ForeignKey(User.id), nullable=False)

    user = db.relationship(User, backref='external_identifiers')

    __table_args__ = (db.Index('useridentity_id_user_method',
                               id_user,
                               method,
                               unique=True), )
コード例 #14
0
class CommunityRecord(db.Model, RecordMetadataBase):
    """Comunity-record relationship model."""

    __tablename__ = 'communities_community_record'
    __table_args__ = (
        db.Index(
            'uidx_community_pid_record_pid',
            'community_pid_id', 'record_pid_id',
            unique=True,
        ),
        {'extend_existing': True},
    )
    __versioned__ = {'versioning': False}

    community_pid_id = db.Column(
        db.Integer,
        db.ForeignKey(PersistentIdentifier.id),
        nullable=False,
    )

    record_pid_id = db.Column(
        db.Integer,
        db.ForeignKey(PersistentIdentifier.id),
        nullable=False,
    )

    request_id = db.Column(
        UUIDType,
        db.ForeignKey(Request.id),
        # TODO: should we also allow CommunityRecords without a request?
        nullable=False,
    )

    status = db.Column(
        ChoiceType(CommunityRecordStatus, impl=db.CHAR(1)),
        nullable=False,
        default=CommunityRecordStatus.PENDING,
    )

    community_pid = db.relationship(
        PersistentIdentifier,
        foreign_keys=[community_pid_id],
    )
    record_pid = db.relationship(
        PersistentIdentifier,
        foreign_keys=[record_pid_id],
    )

    request = db.relationship(Request)

    @property
    def commmunity(self):
        """Return community model."""
        # TODO: make a JOIN instead?
        return CommunityMetadata.query.get(self.community_pid.object_uuid)

    @classmethod
    def create(cls, community_pid_id, record_pid_id, request_id,
               status=None, json=None):
        try:
            with db.session.begin_nested():
                # TODO: check if status None works with default
                obj = cls(
                    community_pid_id=community_pid_id,
                    record_pid_id=record_pid_id,
                    request_id=request_id,
                    status=status,
                    json=json,
                )
                db.session.add(obj)
        # TODO: Check if actually this constraint check happens on the DB side
        #       when db.session.add() is called.
        except IntegrityError:
            raise CommunityRecordAlreadyExists(
                community_pid_id=community_pid_id,
                record_pid_id=record_pid_id,
            )
        return obj

    @classmethod
    def delete(cls, community_record):
        """Delete community record relationship."""
        with db.session.begin_nested():
            db.session.delete(community_record)
コード例 #15
0
ファイル: models.py プロジェクト: tsgit/inspirehep
class LegacyRecordsMirror(db.Model):
    __tablename__ = "legacy_records_mirror"

    __table_args__ = (db.Index("ix_legacy_records_mirror_valid_collection",
                               "valid", "collection"), )

    recid = db.Column(db.Integer, primary_key=True)
    last_updated = db.Column(db.DateTime,
                             default=datetime.utcnow,
                             nullable=False,
                             index=True)
    _marcxml = db.Column("marcxml", db.LargeBinary, nullable=False)
    valid = db.Column(db.Boolean, default=None, nullable=True)
    _errors = db.Column("errors", db.Text(), nullable=True)
    collection = db.Column(db.Text(), default="")

    re_recid = re.compile(
        r"<controlfield.*?tag=.001.*?>(?P<recid>\d+)</controlfield>")

    @hybrid_property
    def marcxml(self):
        """marcxml column wrapper to compress/decompress on the fly."""
        try:
            return decompress(self._marcxml)
        except error:
            # Legacy uncompress data?
            return self._marcxml

    @marcxml.setter
    def marcxml(self, value):
        if isinstance(value, str):
            self._marcxml = compress(bytes(value, "utf8"))
        else:
            self._marcxml = compress(value)

    @hybrid_property
    def error(self):
        return self._errors

    @error.setter
    def error(self, value):
        """Errors column setter that stores an Exception and sets the ``valid`` flag."""
        self.valid = False
        self.collection = get_collection_from_marcxml(self.marcxml)
        value_type = type(value).__name__
        self._errors = f"{value_type}: {value}"

    @classmethod
    def from_marcxml(cls, raw_record):
        """Create an instance from a MARCXML record.
        The record must have a ``001`` tag containing the recid, otherwise it raises a ValueError.
        """
        try:
            recid = int(cls.re_recid.search(str(raw_record)).group("recid"))
        except AttributeError:
            raise ValueError(
                "The MARCXML record contains no recid or recid is malformed")
        # FIXME also get last_updated from marcxml
        record = cls(recid=recid)
        record.marcxml = raw_record
        record.valid = None
        return record
コード例 #16
0
class Archive(db.Model, Timestamp):
    """Registers the status of a sip: archived or not.

    The status is a member of
    :py:class:`invenio_archivematica.models.ArchiveStatus`.

    A sip can have only one archive, and an archive applies to only
    one sip.
    """

    __tablename__ = 'archivematica_archive'
    __table_args__ = (db.Index('idx_ark_sip',
                               'sip_id'), db.Index('idx_ark_status', 'status'),
                      db.Index('idx_ark_accession_id', 'accession_id'))

    id = db.Column(db.BigInteger().with_variant(db.Integer, 'sqlite'),
                   primary_key=True)
    """ID of the Archive object."""

    sip_id = db.Column(UUIDType,
                       db.ForeignKey(SIP.id, name='fk_archivematica_sip_id'),
                       nullable=False)
    """SIP related with the Archive."""

    status = db.Column(ChoiceType(ArchiveStatus, impl=db.String(20)),
                       nullable=False)
    """Status of the archive."""

    accession_id = db.Column(db.String(255), nullable=True, unique=True)
    """Accessioned ID of the AIP in Archivematica."""

    archivematica_id = db.Column(UUIDType, nullable=True)
    """ID of the AIP in Archivematica."""

    # Relations
    sip = db.relationship(SIP)
    """Relationship with SIP."""

    #
    # Class methods
    #
    @classmethod
    def create(cls, sip, accession_id=None, archivematica_id=None):
        """Create a new Archive object and add it to the session.

        The new Archive object will have a NEW status

        :param sip: the sip attached to the archive
        :type sip: :py:class:`invenio_sipstore.models.SIP`
        :param str accession_id: the accession ID of the AIP
        :param str archivematica_id: The UUID of the AIP
        """
        ark = cls(sip=sip,
                  status=ArchiveStatus.NEW,
                  accession_id=accession_id,
                  archivematica_id=archivematica_id)
        db.session.add(ark)
        return ark

    @classmethod
    def get_from_sip(cls, uuid):
        """Return the Archive object associated to the given sip.

        It tries to get the Archive object associated to the sip. If it
        exists, it returns it, otherwise it returns None.

        :param str uuid: the uuid of the sip
        :rtype: :py:class:`invenio_archivematica.models.Archive` or None
        """
        return cls.query.filter_by(sip_id=uuid).one_or_none()

    @classmethod
    def get_from_accession_id(cls, accession_id):
        """Return the Archive object associated to the given accession_id.

        If the accession_id is not in the table, it returns None.

        :param str accession_id: the accession_id of the Archive object.
        :rtype: :py:class:`invenio_archivematica.models.Archive` or None
        """
        return cls.query.filter_by(accession_id=accession_id).one_or_none()
コード例 #17
0
ファイル: models.py プロジェクト: N03/invenio
class PersistentIdentifier(db.Model, Timestamp):
    """Store and register persistent identifiers.

    Assumptions:
      * Persistent identifiers can be represented as a string of max 255 chars.
      * An object has many persistent identifiers.
      * A persistent identifier has one and only one object.
    """

    __tablename__ = 'pidstore_pid'
    __table_args__ = (
        db.Index('uidx_type_pid', 'pid_type', 'pid_value', unique=True),
        db.Index('idx_status', 'status'),
        db.Index('idx_object', 'object_type', 'object_uuid'),
    )

    id = db.Column(db.Integer, primary_key=True)
    """Id of persistent identifier entry."""

    pid_type = db.Column(db.String(6), nullable=False)
    """Persistent Identifier Schema."""

    pid_value = db.Column(db.String(255), nullable=False)
    """Persistent Identifier."""

    pid_provider = db.Column(db.String(8), nullable=True)
    """Persistent Identifier Provider"""

    status = db.Column(ChoiceType(PIDStatus, impl=db.CHAR(1)), nullable=False)
    """Status of persistent identifier, e.g. registered, reserved, deleted."""

    object_type = db.Column(db.String(3), nullable=True)
    """Object Type - e.g. rec for record."""

    object_uuid = db.Column(UUIDType, nullable=True)
    """Object ID - e.g. a record id."""

    #
    # Class methods
    #
    @classmethod
    def create(
        cls,
        pid_type,
        pid_value,
        pid_provider=None,
        status=PIDStatus.NEW,
        object_type=None,
        object_uuid=None,
    ):
        """Create a new persistent identifier with specific type and value.

        :param pid_type: Persistent identifier type.
        :param pid_value: Persistent identifier value.
        :param pid_provider: Persistent identifier provider. (default: None).
        :param status: Current PID status.
            (Default: :attr:`invenio_pidstore.models.PIDStatus.NEW`)
        :param object_type: The object type is a string that identify its type.
            (default: None).
        :param object_uuid: The object UUID. (default: None).
        :returns: A :class:`invenio_pidstore.models.PersistentIdentifier`
            instance.
        """
        try:
            with db.session.begin_nested():
                obj = cls(pid_type=pid_type,
                          pid_value=pid_value,
                          pid_provider=pid_provider,
                          status=status)
                if object_type and object_uuid:
                    obj.assign(object_type, object_uuid)
                db.session.add(obj)
            logger.info("Created PID {0}:{1}".format(pid_type, pid_value),
                        extra={'pid': obj})
        except IntegrityError:
            logger.exception("PID already exists: {0}:{1}".format(
                pid_type, pid_value),
                             extra=dict(
                                 pid_type=pid_type,
                                 pid_value=pid_value,
                                 pid_provider=pid_provider,
                                 status=status,
                                 object_type=object_type,
                                 object_uuid=object_uuid,
                             ))
            raise PIDAlreadyExists(pid_type=pid_type, pid_value=pid_value)
        except SQLAlchemyError:
            logger.exception("Failed to create PID: {0}:{1}".format(
                pid_type, pid_value),
                             extra=dict(
                                 pid_type=pid_type,
                                 pid_value=pid_value,
                                 pid_provider=pid_provider,
                                 status=status,
                                 object_type=object_type,
                                 object_uuid=object_uuid,
                             ))
            raise
        return obj

    @classmethod
    def get(cls, pid_type, pid_value, pid_provider=None):
        """Get persistent identifier.

        :param pid_type: Persistent identifier type.
        :param pid_value: Persistent identifier value.
        :param pid_provider: Persistent identifier provider. (default: None).
        :raises: :exc:`invenio_pidstore.errors.PIDDoesNotExistError` if no
            PID is found.
        :returns: A :class:`invenio_pidstore.models.PersistentIdentifier`
            instance.
        """
        try:
            args = dict(pid_type=pid_type, pid_value=six.text_type(pid_value))
            if pid_provider:
                args['pid_provider'] = pid_provider
            return cls.query.filter_by(**args).one()
        except NoResultFound:
            raise PIDDoesNotExistError(pid_type, pid_value)

    @classmethod
    def get_by_object(cls, pid_type, object_type, object_uuid):
        """Get a persistent identifier for a given object.

        :param pid_type: Persistent identifier type.
        :param object_type: The object type is a string that identify its type.
        :param object_uuid: The object UUID.
        :raises invenio_pidstore.errors.PIDDoesNotExistError: If no PID is
            found.
        :returns: A :class:`invenio_pidstore.models.PersistentIdentifier`
            instance.
        """
        try:
            return cls.query.filter_by(pid_type=pid_type,
                                       object_type=object_type,
                                       object_uuid=object_uuid).one()
        except NoResultFound:
            raise PIDDoesNotExistError(pid_type, None)

    #
    # Assigned object methods
    #
    def has_object(self):
        """Determine if this PID has an assigned object.

        :returns: `True` if the PID has a object assigned.
        """
        return bool(self.object_type and self.object_uuid)

    def get_assigned_object(self, object_type=None):
        """Return the current assigned object UUID.

        :param object_type: If it's specified, returns only if the PID
            object_type is the same, otherwise returns None. (default: None).
        :returns: The object UUID.
        """
        if object_type is not None:
            if self.object_type == object_type:
                return self.object_uuid
            else:
                return None
        return self.object_uuid

    def assign(self, object_type, object_uuid, overwrite=False):
        """Assign this persistent identifier to a given object.

        Note, the persistent identifier must first have been reserved. Also,
        if an existing object is already assigned to the pid, it will raise an
        exception unless overwrite=True.

        :param object_type: The object type is a string that identify its type.
        :param object_uuid: The object UUID.
        :param overwrite: Force PID overwrites in case was previously assigned.
        :raises invenio_pidstore.errors.PIDInvalidAction: If the PID was
            previously deleted.
        :raises invenio_pidstore.errors.PIDObjectAlreadyAssigned: If the PID
            was previously assigned with a different type/uuid.
        :returns: `True` if the PID is successfully assigned.
        """
        if self.is_deleted():
            raise PIDInvalidAction(
                "You cannot assign objects to a deleted/redirected persistent"
                " identifier.")

        if not isinstance(object_uuid, uuid.UUID):
            object_uuid = uuid.UUID(object_uuid)

        if self.object_type or self.object_uuid:
            # The object is already assigned to this pid.
            if object_type == self.object_type and \
               object_uuid == self.object_uuid:
                return True
            if not overwrite:
                raise PIDObjectAlreadyAssigned(object_type, object_uuid)
            self.unassign()

        try:
            with db.session.begin_nested():
                self.object_type = object_type
                self.object_uuid = object_uuid
                db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to assign {0}:{1}".format(
                object_type, object_uuid),
                             extra=dict(pid=self))
            raise
        logger.info("Assigned object {0}:{1}".format(object_type, object_uuid),
                    extra=dict(pid=self))
        return True

    def unassign(self):
        """Unassign the registered object.

        Note:
        Only registered PIDs can be redirected so we set it back to registered.

        :returns: `True` if the PID is successfully unassigned.
        """
        if self.object_uuid is None and self.object_type is None:
            return True

        try:
            with db.session.begin_nested():
                if self.is_redirected():
                    db.session.delete(Redirect.query.get(self.object_uuid))
                    # Only registered PIDs can be redirected so we set it back
                    # to registered
                    self.status = PIDStatus.REGISTERED
                self.object_type = None
                self.object_uuid = None
                db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to unassign object.".format(self),
                             extra=dict(pid=self))
            raise
        logger.info("Unassigned object from {0}.".format(self),
                    extra=dict(pid=self))
        return True

    def get_redirect(self):
        """Get redirected persistent identifier.

        :returns: The :class:`invenio_pidstore.models.PersistentIdentifier`
            instance.
        """
        return Redirect.query.get(self.object_uuid).pid

    #
    # Status methods.
    #
    def redirect(self, pid):
        """Redirect persistent identifier to another persistent identifier.

        :param pid: The :class:`invenio_pidstore.models.PersistentIdentifier`
            where redirect the PID.
        :raises invenio_pidstore.errors.PIDInvalidAction: If the PID is not
            registered or is not already redirecting to another PID.
        :raises invenio_pidstore.errors.PIDDoesNotExistError: If PID is not
            found.
        :returns: `True` if the PID is successfully redirect.
        """
        if not (self.is_registered() or self.is_redirected()):
            raise PIDInvalidAction("Persistent identifier is not registered.")

        try:
            with db.session.begin_nested():
                if self.is_redirected():
                    r = Redirect.query.get(self.object_uuid)
                    r.pid = pid
                else:
                    with db.session.begin_nested():
                        r = Redirect(pid=pid)
                        db.session.add(r)

                self.status = PIDStatus.REDIRECTED
                self.object_type = None
                self.object_uuid = r.id
                db.session.add(self)
        except IntegrityError:
            raise PIDDoesNotExistError(pid.pid_type, pid.pid_value)
        except SQLAlchemyError:
            logger.exception("Failed to redirect to {0}".format(pid),
                             extra=dict(pid=self))
            raise
        logger.info("Redirected PID to {0}".format(pid), extra=dict(pid=self))
        return True

    def reserve(self):
        """Reserve the persistent identifier.

        Note, the reserve method may be called multiple times, even if it was
        already reserved.

        :raises: :exc:`invenio_pidstore.errors.PIDInvalidAction` if the PID is
            not new or is not already reserved a PID.
        :returns: `True` if the PID is successfully reserved.
        """
        if not (self.is_new() or self.is_reserved()):
            raise PIDInvalidAction(
                "Persistent identifier is not new or reserved.")

        try:
            with db.session.begin_nested():
                self.status = PIDStatus.RESERVED
                db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to reserve PID.", extra=dict(pid=self))
            raise
        logger.info("Reserved PID.", extra=dict(pid=self))
        return True

    def register(self):
        """Register the persistent identifier with the provider.

        :raises invenio_pidstore.errors.PIDInvalidAction: If the PID is not
            already registered or is deleted or is a redirection to another
            PID.
        :returns: `True` if the PID is successfully register.
        """
        if self.is_registered() or self.is_deleted() or self.is_redirected():
            raise PIDInvalidAction(
                "Persistent identifier has already been registered"
                " or is deleted.")

        try:
            with db.session.begin_nested():
                self.status = PIDStatus.REGISTERED
                db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to register PID.", extra=dict(pid=self))
            raise
        logger.info("Registered PID.", extra=dict(pid=self))
        return True

    def delete(self):
        """Delete the persistent identifier.

        If the persistent identifier haven't been registered yet, it is
        removed from the database.
        Otherwise, it's marked as
        :data:`invenio_pidstore.models.PIDStatus.DELETED`.
        :returns: `True` if the PID is successfully removed.
        """
        removed = False
        try:
            with db.session.begin_nested():
                if self.is_new():
                    # New persistent identifier which haven't been registered
                    # yet.
                    db.session.delete(self)
                    removed = True
                else:
                    self.status = PIDStatus.DELETED
                    db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to delete PID.", extra=dict(pid=self))
            raise

        if removed:
            logger.info("Deleted PID (removed).", extra=dict(pid=self))
        else:
            logger.info("Deleted PID.", extra=dict(pid=self))
        return True

    def sync_status(self, status):
        """Synchronize persistent identifier status.

        Used when the provider uses an external service, which might have been
        modified outside of our system.

        :param status: The new status to set.
        :returns: `True` if the PID is successfully sync.
        """
        if self.status == status:
            return True

        try:
            with db.session.begin_nested():
                self.status = status
                db.session.add(self)
        except SQLAlchemyError:
            logger.exception("Failed to sync status {0}.".format(status),
                             extra=dict(pid=self))
            raise
        logger.info("Synced PID status to {0}.".format(status),
                    extra=dict(pid=self))
        return True

    def is_redirected(self):
        """Return true if the persistent identifier has been registered."""
        return self.status == PIDStatus.REDIRECTED

    def is_registered(self):
        """Return true if the persistent identifier has been registered.

        :returns: A :class:`invenio_pidstore.models.PIDStatus` status.
        """
        return self.status == PIDStatus.REGISTERED

    def is_deleted(self):
        """Return true if the persistent identifier has been deleted.

        :returns: A boolean value.
        """
        return self.status == PIDStatus.DELETED

    def is_new(self):
        """Return true if the PIDhas not yet been registered or reserved.

        :returns: A boolean value.
        """
        return self.status == PIDStatus.NEW

    def is_reserved(self):
        """Return true if the PID has not yet been reserved.

        :returns: A boolean value.
        """
        return self.status == PIDStatus.RESERVED

    def __repr__(self):
        """Get representation of object."""
        return "<PersistentIdentifier {0}:{1}{3} ({2})>".format(
            self.pid_type, self.pid_value, self.status,
            " / {0}:{1}".format(self.object_type, self.object_uuid)
            if self.object_type else "")