gabriel / musehub public
musehub_stash_models.py python
96 lines 3.5 KB
cd448303 Initial extraction of MuseHub from maestro monorepo. Gabriel Cardona <gabriel@tellurstori.com> 7d ago
1 """SQLAlchemy ORM models for Muse Hub stash — a temporary shelf for uncommitted changes.
2
3 Analogous to git stash: musicians can save in-progress work, switch context,
4 and pop the stash later to resume. Each stash record captures the branch it
5 was created on plus zero or more MIDI file snapshots (entries).
6
7 Tables:
8 - musehub_stash: one stash record per save point
9 - musehub_stash_entries: individual MIDI file snapshots within a stash
10 """
11 from __future__ import annotations
12
13 import uuid
14 from datetime import datetime, timezone
15
16 from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String
17 from sqlalchemy.orm import Mapped, mapped_column, relationship
18
19 from musehub.db.database import Base
20
21
22 def _new_uuid() -> str:
23 return str(uuid.uuid4())
24
25
26 def _utc_now() -> datetime:
27 return datetime.now(tz=timezone.utc)
28
29
30 class MusehubStash(Base):
31 """A stash record — a named save point for uncommitted Muse changes.
32
33 ``branch`` records which branch the stash was created on so the user
34 can be warned if they try to pop it on a different branch.
35 ``message`` is an optional free-text description (up to 500 chars).
36 ``is_applied`` flips to True when the stash has been popped back into
37 the working tree; ``applied_at`` records the exact timestamp.
38 """
39
40 __tablename__ = "musehub_stash"
41 __table_args__ = (
42 Index("ix_musehub_stash_repo_id", "repo_id"),
43 Index("ix_musehub_stash_user_id", "user_id"),
44 )
45
46 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_uuid)
47 repo_id: Mapped[str] = mapped_column(
48 String(36),
49 ForeignKey("musehub_repos.repo_id", ondelete="CASCADE"),
50 nullable=False,
51 )
52 user_id: Mapped[str] = mapped_column(
53 String(36),
54 ForeignKey("maestro_users.id", ondelete="CASCADE"),
55 nullable=False,
56 )
57 branch: Mapped[str] = mapped_column(String(255), nullable=False)
58 message: Mapped[str | None] = mapped_column(String(500), nullable=True)
59 is_applied: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
60 created_at: Mapped[datetime] = mapped_column(
61 DateTime(timezone=True), nullable=False, default=_utc_now
62 )
63 applied_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
64
65 entries: Mapped[list[MusehubStashEntry]] = relationship(
66 "MusehubStashEntry",
67 back_populates="stash",
68 cascade="all, delete-orphan",
69 order_by="MusehubStashEntry.position",
70 )
71
72
73 class MusehubStashEntry(Base):
74 """A single MIDI file snapshot within a stash.
75
76 ``path`` is the MIDI file's path relative to the repo root.
77 ``object_id`` is the content-addressed hash of the file at stash time,
78 matching the format used in ``musehub_objects`` (e.g. ``sha256:<hex>``).
79 ``position`` preserves the order of entries within the stash so pop
80 restores files in a deterministic sequence.
81 """
82
83 __tablename__ = "musehub_stash_entries"
84 __table_args__ = (Index("ix_musehub_stash_entries_stash_id", "stash_id"),)
85
86 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=_new_uuid)
87 stash_id: Mapped[str] = mapped_column(
88 String(36),
89 ForeignKey("musehub_stash.id", ondelete="CASCADE"),
90 nullable=False,
91 )
92 path: Mapped[str] = mapped_column(String(1024), nullable=False)
93 object_id: Mapped[str] = mapped_column(String(128), nullable=False)
94 position: Mapped[int] = mapped_column(Integer, nullable=False)
95
96 stash: Mapped[MusehubStash] = relationship("MusehubStash", back_populates="entries")