gabriel / musehub public
musehub_label_models.py python
117 lines 3.6 KB
6b53f1af feat: supercharge all pages, full SOC refactor, and Python 3.14 upgrade (#7) Gabriel Cardona <cgcardona@gmail.com> 5d ago
1 """SQLAlchemy ORM models for MuseHub label tables.
2
3 Tables:
4 - musehub_labels: Coloured label definitions scoped to a repo
5 - musehub_issue_labels: Many-to-many join between issues and labels
6 - musehub_pr_labels: Many-to-many join between pull requests and labels
7 """
8 from __future__ import annotations
9
10
11 import uuid
12 from datetime import datetime, timezone
13
14 from sqlalchemy import DateTime, ForeignKey, Index, String, UniqueConstraint
15 from sqlalchemy.orm import Mapped, mapped_column, relationship
16
17 from musehub.db.database import Base
18
19
20 def _new_uuid() -> str:
21 return str(uuid.uuid4())
22
23
24 def _utc_now() -> datetime:
25 return datetime.now(tz=timezone.utc)
26
27
28 class MusehubLabel(Base):
29 """A coloured label tag that can be applied to issues and pull requests.
30
31 Labels are scoped to a repo — the same name may exist across repos with
32 different colours. The UNIQUE(repo_id, name) constraint enforces uniqueness
33 within a repo. ``color`` stores a hex string like ``#d73a4a``.
34 """
35
36 __tablename__ = "musehub_labels"
37 __table_args__ = (
38 UniqueConstraint("repo_id", "name", name="uq_musehub_labels_repo_name"),
39 Index("ix_musehub_labels_repo_id", "repo_id"),
40 )
41
42 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_uuid)
43 repo_id: Mapped[str] = mapped_column(
44 String(36),
45 ForeignKey("musehub_repos.repo_id", ondelete="CASCADE"),
46 nullable=False,
47 )
48 name: Mapped[str] = mapped_column(String(50), nullable=False)
49 # Hex colour string, e.g. "#d73a4a"
50 color: Mapped[str] = mapped_column(String(7), nullable=False)
51 description: Mapped[str | None] = mapped_column(String(200), nullable=True)
52 created_at: Mapped[datetime] = mapped_column(
53 DateTime(timezone=True), nullable=False, default=_utc_now
54 )
55
56 issue_labels: Mapped[list[MusehubIssueLabel]] = relationship(
57 "MusehubIssueLabel", back_populates="label", cascade="all, delete-orphan"
58 )
59 pr_labels: Mapped[list[MusehubPRLabel]] = relationship(
60 "MusehubPRLabel", back_populates="label", cascade="all, delete-orphan"
61 )
62
63
64 class MusehubIssueLabel(Base):
65 """Join table linking issues to labels.
66
67 Composite primary key on (issue_id, label_id). Both sides cascade-delete
68 so removing an issue or a label automatically cleans up the association.
69 """
70
71 __tablename__ = "musehub_issue_labels"
72 __table_args__ = (
73 Index("ix_musehub_issue_labels_label_id", "label_id"),
74 )
75
76 issue_id: Mapped[str] = mapped_column(
77 String(36),
78 ForeignKey("musehub_issues.issue_id", ondelete="CASCADE"),
79 primary_key=True,
80 )
81 label_id: Mapped[str] = mapped_column(
82 String(36),
83 ForeignKey("musehub_labels.id", ondelete="CASCADE"),
84 primary_key=True,
85 )
86
87 label: Mapped[MusehubLabel] = relationship(
88 "MusehubLabel", back_populates="issue_labels"
89 )
90
91
92 class MusehubPRLabel(Base):
93 """Join table linking pull requests to labels.
94
95 Composite primary key on (pr_id, label_id). Both sides cascade-delete
96 so removing a PR or a label automatically cleans up the association.
97 """
98
99 __tablename__ = "musehub_pr_labels"
100 __table_args__ = (
101 Index("ix_musehub_pr_labels_label_id", "label_id"),
102 )
103
104 pr_id: Mapped[str] = mapped_column(
105 String(36),
106 ForeignKey("musehub_pull_requests.pr_id", ondelete="CASCADE"),
107 primary_key=True,
108 )
109 label_id: Mapped[str] = mapped_column(
110 String(36),
111 ForeignKey("musehub_labels.id", ondelete="CASCADE"),
112 primary_key=True,
113 )
114
115 label: Mapped[MusehubLabel] = relationship(
116 "MusehubLabel", back_populates="pr_labels"
117 )