from __future__ import annotations import typing as T from datetime import datetime from pycs import db from pycs.database.base import NamedBaseModel from pycs.database.util import commit_on_return def compare_children(start_label: Label, identifier: int) -> bool: """ check for cyclic relationships """ labels_to_check = [start_label] while labels_to_check: label = labels_to_check.pop(0) if label.id == identifier: return False labels_to_check.extend(label.children) return True def _label_id(): return Label.id class Label(NamedBaseModel): """ DB Model for labels """ id = db.Column(db.Integer, primary_key=True) project_id = db.Column( db.Integer, db.ForeignKey("project.id", ondelete="CASCADE"), nullable=False) parent_id = db.Column( db.Integer, db.ForeignKey("label.id", ondelete="SET NULL")) created = db.Column(db.DateTime, default=datetime.utcnow, index=True, nullable=False) reference = db.Column(db.String) hierarchy_level = db.Column(db.String) # contraints __table_args__ = ( db.UniqueConstraint('project_id', 'reference'), ) # relationships to other models parent = db.relationship("Label", backref="children", remote_side=_label_id, ) results = db.relationship("Result", backref="label", passive_deletes=True, lazy="dynamic", ) serialize_only = NamedBaseModel.serialize_only + ( "project_id", "parent_id", "reference", "hierarchy_level", # "children", ) @commit_on_return def set_parent(self, parent: T.Optional[T.Union[int, str, Label]] = None) -> None: """ set this labels parent :param parent: parent label. Can be a reference, an id or a Label instance :return: """ parent_id = None if parent is not None: if isinstance(parent, Label): parent_id = parent.id elif isinstance(parent, str): parent_id = Label.query.filter(Label.reference == parent).one().id elif isinstance(parent, int): parent_id = parent if not compare_children(self, parent_id): raise ValueError('Cyclic relationship detected!') self.parent_id = parent_id