Trim wishlist runtime plumbing

- remove the redundant wishlist-service injection from the runtime wrappers
- keep the package owning its own singleton service access
- simplify the route runtime API and update the wishlist tests to match
This commit is contained in:
Antti Kettunen 2026-04-28 21:52:21 +03:00
parent 7f3272f3ba
commit 0125f478fc
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
6 changed files with 28 additions and 46 deletions

View file

@ -11,6 +11,7 @@ from typing import Any, Callable, Dict
from core.wishlist.payloads import build_failed_track_wishlist_context
from core.wishlist.selection import filter_wishlist_tracks_by_category, sanitize_and_dedupe_wishlist_tracks
from core.wishlist.service import get_wishlist_service
from core.wishlist.state import get_wishlist_cycle, set_wishlist_cycle
from utils.logging_config import get_logger
@ -26,7 +27,6 @@ class WishlistAutoProcessingRuntime:
processing_guard: Callable[[], AbstractContextManager[bool]]
is_actually_processing: Callable[[], bool]
app_context_factory: Callable[[], AbstractContextManager[Any]]
get_wishlist_service: Callable[[], Any]
get_profiles_database: Callable[[], Any]
get_music_database: Callable[[], Any]
download_batches: Dict[str, Dict[str, Any]]
@ -292,7 +292,6 @@ def remove_tracks_already_in_library(
class WishlistManualDownloadRuntime:
"""Dependencies needed to start a manual wishlist download batch outside the controller."""
get_wishlist_service: Callable[[], Any]
get_music_database: Callable[[], Any]
download_batches: Dict[str, Dict[str, Any]]
tasks_lock: Any
@ -316,7 +315,7 @@ def start_manual_wishlist_download_batch(
logger = runtime.logger
try:
wishlist_service = runtime.get_wishlist_service()
wishlist_service = get_wishlist_service()
db = runtime.get_music_database()
manual_profile_id = runtime.profile_id
@ -471,7 +470,7 @@ def process_wishlist_automatically(runtime: WishlistAutoProcessingRuntime, autom
return
with runtime.app_context_factory():
wishlist_service = runtime.get_wishlist_service()
wishlist_service = get_wishlist_service()
# Check if wishlist has tracks across all profiles
database = runtime.get_profiles_database()
@ -627,7 +626,6 @@ def automatic_wishlist_cleanup_after_db_update(
"""Remove wishlist entries that already exist in the library after a DB update."""
try:
from config.settings import config_manager
from core.wishlist.service import get_wishlist_service
from database.music_database import MusicDatabase, get_database
wishlist_service = wishlist_service or get_wishlist_service()

View file

@ -10,6 +10,7 @@ from typing import Any, Callable, Dict
from core.wishlist.reporting import build_wishlist_stats_payload
from core.wishlist.selection import prepare_wishlist_tracks_for_display
from core.wishlist.service import get_wishlist_service
from core.wishlist.state import get_wishlist_cycle as _get_wishlist_cycle
from core.wishlist.state import set_wishlist_cycle as _set_wishlist_cycle
from utils.logging_config import get_logger
@ -23,13 +24,11 @@ logger = module_logger
class WishlistRouteRuntime:
"""Dependencies needed to service wishlist HTTP endpoints outside the controller."""
get_wishlist_service: Callable[[], Any]
get_music_database: Callable[[], Any]
get_current_profile_id: Callable[[], int]
profile_id: int
download_batches: Dict[str, Dict[str, Any]]
download_tasks: Dict[str, Dict[str, Any]]
tasks_lock: Any
is_wishlist_auto_processing_flag: Callable[[], bool]
is_wishlist_actually_processing: Callable[[], bool]
reset_wishlist_processing_state: Callable[[], None]
add_activity_item: Callable[[Any, Any, Any, Any], Any]
@ -113,7 +112,7 @@ def process_wishlist_api(
) -> tuple[Dict[str, Any], int]:
"""Trigger wishlist processing in the background."""
try:
if runtime.is_wishlist_auto_processing_flag():
if runtime.is_wishlist_actually_processing():
return {"success": False, "error": "Wishlist processing already in progress"}, 409
thread = runtime.thread_factory(target=start_processing, daemon=True)
@ -127,8 +126,7 @@ def process_wishlist_api(
def get_wishlist_count(runtime: WishlistRouteRuntime) -> tuple[Dict[str, Any], int]:
"""Return the current wishlist count for the active profile."""
try:
wishlist_service = runtime.get_wishlist_service()
count = wishlist_service.get_wishlist_count(profile_id=runtime.get_current_profile_id())
count = get_wishlist_service().get_wishlist_count(profile_id=runtime.profile_id)
return {"count": count}, 200
except Exception as exc:
runtime.logger.error("Error getting wishlist count: %s", exc)
@ -138,8 +136,7 @@ def get_wishlist_count(runtime: WishlistRouteRuntime) -> tuple[Dict[str, Any], i
def get_wishlist_stats(runtime: WishlistRouteRuntime) -> tuple[Dict[str, Any], int]:
"""Return wishlist statistics for the UI."""
try:
wishlist_service = runtime.get_wishlist_service()
raw_tracks = wishlist_service.get_wishlist_tracks_for_download(profile_id=runtime.get_current_profile_id())
raw_tracks = get_wishlist_service().get_wishlist_tracks_for_download(profile_id=runtime.profile_id)
next_run_in_seconds = runtime.get_next_run_seconds("process_wishlist") if runtime.get_next_run_seconds else 0
is_processing = runtime.is_wishlist_actually_processing()
current_cycle = _get_wishlist_cycle(runtime.get_music_database)
@ -188,7 +185,6 @@ def get_wishlist_tracks(
) -> tuple[Dict[str, Any], int]:
"""Return wishlist tracks for the modal UI."""
try:
wishlist_service = runtime.get_wishlist_service()
db = runtime.get_music_database()
with runtime.tasks_lock:
@ -198,13 +194,13 @@ def get_wishlist_tracks(
)
if not wishlist_batch_active:
duplicates_removed = db.remove_wishlist_duplicates(profile_id=runtime.get_current_profile_id())
duplicates_removed = db.remove_wishlist_duplicates(profile_id=runtime.profile_id)
if duplicates_removed > 0:
runtime.logger.warning("Cleaned %s duplicate tracks from wishlist", duplicates_removed)
else:
runtime.logger.warning("Skipping wishlist duplicate cleanup - download in progress")
raw_tracks = wishlist_service.get_wishlist_tracks_for_download(profile_id=runtime.get_current_profile_id())
raw_tracks = get_wishlist_service().get_wishlist_tracks_for_download(profile_id=runtime.profile_id)
prepared = prepare_wishlist_tracks_for_display(raw_tracks, category=category, limit=limit)
if prepared["duplicates_found"] > 0:
@ -232,8 +228,7 @@ def get_wishlist_tracks(
def clear_wishlist(runtime: WishlistRouteRuntime) -> tuple[Dict[str, Any], int]:
"""Clear the wishlist and cancel active wishlist batches."""
try:
wishlist_service = runtime.get_wishlist_service()
success = wishlist_service.clear_wishlist(profile_id=runtime.get_current_profile_id())
success = get_wishlist_service().clear_wishlist(profile_id=runtime.profile_id)
if success:
cancelled_count = 0
@ -282,10 +277,9 @@ def remove_track_from_wishlist(
if not spotify_track_id:
return {"success": False, "error": "No spotify_track_id provided"}, 400
wishlist_service = runtime.get_wishlist_service()
success = wishlist_service.remove_track_from_wishlist(
success = get_wishlist_service().remove_track_from_wishlist(
spotify_track_id,
profile_id=runtime.get_current_profile_id(),
profile_id=runtime.profile_id,
)
if success:
@ -310,8 +304,8 @@ def remove_album_from_wishlist(
if not album_id and not album_name_filter:
return {"success": False, "error": "No album_id or album_name provided"}, 400
wishlist_service = runtime.get_wishlist_service()
all_tracks = wishlist_service.get_wishlist_tracks_for_download(profile_id=runtime.get_current_profile_id())
wishlist_service = get_wishlist_service()
all_tracks = wishlist_service.get_wishlist_tracks_for_download(profile_id=runtime.profile_id)
tracks_to_remove = []
for track in all_tracks:
@ -334,7 +328,7 @@ def remove_album_from_wishlist(
tracks_to_remove.append(spotify_track_id)
removed_count = 0
album_remove_pid = runtime.get_current_profile_id()
album_remove_pid = runtime.profile_id
for spotify_track_id in tracks_to_remove:
if wishlist_service.remove_track_from_wishlist(spotify_track_id, profile_id=album_remove_pid):
removed_count += 1
@ -363,11 +357,10 @@ def remove_batch_from_wishlist(
if not spotify_track_ids or not isinstance(spotify_track_ids, list):
return {"success": False, "error": "Missing or invalid spotify_track_ids"}, 400
wishlist_service = runtime.get_wishlist_service()
removed = 0
pid = runtime.get_current_profile_id()
pid = runtime.profile_id
for track_id in spotify_track_ids:
if wishlist_service.remove_track_from_wishlist(track_id, profile_id=pid):
if get_wishlist_service().remove_track_from_wishlist(track_id, profile_id=pid):
removed += 1
runtime.logger.info("Batch removed %s track(s) from wishlist", removed)
@ -406,13 +399,12 @@ def add_album_track_to_wishlist(
"added_via": "library_wishlist_modal",
}
wishlist_service = runtime.get_wishlist_service()
success = wishlist_service.add_spotify_track_to_wishlist(
success = get_wishlist_service().add_spotify_track_to_wishlist(
spotify_track_data=spotify_track_data,
failure_reason="Added from library (incomplete album)",
source_type=source_type,
source_context=enhanced_source_context,
profile_id=runtime.get_current_profile_id(),
profile_id=runtime.profile_id,
)
if success:

View file

@ -1,6 +1,7 @@
from contextlib import contextmanager
from types import SimpleNamespace
from core.wishlist import processing
from core.wishlist.processing import WishlistAutoProcessingRuntime, process_wishlist_automatically
@ -139,6 +140,7 @@ def _build_runtime(
batch_map = {}
wishlist_service = _FakeWishlistService(tracks, count=count)
processing.get_wishlist_service = lambda: wishlist_service
profiles_db = _FakeProfilesDatabase(profiles or [{"id": 1}])
music_db = _FakeMusicDatabase(cycle_value=cycle_value)
executor = _FakeExecutor()
@ -162,7 +164,6 @@ def _build_runtime(
runtime = WishlistAutoProcessingRuntime(
processing_guard=guard,
app_context_factory=app_context,
get_wishlist_service=lambda: wishlist_service,
get_profiles_database=lambda: profiles_db,
get_music_database=lambda: music_db,
download_batches=batch_map,

View file

@ -80,6 +80,7 @@ class _FakeLock:
def _build_runtime(tracks, owned_matches=None, batch_map=None):
wishlist_service = _FakeWishlistService(tracks)
processing.get_wishlist_service = lambda: wishlist_service
music_db = _FakeMusicDatabase(owned_matches=owned_matches)
executor = _FakeExecutor()
logger = _FakeLogger()
@ -87,7 +88,6 @@ def _build_runtime(tracks, owned_matches=None, batch_map=None):
batch_map = batch_map or {}
runtime = WishlistManualDownloadRuntime(
get_wishlist_service=lambda: wishlist_service,
get_music_database=lambda: music_db,
download_batches=batch_map,
tasks_lock=_FakeLock(),

View file

@ -1,5 +1,6 @@
import json
import core.wishlist.routes as routes_module
from core.wishlist.routes import (
WishlistRouteRuntime,
add_album_track_to_wishlist,
@ -148,7 +149,6 @@ def _build_runtime(
cycle_value="albums",
duplicate_removals=0,
clear_result=True,
auto_processing_flag=False,
actually_processing=False,
next_run_seconds=0,
download_batches=None,
@ -157,17 +157,16 @@ def _build_runtime(
reset_callback=None,
):
service = _FakeWishlistService(tracks=tracks, count=count, clear_result=clear_result)
routes_module.get_wishlist_service = lambda: service
db = _FakeMusicDatabase(cycle_value=cycle_value, duplicate_removals=duplicate_removals)
logger = _FakeLogger()
activity_calls = []
runtime = WishlistRouteRuntime(
get_wishlist_service=lambda: service,
get_music_database=lambda: db,
get_current_profile_id=lambda: 1,
profile_id=1,
download_batches=download_batches if download_batches is not None else {},
download_tasks=download_tasks if download_tasks is not None else {},
tasks_lock=_FakeLock(),
is_wishlist_auto_processing_flag=lambda: auto_processing_flag,
is_wishlist_actually_processing=lambda: actually_processing,
reset_wishlist_processing_state=reset_callback or (lambda: None),
add_activity_item=lambda *args: activity_calls.append(args),
@ -200,7 +199,7 @@ def test_process_wishlist_api_starts_background_thread_when_idle():
def test_process_wishlist_api_rejects_when_flag_is_set():
thread_factory = _FakeThreadFactory()
runtime, _service, _db, logger, _activity_calls = _build_runtime(
auto_processing_flag=True,
actually_processing=True,
thread_factory=thread_factory,
)

View file

@ -17529,7 +17529,6 @@ def _process_wishlist_automatically(automation_id=None):
processing_guard=_processing_guard,
is_actually_processing=is_wishlist_actually_processing,
app_context_factory=lambda: app.app_context(),
get_wishlist_service=get_wishlist_service,
get_profiles_database=get_database,
get_music_database=MusicDatabase,
download_batches=download_batches,
@ -18344,22 +18343,18 @@ def get_wishlist_tracks():
def _build_wishlist_route_runtime(
*,
is_auto_processing_flag=None,
is_actually_processing_fn=None,
reset_wishlist_processing_state=None,
get_next_run_seconds=None,
):
from core.wishlist_service import get_wishlist_service
from database.music_database import MusicDatabase
return _WishlistRouteRuntime(
get_wishlist_service=get_wishlist_service,
get_music_database=MusicDatabase,
get_current_profile_id=get_current_profile_id,
profile_id=get_current_profile_id(),
download_batches=download_batches,
download_tasks=download_tasks,
tasks_lock=tasks_lock,
is_wishlist_auto_processing_flag=is_auto_processing_flag or (lambda: wishlist_auto_processing),
is_wishlist_actually_processing=is_actually_processing_fn or is_wishlist_actually_processing,
reset_wishlist_processing_state=reset_wishlist_processing_state or (lambda: None),
add_activity_item=add_activity_item,
@ -18385,14 +18380,11 @@ def start_wishlist_missing_downloads():
}), 409
data = request.get_json() or {}
from core.wishlist_service import get_wishlist_service
from database.music_database import MusicDatabase
wishlist_service = get_wishlist_service()
db = MusicDatabase()
manual_profile_id = get_current_profile_id()
manual_runtime = _WishlistManualDownloadRuntime(
get_wishlist_service=lambda: wishlist_service,
get_music_database=lambda: db,
download_batches=download_batches,
tasks_lock=tasks_lock,