Conversation
Add library provider_mappings to smart playlists when loaded via get_recommendation_items() and get_playlist(). This enables the frontend to correctly match smart playlists pinned from recommendation rows with their library equivalents, fixing an issue where these shortcuts would not appear in the sidebar. The issue was introduced in music-assistant#4487 when recommendation loading was changed to on-demand - playlists from get_recommendation_items() never had library mappings, preventing identity matching.
|
✅ The title and description are good to go. Thanks! |
There was a problem hiding this comment.
Pull request overview
This PR aims to fix smart playlists pinned from recommendation rows not appearing in the frontend sidebar by ensuring smart-playlist items can be correlated with their library counterparts during URI-based lookups.
Changes:
- Adds logic in
get_recommendation_items()to look up a corresponding library playlist for each smart playlist. - Adds similar library lookup logic in
get_playlist()when resolving a smart playlist by provider ID/URI.
Suppressed comments (1)
music_assistant/providers/smart_playlist/init.py:366
- [CRITICAL] Same issue here: injecting a
ProviderMappingwithprovider_instance='library'/provider_domain='library'intoprovider_mappingscan later trip assertions in library-matching code that expects provider mappings to always reference real providers. Returning the existing library playlist (when present) avoids corrupting provider_mappings and still yields the correct identity for the client.
try:
lib_item = await self.mass.music.playlists.get_library_item_by_prov_id(
resolved_id, self.instance_id
)
if lib_item:
playlist.provider_mappings.add(
ProviderMapping(
item_id=str(lib_item.item_id),
provider_domain="library",
provider_instance="library",
)
)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| playlist.provider_mappings.add( | ||
| ProviderMapping( | ||
| item_id=library_mappings[pid], | ||
| provider_domain="library", | ||
| provider_instance="library", | ||
| ) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (4)
music_assistant/providers/smart_playlist/init.py:358
- [PROBLEM]
get_playlistnow always performs two library lookups (one inside_build_playlistvia_images_for, then another here to add the library ProviderMapping), doubling DB/API work on a hot path. Consider fetching the library item once and reusing it for both images and the mapping (e.g., pass a prefetched library item into_build_playlist/_images_foror add a small in-method cache).
playlist = await self._build_playlist(resolved_id, rules)
try:
lib_item = await self.mass.music.playlists.get_library_item_by_prov_id(
resolved_id, self.instance_id
)
tests/providers/smart_playlist/test_recommendations.py:112
- [PROBLEM] This mock returns a truthy
metadata.imagesMagicMock by default, so_images_for()may treat it as artwork and return a MagicMock instead of an empty list, making the test less representative and potentially fragile. Setmetadata.imagesto an empty list explicitly.
lib_playlist = MagicMock()
lib_playlist.item_id = 456
mass.music.playlists.get_library_item_by_prov_id = AsyncMock(return_value=lib_playlist)
music_assistant/providers/smart_playlist/init.py:326
- [PROBLEM]
get_recommendation_itemsiterates directly overself._rules_storewhile awaiting I/O, which can raiseRuntimeError: dictionary changed size during iterationif rules are added/removed concurrently; it also performs the library lookups sequentially, increasing worst-case latency for rows with many playlists. Snapshot the items first and run the library lookups concurrently (withreturn_exceptions=True) before building playlists.
library_mappings: dict[str, str] = {}
for pid in self._rules_store:
try:
lib_item = await self.mass.music.playlists.get_library_item_by_prov_id(
pid, self.instance_id
tests/providers/smart_playlist/test_recommendations.py:91
- [PROBLEM] This mock returns a truthy
metadata.imagesMagicMock by default, so_images_for()may treat it as artwork and return a MagicMock instead of an empty list, making the test less representative and potentially fragile. Setmetadata.imagesto an empty list explicitly.
This issue also appears on line 110 of the same file.
lib_playlist = MagicMock()
lib_playlist.item_id = 123
mass.music.playlists.get_library_item_by_prov_id = AsyncMock(return_value=lib_playlist)
What does this implement/fix?
Fixes smart playlists pinned from recommendation rows not appearing in the sidebar. The issue was introduced in #4487 when recommendation loading was changed to on-demand.
When a user pins a smart playlist from a recommendation row, the frontend saves a URI like
smart_playlist--1://playlist/abc. When the shortcut system loads this URI viagetItemByUri(), the server returns a playlist without library provider_mappings. This prevents the frontend from matching it with already-pinned library versions, causing the shortcut to not appear in the sidebar even though it's correctly saved in preferences.Solution:
Add library provider_mappings to smart playlists when loaded via:
get_recommendation_items()- for recommendation row items (batch lookup for performance)get_playlist()- when loading by URIThis enables frontend identity matching to work correctly.
Related issue (if applicable):
Types of changes
bugfixnew-featureenhancementnew-providerbreaking-changerefactordocumentationmaintenancecidependenciesChecklist
pre-commit run --all-filespasses.pytestpasses, and tests have been added/updated undertests/where applicable.music-assistant/modelsis linked.music-assistant/frontendis linked: music-assistant/frontend#[PR_NUMBER]