class Artifact(Model, FileMixin): __tablename__ = "artifacts" result_id = Column(PortableUUID(), ForeignKey("results.id"), index=True) run_id = Column(PortableUUID(), ForeignKey("runs.id"), index=True) filename = Column(Text, index=True) data = Column(mutable_json_type(dbtype=PortableJSON(), nested=True)) upload_date = Column(DateTime, default=datetime.utcnow, index=True)
class Import(Model, ModelMixin): __tablename__ = "imports" file = relationship("ImportFile") filename = Column(Text, index=True) format = Column(Text, index=True) data = Column(mutable_json_type(dbtype=PortableJSON(), nested=True)) status = Column(Text, index=True)
def upgrade_1(session): """Version 1 upgrade This upgrade adds a dashboard_id to the widget_configs table """ engine = session.get_bind() op = get_upgrade_op(session) metadata = MetaData() metadata.reflect(bind=engine) widget_configs = metadata.tables.get("widget_configs") if ("dashboards" in metadata.tables and widget_configs is not None and "dashboard_id" not in [col.name for col in widget_configs.columns]): op.add_column( "widget_configs", Column("dashboard_id", PortableUUID, server_default=null())) if engine.url.get_dialect().name != "sqlite": # SQLite doesn't support ALTER TABLE ADD CONSTRAINT op.create_foreign_key( "fk_widget_configs_dashboard_id", "widget_configs", "dashboards", ["dashboard_id"], ["id"], ) if metadata.tables["projects"].columns["owner_id"].type in [ "TEXT", "CLOB" ]: op.alter_column( "projects", "owner_id", schema=Column(PortableUUID(), ForeignKey("users.id"), index=True), )
class ReportFile(Model, FileMixin): __tablename__ = "report_files" report_id = Column(PortableUUID(), ForeignKey("reports.id"), nullable=False, index=True) filename = Column(Text, index=True) data = Column(mutable_json_type(dbtype=PortableJSON(), nested=True))
class Dashboard(Model, ModelMixin): __tablename__ = "dashboards" title = Column(Text, index=True) description = Column(Text, default="") filters = Column(Text, default="") project_id = Column(PortableUUID(), ForeignKey("projects.id"), index=True) user_id = Column(PortableUUID(), ForeignKey("users.id"), index=True) widgets = relationship("WidgetConfig")
class Meta(Model): """Metadata about the table This is a simple table used for storing metadata about the database itself. This is mostly used for the database versioning, but expandable if we want to use it for other things. """ __tablename__ = "meta" key = Column(Text, primary_key=True, nullable=False, index=True) value = Column(Text)
class Report(Model, ModelMixin): __tablename__ = "reports" created = Column(DateTime, default=datetime.utcnow, index=True) download_url = Column(Text, index=True) filename = Column(Text, index=True) mimetype = Column(Text, index=True) name = Column(Text, index=True) params = Column(mutable_json_type(dbtype=PortableJSON())) project_id = Column(PortableUUID(), ForeignKey("projects.id"), index=True) file = relationship("ReportFile") status = Column(Text, index=True) url = Column(Text, index=True) view_url = Column(Text, index=True)
class ModelMixin(object): id = Column(PortableUUID(), primary_key=True, default=_gen_uuid, unique=True, nullable=False) def to_dict(self): record_dict = { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs } # when outputting info, translate data to metadata if "data" in record_dict: record_dict["metadata"] = record_dict.pop("data") or {} return record_dict @classmethod def from_dict(cls, **record_dict): # because metadata is a reserved attr name, translate it to data if "metadata" in record_dict: record_dict["data"] = record_dict.pop("metadata") or {} return cls(**record_dict) def update(self, record_dict): if "id" in record_dict: record_dict.pop("id") values_dict = self.to_dict() values_dict.update(record_dict) if "metadata" in values_dict: values_dict["data"] = values_dict.pop("metadata") for key, value in values_dict.items(): setattr(self, key, value)
def upgrade_3(session): """Version 3 upgrade This upgrade: - makes the 'result_id' column of artifacts nullable - adds a 'run_id' to the artifacts table """ engine = session.get_bind() op = get_upgrade_op(session) metadata = MetaData() metadata.reflect(bind=engine) artifacts = metadata.tables.get("artifacts") if ("runs" in metadata.tables and artifacts is not None and "run_id" not in [col.name for col in artifacts.columns]): op.alter_column("artifacts", "result_id", nullable=True, server_default=null()) op.add_column("artifacts", Column("run_id", PortableUUID, server_default=null())) if engine.url.get_dialect().name != "sqlite": # SQLite doesn't support ALTER TABLE ADD CONSTRAINT op.create_foreign_key( "fk_artifacts_run_id", "artifacts", "runs", ["run_id"], ["id"], )
class User(Model, ModelMixin): __tablename__ = "users" email = Column(Text, index=True, nullable=False, unique=True) _password = Column(Text, nullable=True) name = Column(Text) activation_code = Column(Text, nullable=True) is_superadmin = Column(Boolean, default=False) is_active = Column(Boolean, default=False) group_id = Column(PortableUUID(), ForeignKey("groups.id"), index=True) dashboards = relationship("Dashboard") owned_projects = relationship("Project", backref="owner") tokens = relationship("Token", backref="user") projects = relationship("Project", secondary=users_projects, backref=backref("users", lazy="subquery")) @hybrid_property def password(self): return self._password @password.setter def password(self, value): self._password = bcrypt.generate_password_hash(value).decode("utf8") def check_password(self, plaintext): return bcrypt.check_password_hash(self.password, plaintext) def to_dict(self, with_projects=False): """An overridden method to include projects""" user_dict = super().to_dict() if with_projects: user_dict["projects"] = [ project.to_dict() for project in self.projects ] return user_dict
def upgrade_4(session): """Version 4 upgrade This upgrade removes the "nullable" constraint on the password field, and adds a "is_superadmin" field to the user table. """ engine = session.get_bind() op = get_upgrade_op(session) metadata = MetaData() metadata.reflect(bind=engine) users = metadata.tables.get("users") if ("users" in metadata.tables and users is not None and "is_superadmin" not in [col.name for col in users.columns]): op.alter_column("users", "_password", nullable=True) op.add_column("users", Column("is_superadmin", Boolean, default=False)) op.add_column("users", Column("is_active", Boolean, default=False)) op.add_column("users", Column("activation_code", Text, default=None))
class Project(Model, ModelMixin): __tablename__ = "projects" name = Column(Text, index=True) title = Column(Text, index=True) owner_id = Column(PortableUUID(), ForeignKey("users.id"), index=True) group_id = Column(PortableUUID(), ForeignKey("groups.id"), index=True) reports = relationship("Report") results = relationship("Result", backref="project") runs = relationship("Run", backref="project") dashboards = relationship("Dashboard", backref="project") widget_configs = relationship("WidgetConfig", backref="project") def to_dict(self, with_owner=False): """An overridden method to include the owner""" project_dict = super().to_dict() if with_owner and self.owner: project_dict["owner"] = self.owner.to_dict() return project_dict
class Run(Model, ModelMixin): __tablename__ = "runs" artifacts = relationship("Artifact") component = Column(Text, index=True) created = Column(DateTime, default=datetime.utcnow, index=True) # this is metadata but it is a reserved attr data = Column(mutable_json_type(dbtype=PortableJSON(), nested=True)) duration = Column(Float, index=True) env = Column(Text, index=True) project_id = Column(PortableUUID(), ForeignKey("projects.id"), index=True) results = relationship("Result") source = Column(Text, index=True) start_time = Column(DateTime, default=datetime.utcnow, index=True) summary = Column(mutable_json_type(dbtype=PortableJSON())) artifacts = relationship("Artifact", backref="run")
class WidgetConfig(Model, ModelMixin): __tablename__ = "widget_configs" navigable = Column(Boolean, index=True) params = Column(mutable_json_type(dbtype=PortableJSON())) project_id = Column(PortableUUID(), ForeignKey("projects.id"), index=True) dashboard_id = Column(PortableUUID(), ForeignKey("dashboards.id"), index=True) title = Column(Text, index=True) type = Column(Text, index=True) weight = Column(Integer, index=True) widget = Column(Text, index=True)
class FileMixin(ModelMixin): content = Column(LargeBinary) def to_dict(self): record_dict = { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs } record_dict.pop("content") if "data" in record_dict: record_dict["additional_metadata"] = record_dict.pop("data") or {} return record_dict
from ibutsu_server.db.types import PortableUUID from sqlalchemy.exc import DBAPIError from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import backref from sqlalchemy_json import mutable_json_type def _gen_uuid(): """Generate a UUID""" return str(uuid4()) users_projects = Table( "users_projects", Column("user_id", PortableUUID(), ForeignKey("users.id"), primary_key=True), Column("project_id", PortableUUID(), ForeignKey("projects.id"), primary_key=True), ) class ModelMixin(object): id = Column(PortableUUID(), primary_key=True, default=_gen_uuid, unique=True, nullable=False) def to_dict(self):
class ImportFile(Model, FileMixin): __tablename__ = "import_files" import_id = Column(PortableUUID(), ForeignKey("imports.id"), nullable=False, index=True)
class Group(Model, ModelMixin): __tablename__ = "groups" name = Column(Text, index=True) projects = relationship("Project") data = Column(mutable_json_type(dbtype=PortableJSON(), nested=True))
class Token(Model, ModelMixin): __tablename__ = "tokens" name = Column(Text, nullable=False) token = Column(Text, nullable=False) expires = Column(DateTime) user_id = Column(PortableUUID(), ForeignKey("users.id"), index=True)