Automations: per-profile playlist source reads in auto-sync (part 2)

The playlist source registry built the Spotify/Tidal adapters with a client
GETTER (resolved fresh on every read), but web_server passed `lambda: <global
client>`. Swapped those to get_spotify_client_for_profile /
get_tidal_client_for_profile.

Combined with part 1 (the engine running each automation as its owner), an
auto-sync pipeline now reads its source playlist through the OWNER's account:
- interactive sync → the user's session profile,
- background automation → the automation owner (via core.profile_context),
- admin / profile 1 → the global client, so the admin's existing auto-sync
  pipelines pull exactly as before.

The adapters re-resolve per read, so a singleton registry is fine. Deezer/Qobuz
getters left global (their playlist login is tangled with downloads — deferred).

Tests: the Spotify/Tidal source adapters resolve the global client under admin
and re-resolve through the profile context per call (unconnected → safe global
fallback). 27 endpoint/profile tests pass.
This commit is contained in:
BoulderBadgeDad 2026-06-10 15:45:45 -07:00
parent 6980253a96
commit 783839349c
2 changed files with 34 additions and 2 deletions

View file

@ -315,3 +315,29 @@ def test_real_session_still_wins_over_background(client, nonadmin_profile):
assert body['is_admin'] is False # it's the non-admin session, not 999/admin
finally:
reset_background_profile(tok)
# ── Part 2: the playlist SOURCE adapters read per-profile (sync handlers) ──────
# bootstrap now passes get_*_client_for_profile as the source adapters'
# client_getter; these prove that composition resolves per the current profile
# context and stays on the global client for admin (the existing pipelines).
def test_spotify_source_adapter_resolves_per_profile():
from core.playlists.sources.spotify import SpotifyPlaylistSource
src = SpotifyPlaylistSource(web_server.get_spotify_client_for_profile)
# admin / no override -> the global client (admin pipelines unchanged)
assert src._client() is web_server.spotify_client
# a background owner override flows through the resolver, re-resolved per
# call (unconnected profile -> safe global fallback, never a frozen client)
from core.profile_context import set_background_profile, reset_background_profile
tok = set_background_profile(424242)
try:
assert src._client() is web_server.spotify_client
finally:
reset_background_profile(tok)
def test_tidal_source_adapter_resolves_per_profile():
from core.playlists.sources.tidal import TidalPlaylistSource
src = TidalPlaylistSource(web_server.get_tidal_client_for_profile)
assert src._client() is web_server.tidal_client # admin -> global, unchanged

View file

@ -1138,8 +1138,14 @@ def _register_automation_handlers():
return match_mb_tracks(tracks, _mb_match_deps)
_playlist_source_registry = build_playlist_source_registry(
spotify_client_getter=lambda: spotify_client,
tidal_client_getter=lambda: tidal_client,
# Per-profile source reads: the adapter calls these fresh on every read,
# so they resolve the CURRENT profile's account (their session
# interactively, their automation's owner in the background — via
# core.profile_context). Admin/profile 1 → the global client, so the
# admin's existing auto-sync pipelines are unchanged. (Deezer/Qobuz stay
# global for now — their playlist login is tangled with downloads.)
spotify_client_getter=get_spotify_client_for_profile,
tidal_client_getter=get_tidal_client_for_profile,
qobuz_client_getter=_get_qobuz_client_for_sync,
deezer_client_getter=_get_deezer_client,
youtube_parser=parse_youtube_playlist,