feat: finalize root api.py with X-Auth-Secret auth security and clean pure sales cohort
This commit is contained in:
1 parent
85e6395770
commit
78a46a8dbe
629 files changed
+123965
-755
No files matched your search
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
scripts/fetch_spotify_verified_faces.py
|
||||
Fetches the 100% authentic, verified label profile portrait for every artist
|
||||
in the 500 cohort directly from Spotify's Web API.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import base64
|
||||
from pathlib import Path
|
||||
import pandas as pd
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||
if str(ROOT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT_DIR))
|
||||
|
||||
load_dotenv(ROOT_DIR / ".env")
|
||||
from core.database import get_connection
|
||||
|
||||
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
||||
SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
||||
|
||||
def get_spotify_token():
|
||||
if not SPOTIFY_CLIENT_ID or not SPOTIFY_CLIENT_SECRET:
|
||||
print("ERROR: SPOTIFY_CLIENT_ID or SPOTIFY_CLIENT_SECRET is missing from .env")
|
||||
sys.exit(1)
|
||||
|
||||
auth_str = f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}"
|
||||
b64_auth = base64.b64encode(auth_str.encode()).decode()
|
||||
|
||||
url = "https://accounts.spotify.com/api/token"
|
||||
headers = {
|
||||
"Authorization": f"Basic {b64_auth}",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
data = {"grant_type": "client_credentials"}
|
||||
r = requests.post(url, headers=headers, data=data, timeout=10)
|
||||
if r.status_code == 200:
|
||||
return r.json().get("access_token")
|
||||
else:
|
||||
print(f"Failed to obtain Spotify token: {r.status_code} - {r.text}")
|
||||
sys.exit(1)
|
||||
|
||||
def chunk_list(lst, size=50):
|
||||
for i in range(0, len(lst), size):
|
||||
yield lst[i:i + size]
|
||||
|
||||
def main():
|
||||
print("=" * 65)
|
||||
print("HIFFI INTELLIGENCE: SPOTIFY OFFICIAL ARTIST PORTRAIT ENGINE")
|
||||
print("=" * 65)
|
||||
|
||||
token = get_spotify_token()
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
csv_path = ROOT_DIR / "data" / "hiffi_multiplatform_500.csv"
|
||||
if not csv_path.exists():
|
||||
csv_path = ROOT_DIR / "cleaned_artists_inventory.csv"
|
||||
|
||||
df_csv = pd.read_csv(csv_path)
|
||||
name_col = "name" if "name" in df_csv.columns else "artist_name"
|
||||
cohort = [str(n).strip() for n in df_csv[name_col].dropna()]
|
||||
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
placeholders = ",".join(["?"] * len(cohort))
|
||||
query = f"""
|
||||
SELECT artist_id, artist_name, spotify_url
|
||||
FROM artists
|
||||
WHERE artist_name IN ({placeholders})
|
||||
"""
|
||||
cur.execute(query, cohort)
|
||||
rows = cur.fetchall()
|
||||
|
||||
id_to_artist = {}
|
||||
direct_search_artists = []
|
||||
|
||||
for artist_id, name, sp_url in rows:
|
||||
sp_url = sp_url or ""
|
||||
if "/artist/" in sp_url:
|
||||
sp_id = sp_url.split("/artist/")[-1].split("?")[0].strip("/")
|
||||
id_to_artist[sp_id] = (artist_id, name)
|
||||
else:
|
||||
direct_search_artists.append((artist_id, name))
|
||||
|
||||
print(f"Direct Spotify Artist IDs mapped: {len(id_to_artist)}")
|
||||
print(f"Artists needing search lookup : {len(direct_search_artists)}\n")
|
||||
|
||||
updated = 0
|
||||
|
||||
# 1. Batch fetch official portraits via Spotify's /v1/artists endpoint (50 at a time)
|
||||
sp_ids = list(id_to_artist.keys())
|
||||
for batch_num, batch in enumerate(chunk_list(sp_ids, 50), start=1):
|
||||
ids_str = ",".join(batch)
|
||||
url = f"https://api.spotify.com/v1/artists?ids={ids_str}"
|
||||
try:
|
||||
r = requests.get(url, headers=headers, timeout=12)
|
||||
if r.status_code == 200:
|
||||
artists_data = r.json().get("artists", [])
|
||||
for a in artists_data:
|
||||
if not a:
|
||||
continue
|
||||
sp_id = a.get("id")
|
||||
images = a.get("images", [])
|
||||
if images and sp_id in id_to_artist:
|
||||
# Grab the highest resolution portrait
|
||||
img_url = images[0].get("url")
|
||||
art_id, art_name = id_to_artist[sp_id]
|
||||
cur.execute("""
|
||||
UPDATE artists
|
||||
SET image_url = ?,
|
||||
image_verified_flag = 1
|
||||
WHERE artist_id = ?
|
||||
""", (img_url, art_id))
|
||||
updated += 1
|
||||
conn.commit()
|
||||
print(f"Batch {batch_num:>2}: Updated {len(artists_data)} verified portraits.")
|
||||
else:
|
||||
print(f"Batch {batch_num} error {r.status_code}: {r.text[:80]}")
|
||||
except Exception as e:
|
||||
print(f"Batch {batch_num} exception: {e}")
|
||||
|
||||
# 2. Resolve the remaining search artists
|
||||
for idx, (artist_id, name) in enumerate(direct_search_artists, start=1):
|
||||
url = f"https://api.spotify.com/v1/search?q={name}&type=artist&limit=1"
|
||||
try:
|
||||
r = requests.get(url, headers=headers, timeout=8)
|
||||
if r.status_code == 200:
|
||||
items = r.json().get("artists", {}).get("items", [])
|
||||
if items:
|
||||
images = items[0].get("images", [])
|
||||
if images:
|
||||
img_url = images[0].get("url")
|
||||
sp_canonical = items[0].get("external_urls", {}).get("spotify")
|
||||
cur.execute("""
|
||||
UPDATE artists
|
||||
SET image_url = ?,
|
||||
spotify_url = COALESCE(NULLIF(spotify_url, ''), ?),
|
||||
image_verified_flag = 1
|
||||
WHERE artist_id = ?
|
||||
""", (img_url, sp_canonical, artist_id))
|
||||
updated += 1
|
||||
print(f"[Search {idx:>2}/{len(direct_search_artists)}] Verified portrait for {name}")
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print(f"Error on {name}: {e}")
|
||||
|
||||
conn.close()
|
||||
|
||||
print("\n" + "=" * 65)
|
||||
print(f"Successfully populated authentic Spotify portraits for {updated} artists.")
|
||||
print("=" * 65)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user