Spaces:
Running
Running
Download collector_python_backup/db.py from vvaibsmusic/symphony: direct link, hf CLI and curl.
- Browser
- Download file 6.93 kB
-
https://huggingface.co/spaces/vvaibsmusic/symphony/resolve/main/collector_python_backup/db.py
- Command line
-
hf download hf://spaces/vvaibsmusic/symphony/collector_python_backup/db.py
-
curl -L -o db.py https://huggingface.co/spaces/vvaibsmusic/symphony/resolve/main/collector_python_backup/db.py
6.93 kB
| """SQLite database helpers for the music dashboard.""" | |
| import sqlite3 | |
| import os | |
| from datetime import datetime, timezone, timedelta | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| DB_PATH = Path(__file__).parent.parent / "db" / "music_dashboard.db" | |
| SCHEMA_PATH = Path(__file__).parent.parent / "db" / "schema.sql" | |
| def get_connection(): | |
| """Get a SQLite connection with WAL mode for better concurrency.""" | |
| conn = sqlite3.connect(str(DB_PATH)) | |
| conn.row_factory = sqlite3.Row | |
| conn.execute("PRAGMA journal_mode=WAL") | |
| conn.execute("PRAGMA foreign_keys=ON") | |
| return conn | |
| def init_db(): | |
| """Initialize the database from schema.sql and run migrations.""" | |
| os.makedirs(DB_PATH.parent, exist_ok=True) | |
| conn = get_connection() | |
| with open(SCHEMA_PATH, "r") as f: | |
| conn.executescript(f.read()) | |
| # Migration: add cycle_id if missing | |
| cols = [row["name"] for row in conn.execute("PRAGMA table_info(play_snapshots)").fetchall()] | |
| if "cycle_id" not in cols: | |
| conn.execute("ALTER TABLE play_snapshots ADD COLUMN cycle_id TEXT") | |
| conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_cycle ON play_snapshots(cycle_id)") | |
| conn.commit() | |
| print("[migration] Added cycle_id to play_snapshots") | |
| # Migration: add ytmusic_play_count if missing | |
| cols = [row["name"] for row in conn.execute("PRAGMA table_info(play_snapshots)").fetchall()] | |
| if "ytmusic_play_count" not in cols: | |
| conn.execute("ALTER TABLE play_snapshots ADD COLUMN ytmusic_play_count INTEGER") | |
| conn.commit() | |
| print("[migration] Added ytmusic_play_count to play_snapshots") | |
| # Composite indexes for the dashboard's hot queries (latest snapshot per | |
| # song by platform, songs by artist+platform, alerts by platform) | |
| conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_platform_song ON play_snapshots(platform, song_id, id)") | |
| conn.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_song_platform_time ON play_snapshots(song_id, platform, collected_at)") | |
| conn.execute("CREATE INDEX IF NOT EXISTS idx_songs_artist_platform ON songs(artist_id, platform)") | |
| conn.execute("CREATE INDEX IF NOT EXISTS idx_alerts_platform ON viral_alerts(platform)") | |
| conn.commit() | |
| # Auto-seed if database is empty | |
| cursor = conn.execute("SELECT COUNT(*) FROM artists") | |
| count = cursor.fetchone()[0] | |
| if count == 0: | |
| print("[init_db] Database is empty. Seeding default artists...") | |
| try: | |
| from seed_artists import ARTISTS, upsert_artist | |
| for artist in ARTISTS: | |
| upsert_artist(conn, artist) | |
| conn.commit() | |
| print(f"[init_db] Successfully seeded {len(ARTISTS)} artists.") | |
| except Exception as e: | |
| print(f"[init_db] Failed to auto-seed: {e}") | |
| conn.close() | |
| print(f"Database initialized at {DB_PATH}") | |
| def upsert_artist(conn, artist: dict): | |
| """Insert or update an artist.""" | |
| conn.execute( | |
| """INSERT INTO artists (id, name, spotify_id, youtube_channel_id, genre, image_url, is_watched) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| ON CONFLICT(id) DO UPDATE SET | |
| name=excluded.name, | |
| spotify_id=excluded.spotify_id, | |
| youtube_channel_id=excluded.youtube_channel_id, | |
| genre=excluded.genre, | |
| image_url=excluded.image_url""", | |
| ( | |
| artist["id"], | |
| artist["name"], | |
| artist.get("spotify_id"), | |
| artist.get("youtube_channel_id"), | |
| artist.get("genre"), | |
| artist.get("image_url"), | |
| artist.get("is_watched", 0), | |
| ), | |
| ) | |
| def upsert_song(conn, song: dict): | |
| """Insert or update a song.""" | |
| conn.execute( | |
| """INSERT INTO songs (id, artist_id, title, platform, platform_id, album_name, release_date, thumbnail_url) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?) | |
| ON CONFLICT(platform, platform_id) DO UPDATE SET | |
| title=excluded.title, | |
| album_name=excluded.album_name, | |
| release_date=excluded.release_date, | |
| thumbnail_url=excluded.thumbnail_url""", | |
| ( | |
| song["id"], | |
| song["artist_id"], | |
| song["title"], | |
| song["platform"], | |
| song["platform_id"], | |
| song.get("album_name"), | |
| song.get("release_date"), | |
| song.get("thumbnail_url"), | |
| ), | |
| ) | |
| def generate_cycle_id(platform: str = "youtube") -> str: | |
| """Generate a cycle_id for the current collection run. | |
| Format: {platform}_{YYYYMMDD_HHMM_IST} | |
| All snapshots in the same run share this ID. | |
| """ | |
| ist = datetime.now(timezone.utc) + timedelta(hours=5, minutes=30) | |
| return f"{platform}_{ist.strftime('%Y%m%d_%H%M')}" | |
| def insert_snapshot(conn, song_id: str, play_count: int, like_count: int, comment_count: int, platform: str, cycle_id: str = None, ytmusic_play_count: int = None): | |
| """Record a play count snapshot with optional cycle_id and ytmusic_play_count.""" | |
| conn.execute( | |
| """INSERT INTO play_snapshots (song_id, play_count, like_count, comment_count, platform, cycle_id, ytmusic_play_count) | |
| VALUES (?, ?, ?, ?, ?, ?, ?)""", | |
| (song_id, play_count, like_count, comment_count, platform, cycle_id, ytmusic_play_count), | |
| ) | |
| def get_previous_snapshot(conn, song_id: str, platform: str): | |
| """Get the most recent snapshot for a song (before the current one).""" | |
| row = conn.execute( | |
| """SELECT play_count, collected_at FROM play_snapshots | |
| WHERE song_id = ? AND platform = ? | |
| ORDER BY collected_at DESC LIMIT 1""", | |
| (song_id, platform), | |
| ).fetchone() | |
| return dict(row) if row else None | |
| def insert_viral_alert(conn, alert: dict): | |
| """Insert a viral alert.""" | |
| conn.execute( | |
| """INSERT INTO viral_alerts (song_id, previous_count, current_count, growth_factor, platform) | |
| VALUES (?, ?, ?, ?, ?)""", | |
| ( | |
| alert["song_id"], | |
| alert["previous_count"], | |
| alert["current_count"], | |
| alert["growth_factor"], | |
| alert["platform"], | |
| ), | |
| ) | |
| def get_all_artists(conn, platform_filter=None): | |
| """Get all artists, optionally filtered by platform availability.""" | |
| if platform_filter == "youtube": | |
| return conn.execute( | |
| "SELECT * FROM artists WHERE youtube_channel_id IS NOT NULL ORDER BY name" | |
| ).fetchall() | |
| elif platform_filter == "spotify": | |
| return conn.execute( | |
| "SELECT * FROM artists WHERE spotify_id IS NOT NULL ORDER BY name" | |
| ).fetchall() | |
| return conn.execute("SELECT * FROM artists ORDER BY name").fetchall() | |
| def get_watched_artists(conn): | |
| """Get all watched artists.""" | |
| return conn.execute( | |
| "SELECT * FROM artists WHERE is_watched = 1 ORDER BY name" | |
| ).fetchall() | |
| if __name__ == "__main__": | |
| init_db() | |