Skip to content

refactor(core): provider-neutral external identities; Deezer-free core workflows - #39

Merged
tajanthind merged 1 commit into
mainfrom
fix/provider-neutral-core-cleanup
Sep 3, 2026
Merged

tajanthind merged 1 commit into
mainfrom
fix/provider-neutral-core-cleanup

Conversation

@tajanthind

Copy link
Copy Markdown
Owner

One focused cleanup PR (per the provider-neutral-core cleanup brief). No SDK redesign, no new providers, no capability changes, no versioning.

What changed

Provider-neutral identity (domain model + migration)

  • Artist.spotify_id → Artist.external_id, Album.deezer_id/Track.deezer_id → external_id, DownloadJob.album_spotify_id → album_external_id. Values are opaque provider identities; core never parses or int()s them.
  • Startup migration renames the columns on existing databases (RENAME COLUMN; data, NOT NULL/unique constraints and indexes preserved — verified on the live DB). Fresh DBs get the new names via create_all.
  • SDK TrackRef gains provider-neutral external_id; deezer_id stays as a documented deprecated alias (no plugin reads it).

Deezer-specific logic removed from core workflows

  • Artist search/add/sync, folder + bulk import, discography sync and the periodic scheduler now carry the opaque external id end-to-end — no int(deezer_artist_id), no isdigit() heuristics, no "resolve Deezer ID".
  • Indexing/pruning/ISRC lookups/dedup use the external id generically. MetadataService docstrings no longer name any provider authoritative.
  • Deezer-specific conversion/validation stays in fnack.deezer-batch (it int()s its own ids and declines foreign ids gracefully — the chain moves on).
  • Legacy wire compat isolated + documented: /set-external-id also answers the old /set-deezer-id path/body key; import accepts deezer_id as an alias of external_id; candidate suggestions now emit suggested_external_id (suggested_deezer kept as alias) and the frontend was updated to the neutral keys.
  • Manual-match user-pasted Deezer URLs + provider-URL help text = user-facing input handling; lookups go through the capability chain (annotated).

Tests

  • New tests/architecture/test_provider_neutral_identity.py (Tests A–D): fake non-integer (fake-artist-123) and colon-prefixed (artist:abc:def) identities flow through search/discography; the Deezer plugin converts ids inside the plugin and declines foreign ones; provider removal leaves the chain working; zero providers → CapabilityUnavailable, no hidden Deezer fallback.
  • Background app-context regression (real _process_track_job on a ThreadPoolExecutor worker with a settings-reading provider) unchanged + green; the structural test still covers all three protected entry points.
  • Architecture suite 22/22, smoke PASSED, fnack-plugins parity OK (bundled plugins untouched, byte-identical).

Live verification

Container rebuilt from this code: the live DB migrated in place; Arctic Monkeys was added via search → discography synced (32 albums / 150 tracks) through the opaque-external-id path → "Do I Wanna Know?" downloaded, verified, and accepted through the queue worker (ISRC lookup + settings read from the worker thread).

Remaining provider mentions in core (all classified)

  • Factual plugin-arrangement comments ("core has no Deezer implementation").
  • Documented legacy wire aliases + the old route path (migration compat).
  • User-facing manual-match provider-URL text + parsing.
  • SDK deezer_id deprecated alias + TrackRef comment examples.
  • The essential-plugin packaging doc (plugin catalog descriptions).
  • Migration table column names.

…re workflows (final cleanup)

Core no longer carries Deezer-specific identity fields, naming, or
behavioral assumptions. One focused cleanup PR (no SDK redesign, no new
providers, no versioning).

Domain model (provider-neutral identity):
- Artist.spotify_id -> Artist.external_id; Album/Track.deezer_id ->
  external_id; DownloadJob.album_spotify_id -> album_external_id. Values are
  OPAQUE provider identities; core never parses/int()s them.
- Startup migration renames the columns on existing databases (SQLite
  RENAME COLUMN; data, NOT NULL/unique constraints and indexes preserved;
  fresh DBs get the new names via create_all).
- SDK TrackRef gains provider-neutral external_id; deezer_id remains as a
  documented deprecated alias (no plugin reads it).

Core workflows (artist search/import/sync/download/import/metadata):
- _sync_artist_discography_background / _start_bounded_artist_sync /
  api_add_artist / api_artist_sync / folder + bulk import / periodic
  scheduler: external identity is an opaque string end-to-end; no int(),
  no isdigit() heuristic, no "resolve Deezer ID".
- Indexing/pruning/ISRC lookups/dedup use the external id generically;
  MetadataService docstrings no longer name any provider authoritative or
  interpret provider ids; the deezer plugin owns conversion/validation and
  declines foreign ids gracefully (chain moves on, no crash).
- Legacy wire compatibility isolated + documented: /set-external-id accepts
  the old /set-deezer-id path and body key; import accepts deezer_id as an
  alias of external_id; the import-candidate suggestion is emitted under
  suggested_external_id (suggested_deezer kept as an alias) and the
  frontend was updated to the neutral keys.
- Manual-match user-pasted Deezer URLs and the provider-URL help text are
  user-facing input handling; the lookups they trigger go through the
  capability chain (annotated).

Tests:
- tests/architecture/test_provider_neutral_identity.py (new): fake
  non-integer (fake-artist-123) and colon-prefixed (artist:abc:def)
  identities flow through search/discography; the Deezer plugin converts ids
  inside the plugin and declines foreign ones; removing the provider leaves
  the chain working, and zero providers raises CapabilityUnavailable (no
  hidden Deezer fallback).
- Background app-context regression (real _process_track_job on a
  ThreadPoolExecutor worker with a settings-reading provider) unchanged and
  green; structural test still covers all three protected entry points.

Docs: docs/architecture.md gained a provider-neutral identity section and
no longer calls any provider authoritative; wayfinder records the change.

Verified: architecture suite 22/22, smoke PASSED, fnack-plugins parity OK
(bundled plugins untouched, byte-identical). Live container: DB migrated in
place, Arctic Monkeys added + discography synced (32 albums/150 tracks) and
'Do I Wanna Know?' downloaded/verified through the queue worker.
@tajanthind
tajanthind merged commit 51af375 into main Sep 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant