musehub_release_models.py
python
sha256:3c58668648c7323bb9f5c6881cfe6a3f14fc93fcb73b537d253732952a5bf8bf
chore: bump version to 0.2.0rc12
Sonnet 4.6
patch
9 days ago
| 1 | """ORM models for releases and release assets. |
| 2 | |
| 3 | Tables: |
| 4 | - musehub_releases: Tagged releases |
| 5 | - musehub_release_assets: File attachments associated with releases |
| 6 | """ |
| 7 | |
| 8 | from __future__ import annotations |
| 9 | |
| 10 | from datetime import datetime, timezone |
| 11 | |
| 12 | import sqlalchemy as sa |
| 13 | from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint |
| 14 | from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column, relationship |
| 15 | from sqlalchemy.dialects.postgresql import JSONB |
| 16 | |
| 17 | from musehub.db.database import Base |
| 18 | |
| 19 | |
| 20 | def _utc_now() -> datetime: |
| 21 | return datetime.now(tz=timezone.utc) |
| 22 | |
| 23 | |
| 24 | class MusehubRelease(MappedAsDataclass, Base): |
| 25 | """A published version release for a MuseHub repo. |
| 26 | |
| 27 | Releases tie a semver ``tag`` (e.g. "v1.2.3") to a specific commit snapshot |
| 28 | and carry structured metadata: |
| 29 | |
| 30 | - Semver components (major/minor/patch/pre/build) for queryable version ordering. |
| 31 | - Named distribution channel (stable | beta | alpha | nightly) replacing the |
| 32 | boolean ``is_prerelease`` — richer than a flag and machine-filterable. |
| 33 | - ``snapshot_id`` pins the release to the content-addressed object store for |
| 34 | guaranteed reproducibility forever. |
| 35 | - ``agent_id`` / ``model_id`` surface AI provenance from the tip commit. |
| 36 | - ``changelog_json`` is a JSON array of ``ChangelogEntry`` objects auto-generated |
| 37 | from typed ``sem_ver_bump`` / ``breaking_changes`` fields on commits since the |
| 38 | previous release. |
| 39 | - ``download_urls`` is a JSON object keyed by package type for generated artifacts. |
| 40 | |
| 41 | ``tag`` is unique per repo — enforced by the DB constraint |
| 42 | ``uq_musehub_releases_repo_tag`` and guarded at the service layer to |
| 43 | return a clean 409 before the constraint fires. |
| 44 | """ |
| 45 | |
| 46 | __tablename__ = "musehub_releases" |
| 47 | __table_args__ = (UniqueConstraint("repo_id", "tag", name="uq_musehub_releases_repo_tag"),) |
| 48 | |
| 49 | # --- Required fields --- |
| 50 | release_id: Mapped[str] = mapped_column(String(128), primary_key=True) |
| 51 | repo_id: Mapped[str] = mapped_column( |
| 52 | String(128), |
| 53 | ForeignKey("musehub_repos.repo_id", ondelete="CASCADE"), |
| 54 | nullable=False, |
| 55 | index=True, |
| 56 | ) |
| 57 | tag: Mapped[str] = mapped_column(String(100), nullable=False, index=True) |
| 58 | |
| 59 | # --- Optional fields --- |
| 60 | title: Mapped[str] = mapped_column(String(500), nullable=False, default="") |
| 61 | body: Mapped[str] = mapped_column(Text, nullable=False, default="") |
| 62 | commit_id: Mapped[str | None] = mapped_column(String(128), nullable=True, default=None) |
| 63 | snapshot_id: Mapped[str | None] = mapped_column(String(128), nullable=True, default=None) |
| 64 | semver_major: Mapped[int] = mapped_column(Integer, nullable=False, default=0) |
| 65 | semver_minor: Mapped[int] = mapped_column(Integer, nullable=False, default=0) |
| 66 | semver_patch: Mapped[int] = mapped_column(Integer, nullable=False, default=0) |
| 67 | semver_pre: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 68 | semver_build: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 69 | channel: Mapped[str] = mapped_column(String(20), nullable=False, default="stable", server_default="stable", index=True) |
| 70 | download_urls: Mapped[dict[str, str]] = mapped_column(JSONB, nullable=False, default_factory=dict) |
| 71 | author: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 72 | agent_id: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 73 | model_id: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 74 | changelog_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]") |
| 75 | is_draft: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default=sa.false()) |
| 76 | gpg_signature: Mapped[str | None] = mapped_column(Text, nullable=True, default=None) |
| 77 | semantic_report_json: Mapped[str] = mapped_column(Text, nullable=False, default="") |
| 78 | created_at: Mapped[datetime] = mapped_column( |
| 79 | DateTime(timezone=True), nullable=False, default_factory=_utc_now |
| 80 | ) |
| 81 | updated_at: Mapped[datetime] = mapped_column( |
| 82 | DateTime(timezone=True), nullable=False, default_factory=_utc_now, onupdate=_utc_now |
| 83 | ) |
| 84 | |
| 85 | repo: Mapped["MusehubRepo"] = relationship("MusehubRepo", back_populates="releases", init=False) |
| 86 | assets: Mapped[list[MusehubReleaseAsset]] = relationship( |
| 87 | "MusehubReleaseAsset", back_populates="release", cascade="all, delete-orphan", init=False, default_factory=list |
| 88 | ) |
| 89 | |
| 90 | |
| 91 | class MusehubReleaseAsset(Base): |
| 92 | """An asset (file attachment) associated with a MuseHub release. |
| 93 | |
| 94 | Assets represent downloadable artifacts attached to a release — for example |
| 95 | a MIDI bundle, a stems archive, or a rendered MP3. ``download_count`` |
| 96 | tracks how many times the asset has been downloaded so the release page |
| 97 | can surface popularity metrics without querying the analytics pipeline. |
| 98 | |
| 99 | ``release_id`` is the FK to the owning release and participates in cascade |
| 100 | deletes: removing the release removes all its assets. |
| 101 | """ |
| 102 | |
| 103 | __tablename__ = "musehub_release_assets" |
| 104 | |
| 105 | # genesis-addressed: sha256(release_id NUL filename NUL created_at_iso) |
| 106 | asset_id: Mapped[str] = mapped_column(String(128), primary_key=True) |
| 107 | release_id: Mapped[str] = mapped_column( |
| 108 | String(128), |
| 109 | ForeignKey("musehub_releases.release_id", ondelete="CASCADE"), |
| 110 | nullable=False, |
| 111 | index=True, |
| 112 | ) |
| 113 | # Denormalised so we can query assets by repo without joining releases. |
| 114 | repo_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True) |
| 115 | # Filename shown in the UI, e.g. "summer-sessions-v1.0.mid" |
| 116 | name: Mapped[str] = mapped_column(String(500), nullable=False) |
| 117 | # Optional human-readable label, e.g. "MIDI Bundle", "Stems Archive" |
| 118 | label: Mapped[str] = mapped_column(String(255), nullable=False, default="") |
| 119 | # MIME type, e.g. "audio/midi", "application/zip" |
| 120 | content_type: Mapped[str] = mapped_column(String(128), nullable=False, default="") |
| 121 | # File size in bytes; 0 when unknown |
| 122 | size: Mapped[int] = mapped_column(Integer, nullable=False, default=0) |
| 123 | # Direct download URL for the artifact (pre-signed or CDN URL) |
| 124 | download_url: Mapped[str] = mapped_column(String(2048), nullable=False) |
| 125 | # Incrementing counter updated each time the asset is downloaded |
| 126 | download_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) |
| 127 | created_at: Mapped[datetime] = mapped_column( |
| 128 | DateTime(timezone=True), nullable=False, default=_utc_now |
| 129 | ) |
| 130 | |
| 131 | release: Mapped[MusehubRelease] = relationship("MusehubRelease", back_populates="assets") |
File History
1 commit
sha256:3c58668648c7323bb9f5c6881cfe6a3f14fc93fcb73b537d253732952a5bf8bf
chore: bump version to 0.2.0rc12
Sonnet 4.6
patch
9 days ago