Add Discogs to watchlist — column, backfill, matching

- Add discogs_artist_id column to watchlist_artists table (migration)
- Add discogs_artist_id to WatchlistArtist dataclass
- Add to get_watchlist_artists optional_columns and constructor
- Add update_watchlist_discogs_id DB method
- Backfill loop includes Discogs when token is configured
- Add _match_to_discogs for cross-provider artist matching
- Backfill maps updated: id_attr, match_fn, update_fn all include discogs
This commit is contained in:
Broque Thomas 2026-04-02 20:53:30 -07:00
parent 58c3e589b6
commit 82f9b84e5b
2 changed files with 54 additions and 3 deletions

View file

@ -609,6 +609,12 @@ class WatchlistScanner:
providers_to_backfill = ['itunes', 'deezer']
if self.spotify_client and self.spotify_client.is_spotify_authenticated():
providers_to_backfill.append('spotify')
try:
from config.settings import config_manager as _cfg
if _cfg.get('discogs.token', ''):
providers_to_backfill.append('discogs')
except Exception:
pass
for provider in providers_to_backfill:
try:
@ -979,6 +985,7 @@ class WatchlistScanner:
'spotify': 'spotify_artist_id',
'itunes': 'itunes_artist_id',
'deezer': 'deezer_artist_id',
'discogs': 'discogs_artist_id',
}.get(provider)
if not id_attr:
@ -997,12 +1004,14 @@ class WatchlistScanner:
'spotify': self._match_to_spotify,
'itunes': self._match_to_itunes,
'deezer': self._match_to_deezer,
'discogs': self._match_to_discogs,
}.get(provider)
update_fn = {
'spotify': self.database.update_watchlist_spotify_id,
'itunes': self.database.update_watchlist_itunes_id,
'deezer': getattr(self.database, 'update_watchlist_deezer_id', None),
'discogs': getattr(self.database, 'update_watchlist_discogs_id', None),
}.get(provider)
if not match_fn or not update_fn:
@ -1150,6 +1159,17 @@ class WatchlistScanner:
logger.warning(f"Could not match {artist_name} to Deezer: {e}")
return None
def _match_to_discogs(self, artist_name: str) -> Optional[str]:
"""Match artist name to Discogs ID using fuzzy name comparison."""
try:
from core.discogs_client import DiscogsClient
client = DiscogsClient()
results = client.search_artists(artist_name, limit=5)
return self._best_artist_match(results, artist_name)
except Exception as e:
logger.warning(f"Could not match {artist_name} to Discogs: {e}")
return None
def _get_lookback_period_setting(self) -> str:
"""
Get the discovery lookback period setting from database.

View file

@ -88,6 +88,7 @@ class WatchlistArtist:
image_url: Optional[str] = None
itunes_artist_id: Optional[str] = None # Cross-provider support
deezer_artist_id: Optional[str] = None # Cross-provider support
discogs_artist_id: Optional[str] = None # Cross-provider support
include_albums: bool = True
include_eps: bool = True
include_singles: bool = True
@ -1272,6 +1273,10 @@ class MusicDatabase:
cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN deezer_artist_id TEXT")
logger.info("Added deezer_artist_id column to watchlist_artists table for cross-provider support")
if 'discogs_artist_id' not in columns:
cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN discogs_artist_id TEXT")
logger.info("Added discogs_artist_id column to watchlist_artists table for cross-provider support")
except Exception as e:
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
# Don't raise - this is a migration, database can still function
@ -6444,7 +6449,7 @@ class MusicDatabase:
# Check if artist already exists by name (case-insensitive) for this profile
cursor.execute("""
SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id
SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id, discogs_artist_id
FROM watchlist_artists
WHERE LOWER(artist_name) = LOWER(?) AND profile_id = ?
LIMIT 1
@ -6457,7 +6462,7 @@ class MusicDatabase:
if existing:
# Artist already on watchlist — update with new source ID if missing
col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id'}
col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id', 'discogs': 'discogs_artist_id'}
col = col_map.get(source)
if col and not existing[col]:
cursor.execute(f"""
@ -6486,6 +6491,13 @@ class MusicDatabase:
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)
""", (artist_id, artist_name, profile_id))
logger.info(f"Added artist '{artist_name}' to watchlist (iTunes ID: {artist_id}, profile: {profile_id})")
elif source == 'discogs':
cursor.execute("""
INSERT INTO watchlist_artists
(discogs_artist_id, artist_name, date_added, updated_at, profile_id)
VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)
""", (artist_id, artist_name, profile_id))
logger.info(f"Added artist '{artist_name}' to watchlist (Discogs ID: {artist_id}, profile: {profile_id})")
else:
cursor.execute("""
INSERT INTO watchlist_artists
@ -6572,7 +6584,7 @@ class MusicDatabase:
# Build SELECT query based on existing columns
base_columns = ['id', 'spotify_artist_id', 'artist_name', 'date_added',
'last_scan_timestamp', 'created_at', 'updated_at']
optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'include_albums', 'include_eps', 'include_singles',
optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'discogs_artist_id', 'include_albums', 'include_eps', 'include_singles',
'include_live', 'include_remixes', 'include_acoustic', 'include_compilations',
'include_instrumentals', 'lookback_days']
@ -6600,6 +6612,7 @@ class MusicDatabase:
image_url = row['image_url'] if 'image_url' in existing_columns else None
itunes_artist_id = row['itunes_artist_id'] if 'itunes_artist_id' in existing_columns else None
deezer_artist_id = row['deezer_artist_id'] if 'deezer_artist_id' in existing_columns else None
discogs_artist_id = row['discogs_artist_id'] if 'discogs_artist_id' in existing_columns else None
include_albums = bool(row['include_albums']) if 'include_albums' in existing_columns else True
include_eps = bool(row['include_eps']) if 'include_eps' in existing_columns else True
include_singles = bool(row['include_singles']) if 'include_singles' in existing_columns else True
@ -6621,6 +6634,7 @@ class MusicDatabase:
image_url=image_url,
itunes_artist_id=itunes_artist_id,
deezer_artist_id=deezer_artist_id,
discogs_artist_id=discogs_artist_id,
include_albums=include_albums,
include_eps=include_eps,
include_singles=include_singles,
@ -6889,6 +6903,23 @@ class MusicDatabase:
logger.error(f"Error updating watchlist Deezer ID: {e}")
return False
def update_watchlist_discogs_id(self, watchlist_id: int, discogs_id: str) -> bool:
"""Update the Discogs artist ID for a watchlist artist (cross-provider support)"""
try:
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
UPDATE watchlist_artists
SET discogs_artist_id = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""", (discogs_id, watchlist_id))
conn.commit()
logger.info(f"Updated Discogs ID for watchlist artist {watchlist_id}: {discogs_id}")
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Error updating watchlist Discogs ID: {e}")
return False
def update_watchlist_artist_itunes_id(self, spotify_artist_id: str, itunes_id: str) -> bool:
"""Update the iTunes artist ID for a watchlist artist by Spotify ID (for cross-provider caching)"""
try: