From 0125f478fcfb0004a17170b8b9b95bff0256bba7 Mon Sep 17 00:00:00 2001 From: Antti Kettunen Date: Tue, 28 Apr 2026 21:52:21 +0300 Subject: [PATCH] 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 --- core/wishlist/processing.py | 8 ++--- core/wishlist/routes.py | 42 +++++++++++--------------- tests/wishlist/test_automation.py | 3 +- tests/wishlist/test_manual_download.py | 2 +- tests/wishlist/test_routes.py | 9 +++--- web_server.py | 10 +----- 6 files changed, 28 insertions(+), 46 deletions(-) diff --git a/core/wishlist/processing.py b/core/wishlist/processing.py index 3856a3da..a6de9989 100644 --- a/core/wishlist/processing.py +++ b/core/wishlist/processing.py @@ -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() diff --git a/core/wishlist/routes.py b/core/wishlist/routes.py index b7e78886..2b767faa 100644 --- a/core/wishlist/routes.py +++ b/core/wishlist/routes.py @@ -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: diff --git a/tests/wishlist/test_automation.py b/tests/wishlist/test_automation.py index d4f10f8b..fa5fd11d 100644 --- a/tests/wishlist/test_automation.py +++ b/tests/wishlist/test_automation.py @@ -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, diff --git a/tests/wishlist/test_manual_download.py b/tests/wishlist/test_manual_download.py index c9b7b927..c462a68c 100644 --- a/tests/wishlist/test_manual_download.py +++ b/tests/wishlist/test_manual_download.py @@ -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(), diff --git a/tests/wishlist/test_routes.py b/tests/wishlist/test_routes.py index 5cdc64fa..89fb8de9 100644 --- a/tests/wishlist/test_routes.py +++ b/tests/wishlist/test_routes.py @@ -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, ) diff --git a/web_server.py b/web_server.py index 30186f86..60723df8 100644 --- a/web_server.py +++ b/web_server.py @@ -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,