R7: consolidate duplicated Spotify-token and NDJSON-parse logic

- The client-credentials token fetch was copy-pasted across spotify-retag.py,
  spotify-genre.py, and fix-track-metadata.py (and the app's spotify_client).
  Add pipeline/lib/_spotify_auth.get_token (cached per id/secret); the three
  scripts now source their own credentials but delegate the request to it. The
  scripts run with pipeline/lib on sys.path, so the plain `from _spotify_auth
  import get_token` resolves.
- The identical _parse_json_lines helper in dedup_review_service and
  genre_review_service is now a single app/services/_ndjson.parse_json_lines.

Verified: unit test of the token helper (cache + request), the NDJSON parser
(tests/test_ndjson.py), full suite green (40), and a live spotify-genre dry-run
that fetched a token and queried Spotify through the shared helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
andrew
2026-07-10 16:08:15 -06:00
parent e49d12a72b
commit bcd07750bf
8 changed files with 83 additions and 66 deletions
+3 -15
View File
@@ -31,27 +31,15 @@ Matching strategy (per file):
use it. If multiple, score each by artist-string overlap and pick the
best. If nothing close, leave the file alone.
"""
import sys, os, re, json, base64, urllib.request, urllib.parse, subprocess
import sys, os, re, json, urllib.request, urllib.parse, subprocess
from pathlib import Path
from difflib import SequenceMatcher
from _spotify_auth import get_token
API = "https://api.spotify.com/v1"
def get_token(cid: str, csec: str) -> str:
creds = base64.b64encode(f"{cid}:{csec}".encode()).decode()
req = urllib.request.Request(
"https://accounts.spotify.com/api/token",
data=urllib.parse.urlencode({"grant_type": "client_credentials"}).encode(),
headers={
"Authorization": f"Basic {creds}",
"Content-Type": "application/x-www-form-urlencoded",
},
)
with urllib.request.urlopen(req, timeout=15) as r:
return json.loads(r.read())["access_token"]
def fetch_playlist(url: str, token: str) -> list[dict]:
m = re.search(r"playlist[/:]([A-Za-z0-9]+)", url)
if not m: