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])
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])
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, )
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)
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"))
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
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."""
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()
def __table_args__(cls): """Table args.""" return (db.Index( f'uidx_{cls.__tablename__}_id_key', 'id', 'key', unique=True, ), )
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])
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
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), )
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), )
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)
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
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()
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 "")