class GroupRelationshipM2M(db.Model, Timestamp): """Many-to-many model for Group Relationships.""" __tablename__ = 'grouprelationshipm2m' __table_args__ = ( PrimaryKeyConstraint('relationship_id', 'subrelationship_id', name='pk_grouprelationshipm2m'), ) relationship_id = Column(UUIDType, ForeignKey(GroupRelationship.id, onupdate="CASCADE", ondelete="CASCADE"), nullable=False) subrelationship_id = Column(UUIDType, ForeignKey(GroupRelationship.id, onupdate="CASCADE", ondelete="CASCADE"), nullable=False) relationship = orm_relationship(GroupRelationship, foreign_keys=[relationship_id], backref='subrelationshipsm2m') subrelationship = orm_relationship(GroupRelationship, foreign_keys=[subrelationship_id], backref='superrelationshipsm2m') def __repr__(self): """String representation of the model.""" return ('<{self.relationship}: {self.subrelationship}>' .format(self=self))
class Relationship2GroupRelationship(db.Model, Timestamp): """Many-to-many model for Relationship to GroupRelationship.""" __tablename__ = 'relationship2grouprelationship' __table_args__ = ( PrimaryKeyConstraint('relationship_id', 'group_relationship_id', name='pk_relationship2grouprelationship'), ) relationship_id = Column(UUIDType, ForeignKey(Relationship.id, onupdate='CASCADE', ondelete='CASCADE'), nullable=False) group_relationship_id = Column(UUIDType, ForeignKey(GroupRelationship.id, onupdate='CASCADE', ondelete='CASCADE'), nullable=False) # DB relationships relationship = orm_relationship(Relationship, foreign_keys=[relationship_id]) group_relationship = orm_relationship(GroupRelationship, foreign_keys=[group_relationship_id]) def __repr__(self): """String representation of the model.""" return ('<{self.group_relationship}: {self.relationship}>' .format(self=self))
class GroupRelationship(db.Model, Timestamp): """Group relationship model.""" __tablename__ = 'grouprelationship' __table_args__ = ( UniqueConstraint('source_id', 'target_id', 'relation', name='uq_grouprelationship_source_target_relation'), Index('ix_grouprelationship_source', 'source_id'), Index('ix_grouprelationship_target', 'target_id'), Index('ix_grouprelationship_relation', 'relation'), ) id = Column(UUIDType, default=uuid.uuid4, primary_key=True) type = Column(Enum(GroupType), nullable=False) relation = Column(Enum(Relation), nullable=False) source_id = Column(UUIDType, ForeignKey(Group.id, ondelete='CASCADE', onupdate='CASCADE'), nullable=False) target_id = Column(UUIDType, ForeignKey(Group.id, ondelete='CASCADE', onupdate='CASCADE'), nullable=False) # DB relationships source = orm_relationship( Group, foreign_keys=[source_id], backref='sources') target = orm_relationship( Group, foreign_keys=[target_id], backref='targets') relationships = orm_relationship( 'GroupRelationship', secondary=lambda: GroupRelationshipM2M.__table__, primaryjoin=lambda: (GroupRelationship.id == GroupRelationshipM2M.relationship_id), secondaryjoin=lambda: (GroupRelationship.id == GroupRelationshipM2M.subrelationship_id)) # TODO: # We don't store 'deleted' as in the relation as most likely don't need # that as 'ground truth' in precomputed groups anyway def __repr__(self): """String representation of the group relationship.""" return ('<{self.source} {self.relation.name} {self.target}>' .format(self=self))
class GroupRelationshipMetadata(db.Model, Timestamp): """Metadata for a group relationship.""" __tablename__ = 'grouprelationshipmetadata' # TODO: assert group_relationship.type == GroupType.Identity group_relationship_id = Column( UUIDType, ForeignKey(GroupRelationship.id, onupdate='CASCADE', ondelete='CASCADE'), primary_key=True) group_relationship = orm_relationship( GroupRelationship, backref=backref('data', uselist=False), single_parent=True, ) json = Column( JSON() .with_variant(postgresql.JSONB(none_as_null=True), 'postgresql') .with_variant(JSONType(), 'sqlite'), default=list, ) # Relationship metadata SCHEMA = { '$schema': 'http://json-schema.org/draft-06/schema#', 'definitions': COMMON_SCHEMA_DEFINITIONS, 'type': 'array', 'items': { 'type': 'object', 'additionalProperties': False, 'properties': { 'LinkPublicationDate': {'$ref': '#/definitions/DateType'}, 'LinkProvider': { 'type': 'array', 'items': {'$ref': '#/definitions/PersonOrOrgType'} }, 'LicenseURL': {'type': 'string'}, }, 'required': ['LinkPublicationDate', 'LinkProvider'], } } def update(self, payload, validate=True, multi=False): """Updates the metadata of a group relationship.""" new_json = deepcopy(self.json or []) if multi: new_json.extend(payload) else: new_json.append(payload) if validate: jsonschema.validate(new_json, self.SCHEMA) self.json = new_json flag_modified(self, 'json') return self
class Identifier2Group(db.Model, Timestamp): """Many-to-many model for Identifier and Group.""" __tablename__ = 'identifier2group' __table_args__ = ( PrimaryKeyConstraint('identifier_id', 'group_id', name='pk_identifier2group'), ) identifier_id = Column(UUIDType, ForeignKey(Identifier.id, ondelete='CASCADE', onupdate='CASCADE'), nullable=False) group_id = Column(UUIDType, ForeignKey(Group.id, ondelete='CASCADE', onupdate='CASCADE'), nullable=False) # DB relationships identifier = orm_relationship(Identifier, foreign_keys=[identifier_id], backref='id2groups') group = orm_relationship(Group, foreign_keys=[group_id], backref='id2groups')
class Group(db.Model, Timestamp): """Group model.""" __tablename__ = 'group' id = Column(UUIDType, default=uuid.uuid4, primary_key=True) type = Column(Enum(GroupType), nullable=False) identifiers = orm_relationship( Identifier, secondary=lambda: Identifier2Group.__table__, backref='groups', viewonly=True) groups = orm_relationship( 'Group', secondary=lambda: GroupM2M.__table__, primaryjoin=lambda: (Group.id == GroupM2M.group_id), secondaryjoin=lambda: (Group.id == GroupM2M.subgroup_id)) def __repr__(self): """String representation of the group.""" return "<{self.id}: {self.type.name}>".format(self=self)
class GroupMetadata(db.Model, Timestamp): """Metadata for a group.""" __tablename__ = 'groupmetadata' # TODO: assert group.type == GroupType.Identity group_id = Column( UUIDType, ForeignKey(Group.id, onupdate='CASCADE', ondelete='CASCADE'), primary_key=True) group = orm_relationship( Group, backref=backref('data', uselist=False), single_parent=True, ) json = Column( JSON() .with_variant(postgresql.JSONB(none_as_null=True), 'postgresql') .with_variant(JSONType(), 'sqlite'), default=dict, ) # Identifier metadata SCHEMA = { '$schema': 'http://json-schema.org/draft-06/schema#', 'definitions': COMMON_SCHEMA_DEFINITIONS, 'additionalProperties': False, 'properties': { k: v for k, v in OBJECT_TYPE_SCHEMA['properties'].items() if k in OVERRIDABLE_KEYS }, } def update(self, payload, validate=True): """Update the metadata of a group.""" new_json = deepcopy(self.json or {}) for k in OVERRIDABLE_KEYS: if payload.get(k): if k == 'Type' and not _is_type_overridable(payload): continue new_json[k] = payload[k] if validate: jsonschema.validate(new_json, self.SCHEMA) self.json = new_json flag_modified(self, 'json') return self
class Relationship(db.Model, Timestamp): """Relationship between two identifiers.""" __tablename__ = 'relationship' __table_args__ = ( UniqueConstraint('source_id', 'target_id', 'relation', name='uq_relationship_source_target_relation'), Index('ix_relationship_source', 'source_id'), Index('ix_relationship_target', 'target_id'), Index('ix_relationship_relation', 'relation'), ) id = Column(UUIDType, default=uuid.uuid4, primary_key=True) source_id = Column(UUIDType, ForeignKey(Identifier.id, onupdate='CASCADE', ondelete='CASCADE', name='fk_relationship_source'), nullable=False) target_id = Column(UUIDType, ForeignKey(Identifier.id, onupdate='CASCADE', ondelete='CASCADE', name='fk_relationship_target'), nullable=False) relation = Column(Enum(Relation)) source = orm_relationship(Identifier, foreign_keys=[source_id], backref='sources') target = orm_relationship(Identifier, foreign_keys=[target_id], backref='targets') @classmethod def get(cls, source, target, relation, **kwargs): """Get the relationship from the database.""" return cls.query.filter_by( source_id=source.id, target_id=target.id, relation=relation).one_or_none() def fetch_or_create_id(self): """Fetches from the database or creates an id for the relationship.""" self.source = self.source.fetch_or_create_id() self.target = self.target.fetch_or_create_id() if not self.id: obj = self.get(self.source, self.target, self.relation) if obj: self = obj else: self.id = uuid.uuid4() return self @property def identity_group(self): """Get the relationship's identity group.""" return GroupRelationship.query.filter_by( source=self.source.identity_group, target=self.target.identity_group, relation=self.relation, type=GroupType.Identity).one_or_none() @property def data(self): """Get the relationship's identity group metadata.""" if self.identity_group and self.identity_group.data: return self.identity_group.data.json def __repr__(self): """String representation of the relationship.""" return ( '<{self.source.value} {self.relation.name} ' '{self.target.value}>'.format(self=self) )