class ApiTokenRepositoryAccess(db.Model): repository_id = db.Column( GUID, db.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True ) api_token_id = db.Column( GUID, db.ForeignKey("api_token.id", ondelete="CASCADE"), primary_key=True ) permission = db.Column(Enum(Permission), nullable=False, default=Permission.read) repository = db.relationship("Repository", innerjoin=True, uselist=False) api_token = db.relationship("ApiToken", innerjoin=True, uselist=False) __tablename__ = "api_token_repository_access" __repr__ = model_repr("repository_id", "api_token_id")
class Bundle(RepositoryBoundMixin, StandardAttributes, db.Model): job_id = db.Column(GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=False, index=True) name = db.Column(db.Text, nullable=False) job = db.relationship("Job") assets = db.relationship("BundleAsset", lazy="subquery") __tablename__ = "bundle" __table_args__ = (db.UniqueConstraint("job_id", "name", name="unq_bundle"), ) __repr__ = model_repr("repository_id", "job_id", "name")
class Source(RepositoryBoundMixin, StandardAttributes, db.Model): """ A source represents the canonical parameters that a build is running against. """ patch_id = db.Column(GUID, db.ForeignKey("patch.id"), unique=True, nullable=True) revision_sha = db.Column(db.String(40), nullable=False) data = db.Column(JSONEncodedDict, nullable=True) author_id = db.Column(GUID, db.ForeignKey("author.id"), index=True) author = db.relationship("Author") patch = db.relationship("Patch") revision = db.relationship( "Revision", foreign_keys="[Source.repository_id, Source.revision_sha]", viewonly=True, ) __tablename__ = "source" __table_args__ = ( db.ForeignKeyConstraint( ("repository_id", "revision_sha"), ("revision.repository_id", "revision.sha"), ), db.Index("idx_source_repo_sha", "repository_id", "revision_sha"), db.UniqueConstraint("repository_id", "revision_sha", "patch_id", name="unq_source_revision"), ) __repr__ = model_repr("repository_id", "revision_sha", "patch_id") def is_commit(self): return self.patch_id is None and self.revision_sha def generate_diff(self): if self.patch: return self.patch.diff vcs = self.repository.get_vcs() if vcs: try: return vcs.export(self.revision_sha) except Exception: pass return None
class RepositoryAccess(db.Model): repository_id = db.Column(GUID, db.ForeignKey("repository.id"), primary_key=True) user_id = db.Column(GUID, db.ForeignKey("user.id"), primary_key=True) repository = db.relationship("Repository", innerjoin=True, uselist=False) user = db.relationship("User", innerjoin=True, uselist=False) permission = db.Column(Enum(Permission), nullable=False, default=Permission.read, server_default="1") __tablename__ = "repository_access" __repr__ = model_repr("repository_id", "user_id")
class Author(RepositoryBoundMixin, db.Model): """ The author of a source. Generally used for things like commit authors. This is different than User, which indicates a known authenticatable user. """ id = db.Column(GUID, primary_key=True, default=GUID.default_value) name = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), nullable=True) __tablename__ = 'author' __table_args__ = (db.UniqueConstraint('repository_id', 'email', name='unq_author_email'), ) __repr__ = model_repr('repository_id', 'name', 'email')
class Build(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single build linked to a source. Each Build contains many Jobs. """ source_id = db.Column(GUID, db.ForeignKey("source.id", ondelete="CASCADE"), nullable=False, index=True) number = db.Column(db.Integer, nullable=False) label = db.Column(db.String, nullable=False) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) result = db.Column(Enum(Result), nullable=False, default=Result.unknown) date_created = db.Column( db.TIMESTAMP(timezone=True), nullable=False, default=timezone.now, server_default=func.now(), index=True, ) date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) data = db.Column(JSONEncodedDict, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) url = db.Column(db.String, nullable=True) source = db.relationship("Source", innerjoin=True) stats = db.relationship( "ItemStat", foreign_keys="[ItemStat.item_id]", primaryjoin="ItemStat.item_id == Build.id", viewonly=True, uselist=True, ) __tablename__ = "build" __table_args__ = ( db.UniqueConstraint("repository_id", "number", name="unq_build_number"), db.UniqueConstraint("repository_id", "provider", "external_id", name="unq_build_provider"), ) __repr__ = model_repr("number", "status", "result")
class Patch(RepositoryBoundMixin, StandardAttributes, db.Model): parent_revision_sha = db.Column(db.String(40), nullable=False) diff = db.deferred(db.Column(db.Text, nullable=False)) parent_revision = db.relationship( 'Revision', foreign_keys='[Patch.repository_id, Patch.parent_revision_sha]', viewonly=True) __tablename__ = 'patch' __table_args__ = ( db.ForeignKeyConstraint(('repository_id', 'parent_revision_sha'), ('revision.repository_id', 'revision.sha')), db.Index('idx_repo_sha', 'repository_id', 'parent_revision_sha'), ) __repr__ = model_repr('repository_id', 'parent_revision_sha')
class FileCoverage(ProjectBoundMixin, db.Model): id = db.Column(GUID, nullable=False, primary_key=True, default=GUID.default_value) job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete='CASCADE'), nullable=False) filename = db.Column(db.String(256), nullable=False, primary_key=True) data = db.Column(db.Text, nullable=False) lines_covered = db.Column(db.Integer, nullable=False) lines_uncovered = db.Column(db.Integer, nullable=False) diff_lines_covered = db.Column(db.Integer, nullable=False) diff_lines_uncovered = db.Column(db.Integer, nullable=False) job = db.relationship('Job', innerjoin=True, uselist=False) __tablename__ = 'filecoverage' __table_args__ = (db.UniqueConstraint('job_id', 'filename', name='unq_job_filname'), ) __repr__ = model_repr('job_id', 'filename')
class User(StandardAttributes, db.Model): """ Actors within Zeus. """ email = db.Column(db.String(128), unique=True, nullable=False) options = db.relationship( "ItemOption", foreign_keys="[ItemOption.item_id]", primaryjoin="ItemOption.item_id == User.id", viewonly=True, uselist=True, ) __tablename__ = "user" __repr__ = model_repr("email")
class ApiToken(StandardAttributes, db.Model): """ An API token. """ access_token = db.Column( db.String(64), default=lambda: ApiToken.generate_token(), unique=True, nullable=False, ) refresh_token = db.Column( db.String(64), default=lambda: ApiToken.generate_token(), unique=True, nullable=False, ) expires_at = db.Column( db.TIMESTAMP(timezone=True), nullable=True, default=lambda: timezone.now() + DEFAULT_EXPIRATION, ) __tablename__ = "api_token" __repr__ = model_repr("id") @classmethod def generate_token(cls) -> str: return token_hex(32) def is_expired(self) -> bool: if not self.expires_at: return False return timezone.now() >= self.expires_at def refresh(self, expires_at=None): if expires_at is None: expires_at = timezone.now() + DEFAULT_EXPIRATION with db.session.begin_nested(): self.token = type(self).generate_token() self.refresh_token = type(self).generate_token() self.expires_at = expires_at db.session.add(self) db.session.commit()
class Job(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single job, which is the actual execution unit for a build. """ id = db.Column(GUID, primary_key=True, default=GUID.default_value) build_id = db.Column(GUID, db.ForeignKey('build.id', ondelete='CASCADE'), nullable=False, index=True) number = db.Column(db.Integer, nullable=False) label = db.Column(db.String, nullable=True) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) result = db.Column(Enum(Result), nullable=False, default=Result.unknown) date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) data = db.Column(JSONEncodedDict, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) url = db.Column(db.String, nullable=True) build = db.relationship('Build', backref=db.backref('jobs', order_by='Job.date_created'), innerjoin=True) stats = db.relationship('ItemStat', foreign_keys='[ItemStat.item_id]', primaryjoin='ItemStat.item_id == Job.id', viewonly=True, uselist=True) failures = db.relationship( 'FailureReason', foreign_keys='[FailureReason.job_id]', primaryjoin='FailureReason.job_id == Job.id', viewonly=True, uselist=True, ) __tablename__ = 'job' __table_args__ = (db.UniqueConstraint('build_id', 'number', name='unq_job_number'), db.UniqueConstraint('build_id', 'provider', 'external_id', name='unq_job_provider')) __repr__ = model_repr('build_id', 'number', 'status', 'result')
class BundleAsset(RepositoryBoundMixin, StandardAttributes, db.Model): bundle_id = db.Column(GUID, db.ForeignKey('bundle.id', ondelete='CASCADE'), nullable=False) job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete='CASCADE'), nullable=False) name = db.Column(db.Text, nullable=False) size = db.Column(db.Integer, nullable=True) job = db.relationship('Job') bundle = db.relationship('Bundle') __tablename__ = 'bundle_asset' __table_args__ = (db.UniqueConstraint('bundle_id', 'name', name='unq_bundle_asset'), ) __repr__ = model_repr('repository_id', 'job_id', 'bundle_id', 'name')
class StyleViolation(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single style violation. """ job_id = db.Column( GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=False ) filename = db.Column(db.Text, nullable=False) severity = db.Column(Enum(Severity), default=Severity.error, nullable=False) message = db.Column(db.Text, nullable=False) lineno = db.Column(db.Integer, nullable=True) colno = db.Column(db.Integer, nullable=True) source = db.Column(db.Text, nullable=True) job = db.relationship("Job") __tablename__ = "styleviolation" __repr__ = model_repr("repository_id", "job_id", "filename", "message")
class Identity(StandardAttributes, db.Model): """ Identities associated with a user. Primarily used for Single Sign-On. """ user_id = db.Column( GUID, db.ForeignKey('user.id', ondelete="CASCADE"), nullable=False, index=True ) external_id = db.Column(db.String(64), unique=True, nullable=False) provider = db.Column(db.String(32), nullable=False) config = db.Column(JSONEncodedDict, nullable=False) scopes = db.Column(ARRAY(db.String(64)), nullable=True) user = db.relationship('User') __tablename__ = 'identity' __table_args__ = (db.UniqueConstraint( 'user_id', 'provider', name='unq_identity_user'), ) __repr__ = model_repr('user_id', 'provider', 'external_id')
class Email(StandardAttributes, db.Model): """ An email address associated with a user. """ user_id = db.Column(GUID, db.ForeignKey('user.id', ondelete="CASCADE"), nullable=False, index=True) email = db.Column(db.String(128), nullable=False) verified = db.Column(db.Boolean, default=False, nullable=False) user = db.relationship('User') __tablename__ = 'email' __table_args__ = (db.UniqueConstraint('user_id', 'email', name='unq_user_email'), ) __repr__ = model_repr('user_id', 'email', 'verified')
class Hook(RepositoryBoundMixin, StandardAttributes, db.Model): """ An webhook bound to a single respository. """ token = db.Column( db.LargeBinary(64), default=lambda: Hook.generate_token(), unique=True, nullable=False, ) provider = db.Column(db.String(64), nullable=False) is_required = db.Column(db.Boolean, nullable=True) # the provider controls the format of data config = db.Column(JSONEncodedDict, nullable=True) __tablename__ = "hook" __repr__ = model_repr("repository_id", "provider") @classmethod def generate_token(cls) -> bytes: return token_bytes(64) def get_signature(self) -> bytes: return hmac.new(key=self.token, msg=self.repository_id.bytes, digestmod=sha256).hexdigest() def is_valid_signature(self, signature: bytes) -> bool: return compare_digest(self.get_signature(), signature) def get_provider(self): from zeus.providers import get_provider return get_provider(self.provider) @classmethod def get_required_hook_ids(cls, repository_id: str) -> List[str]: return sorted([ str(h) for h, in db.session.query(Hook.id).filter( Hook.repository_id == repository_id, Hook.is_required == True, # NOQA ) ])
class UserApiToken(StandardAttributes, db.Model, ApiTokenMixin): """ An API token associated to users. """ user_id = db.Column(GUID, db.ForeignKey("user.id", ondelete="CASCADE"), nullable=False, unique=True) user = db.relationship("User", backref=db.backref("tokens", uselist=False), innerjoin=True) __tablename__ = "user_api_token" __repr__ = model_repr("user_id", "key") def get_token_key(self): return "u"
class TestCase(RepositoryBoundMixin, db.Model): """ A single run of a single test. """ id = db.Column(GUID, nullable=False, primary_key=True, default=GUID.default_value) job_id = db.Column(GUID, db.ForeignKey("job.id", ondelete="CASCADE"), nullable=False) hash = db.Column(db.String(40), nullable=False) name = db.Column(db.Text, nullable=False) result = db.Column(Enum(Result), default=Result.unknown, nullable=False) # duration, in milliseconds duration = db.Column(db.Integer, default=0, nullable=True) message = db.deferred(db.Column(db.Text, nullable=True)) job = db.relationship("Job") __tablename__ = "testcase" __table_args__ = (db.UniqueConstraint("job_id", "hash", name="unq_testcase_hash"), ) __repr__ = model_repr("repository_id", "job_id", "name", "result") @classmethod def calculate_sha(self, name): assert name return sha1(name.encode("utf-8")).hexdigest() @property def sep(self): name = self.name # handle the case where it might begin with some special character if not re.match(r"^[a-zA-Z0-9]", name): return "/" elif "/" in name: return "/" return "."
class Patch(RepositoryBoundMixin, StandardAttributes, db.Model): parent_revision_sha = db.Column(db.String(40), nullable=False) diff = db.deferred(db.Column(db.Text, nullable=False)) parent_revision = db.relationship( "Revision", foreign_keys="[Patch.repository_id, Patch.parent_revision_sha]", viewonly=True, ) __tablename__ = "patch" __table_args__ = ( db.ForeignKeyConstraint( ("repository_id", "parent_revision_sha"), ("revision.repository_id", "revision.sha"), ), db.Index("idx_repo_sha", "repository_id", "parent_revision_sha"), ) __repr__ = model_repr("repository_id", "parent_revision_sha")
class StyleViolation(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single style violation. """ job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete='CASCADE'), nullable=False) filename = db.Column(db.Text, nullable=False) severity = db.Column(Enum(Severity), default=Severity.error, nullable=False) message = db.Column(db.Text, nullable=False) lineno = db.Column(db.Integer, nullable=True) colno = db.Column(db.Integer, nullable=True) source = db.Column(db.Text, nullable=True) job = db.relationship('Job') __tablename__ = 'styleviolation' __repr__ = model_repr('repository_id', 'job_id', 'filename', 'message')
class Hook(RepositoryBoundMixin, StandardAttributes, db.Model): """ An webhook bound to a single respository. """ token = db.Column( db.LargeBinary(64), default=lambda: Hook.generate_token(), unique=True, nullable=False ) provider = db.Column(db.String(64), nullable=False) __tablename__ = 'hook' __repr__ = model_repr('repository_id') @classmethod def generate_token(cls): return token_bytes(64) def get_signature(self): return hmac.new(key=self.token, msg=self.repository_id.bytes, digestmod=sha256).hexdigest() def is_valid_signature(self, signature): return compare_digest(self.get_signature(), signature)
class TestCase(ProjectBoundMixin, db.Model): """ A single run of a single test. """ id = db.Column(GUID, nullable=False, primary_key=True, default=GUID.default_value) job_id = db.Column(GUID, db.ForeignKey('job.id', ondelete="CASCADE"), nullable=False) hash = db.Column(db.String(40), nullable=False) name = db.Column(db.Text, nullable=False) result = db.Column(Enum(Result), default=Result.unknown, nullable=False) # duration, in milliseconds duration = db.Column(db.Integer, default=0, nullable=True) message = db.deferred(db.Column(db.Text, nullable=True)) job = db.relationship('Job') __tablename__ = 'testcase' __table_args__ = (db.UniqueConstraint('job_id', 'hash', name='unq_testcase_hash'), ) __repr__ = model_repr('job_id', 'name', 'result') @classmethod def calculate_sha(self, name): assert name return sha1(name.encode('utf-8')).hexdigest() @property def sep(self): name = self.name # handle the case where it might begin with some special character if not re.match(r'^[a-zA-Z0-9]', name): return '/' elif '/' in name: return '/' return '.'
class UserApiToken(StandardAttributes, db.Model, ApiTokenMixin): """ An API token associated to users. """ user_id = db.Column(GUID, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False, unique=True) user = db.relationship('User', backref=db.backref('tokens', uselist=False), innerjoin=True) __tablename__ = 'user_api_token' __repr__ = model_repr('user_id', 'key') def get_token_key(self): return 'u' def get_tenant(self): return self.user
class Revision(RepositoryBoundMixin, db.Model): sha = db.Column(db.String(40), primary_key=True) author_id = db.Column(GUID, db.ForeignKey("author.id"), index=True, nullable=True) committer_id = db.Column(GUID, db.ForeignKey("author.id"), index=True, nullable=True) message = db.Column(db.Text, nullable=True) parents = db.Column(ARRAY(db.String(40)), nullable=True) branches = db.Column(ARRAY(db.String(128)), nullable=True) date_created = db.Column( db.TIMESTAMP(timezone=True), default=timezone.now, server_default=func.now(), nullable=False, ) date_committed = db.Column( db.TIMESTAMP(timezone=True), default=timezone.now, server_default=func.now(), nullable=False, ) author = db.relationship("Author", foreign_keys=[author_id]) committer = db.relationship("Author", foreign_keys=[committer_id]) __tablename__ = "revision" __table_args__ = (db.UniqueConstraint("repository_id", "sha", name="unq_revision"), ) __repr__ = model_repr("repository_id", "sha", "subject") @property def subject(self): return self.message.splitlines()[0]
class Build(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single build linked to a source. Each Build contains many Jobs. """ source_id = db.Column(GUID, db.ForeignKey('source.id', ondelete='CASCADE'), nullable=False, index=True) number = db.Column(db.Integer, nullable=False) label = db.Column(db.String, nullable=False) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) result = db.Column(Enum(Result), nullable=False, default=Result.unknown) date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) data = db.Column(JSONEncodedDict, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) url = db.Column(db.String, nullable=True) source = db.relationship('Source', innerjoin=True) stats = db.relationship('ItemStat', foreign_keys='[ItemStat.item_id]', primaryjoin='ItemStat.item_id == Build.id', lazy='subquery', viewonly=True, uselist=True) __tablename__ = 'build' __table_args__ = (db.UniqueConstraint('repository_id', 'number', name='unq_build_number'), db.UniqueConstraint('repository_id', 'provider', 'external_id', name='unq_build_provider')) __repr__ = model_repr('number', 'status', 'result')
class User(StandardAttributes, db.Model): """ Actors within Zeus. """ email = db.Column(db.String(128), unique=True, nullable=False) date_active = db.Column( db.TIMESTAMP(timezone=True), nullable=False, default=timezone.now, server_default=func.now(), index=True, ) options = db.relationship( "ItemOption", foreign_keys="[ItemOption.item_id]", primaryjoin="ItemOption.item_id == User.id", viewonly=True, uselist=True, ) __tablename__ = "user" __repr__ = model_repr("email")
class RepositoryApiToken(StandardAttributes, db.Model, ApiTokenMixin): """ An API token associated to users. """ repository_id = db.Column( GUID, db.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False, unique=True, ) repository = db.relationship("Repository", backref=db.backref("token", uselist=False), innerjoin=True) __tablename__ = "repository_api_token" __repr__ = model_repr("repository_id", "key") def get_token_key(self): return "r" def get_tenant(self): return self.user
class FileCoverage(RepositoryBoundMixin, db.Model): id = db.Column(GUID, nullable=False, primary_key=True, default=GUID.default_value) build_id = db.Column(GUID, db.ForeignKey("build.id", ondelete="CASCADE"), nullable=False, index=True) filename = db.Column(db.String(256), nullable=False, primary_key=True) data = db.Column(db.Text, nullable=False) lines_covered = db.Column(db.Integer, nullable=False) lines_uncovered = db.Column(db.Integer, nullable=False) diff_lines_covered = db.Column(db.Integer, nullable=False) diff_lines_uncovered = db.Column(db.Integer, nullable=False) build = db.relationship("Build", innerjoin=True, uselist=False) __tablename__ = "filecoverage" __table_args__ = (db.UniqueConstraint("build_id", "filename", name="unq_coverage_filname"), ) __repr__ = model_repr("repository_id", "build_id", "filename")
class Job(RepositoryBoundMixin, StandardAttributes, db.Model): """ A single job, which is the actual execution unit for a build. """ id = db.Column(GUID, primary_key=True, default=GUID.default_value) build_id = db.Column(GUID, db.ForeignKey("build.id", ondelete="CASCADE"), nullable=False, index=True) number = db.Column(db.Integer, nullable=False) label = db.Column(db.String, nullable=True) status = db.Column(Enum(Status), nullable=False, default=Status.unknown) result = db.Column(Enum(Result), nullable=False, default=Result.unknown) allow_failure = db.Column(db.Boolean, nullable=False, default=False, server_default="0") date_started = db.Column(db.TIMESTAMP(timezone=True), nullable=True) date_updated = db.Column(db.TIMESTAMP(timezone=True), nullable=True, onupdate=timezone.now) date_finished = db.Column(db.TIMESTAMP(timezone=True), nullable=True) data = db.Column(JSONEncodedDict, nullable=True) provider = db.Column(db.String, nullable=True) external_id = db.Column(db.String(64), nullable=True) hook_id = db.Column(GUID, db.ForeignKey("hook.id", ondelete="CASCADE"), nullable=True, index=True) url = db.Column(db.String, nullable=True) build = db.relationship("Build", backref=db.backref("jobs", order_by="Job.date_created"), innerjoin=True) hook = db.relationship("Hook") stats = db.relationship( "ItemStat", foreign_keys="[ItemStat.item_id]", primaryjoin="ItemStat.item_id == Job.id", viewonly=True, uselist=True, ) failures = db.relationship( "FailureReason", foreign_keys="[FailureReason.job_id]", primaryjoin="FailureReason.job_id == Job.id", viewonly=True, uselist=True, ) __tablename__ = "job" __table_args__ = ( db.UniqueConstraint("build_id", "number", name="unq_job_number"), db.UniqueConstraint("build_id", "provider", "external_id", name="unq_job_provider"), ) __repr__ = model_repr("build_id", "number", "status", "result")
class Repository(StandardAttributes, db.Model): """ Represents a single repository. """ # TODO(dcramer): we dont want to be coupled to GitHub (the concept of orgs) # but right now we simply dont care, and this can be refactored later (URLs # are tricky) owner_name = db.Column(db.String(200), nullable=False) name = db.Column(db.String(200), nullable=False) url = db.Column(db.String(200), nullable=False) backend = db.Column( Enum(RepositoryBackend), default=RepositoryBackend.unknown, nullable=False ) status = db.Column( Enum(RepositoryStatus), default=RepositoryStatus.inactive, nullable=False ) provider = db.Column(StrEnum(RepositoryProvider), nullable=False) external_id = db.Column(db.String(64)) data = db.Column(JSONEncodedDict, nullable=True) public = db.Column( db.Boolean, default=False, server_default="false", nullable=False, index=True ) last_update = db.Column(db.TIMESTAMP(timezone=True), nullable=True) last_update_attempt = db.Column(db.TIMESTAMP(timezone=True), nullable=True) options = db.relationship( "ItemOption", foreign_keys="[ItemOption.item_id]", primaryjoin="ItemOption.item_id == Repository.id", viewonly=True, uselist=True, ) query_class = RepositoryAccessBoundQuery __tablename__ = "repository" __table_args__ = ( db.UniqueConstraint("provider", "external_id", name="unq_repo_external_id"), db.UniqueConstraint("provider", "owner_name", "name", name="unq_repo_name"), ) __repr__ = model_repr("name", "url", "provider") def get_vcs(self): from zeus.models import ItemOption from zeus.vcs.git import GitVcs options = dict( db.session.query(ItemOption.name, ItemOption.value).filter( ItemOption.item_id == self.id, ItemOption.name.in_(["auth.username"]) ) ) kwargs = { "path": os.path.join(current_app.config["REPO_ROOT"], self.id.hex), "id": self.id.hex, "url": self.url, "username": options.get("auth.username"), } if self.backend == RepositoryBackend.git: return GitVcs(**kwargs) else: raise NotImplementedError("Invalid backend: {}".format(self.backend)) def get_full_name(self): return "{}/{}/{}".format(self.provider, self.owner_name, self.name)