Only import Plex and Spotify in Sonos when they are set up (#183285)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Franck Nijhof
2026-09-27 15:21:36 +02:00
committed by GitHub
co-authored by Claude
parent be6339d7f8
commit f9dec6e96c
5 changed files with 172 additions and 20 deletions
+3
View File
@@ -213,3 +213,6 @@ SCAN_INTERVAL = datetime.timedelta(seconds=10)
DISCOVERY_INTERVAL = datetime.timedelta(seconds=60)
SUBSCRIPTION_TIMEOUT = 1200
LONG_SERVICE_TIMEOUT = 30.0
# Same as the Plex integration, which is only imported when used
PLEX_URI_SCHEME = "plex://"
+20 -11
View File
@@ -11,7 +11,7 @@ from soco.data_structures import DidlContainer, DidlObject
from soco.ms_data_structures import MusicServiceItem
from soco.music_library import MusicLibrary
from homeassistant.components import media_source, plex, spotify
from homeassistant.components import media_source
from homeassistant.components.media_player import (
BrowseError,
BrowseMedia,
@@ -152,18 +152,25 @@ async def async_browse_media(
hass, media_content_id, content_filter=media_source_filter
)
if plex.is_plex_media_id(media_content_id):
return await plex.async_browse_media(
hass, media_content_type, media_content_id, platform=DOMAIN
)
# Plex and Spotify are only imported once set up, as they are heavy to load
if "plex" in hass.config.components:
from homeassistant.components import plex # noqa: PLC0415
if media_content_type == "plex":
return await plex.async_browse_media(hass, None, None, platform=DOMAIN)
if plex.is_plex_media_id(media_content_id):
return await plex.async_browse_media(
hass, media_content_type, media_content_id, platform=DOMAIN
)
if spotify.is_spotify_media_type(media_content_type):
return await spotify.async_browse_media(
hass, media_content_type, media_content_id, can_play_artist=False
)
if media_content_type == "plex":
return await plex.async_browse_media(hass, None, None, platform=DOMAIN)
if "spotify" in hass.config.components:
from homeassistant.components import spotify # noqa: PLC0415
if spotify.is_spotify_media_type(media_content_type):
return await spotify.async_browse_media(
hass, media_content_type, media_content_id, can_play_artist=False
)
if media_content_type == "library":
return await hass.async_add_executor_job(
@@ -420,6 +427,8 @@ async def root_payload(
)
if "spotify" in hass.config.components:
from homeassistant.components import spotify # noqa: PLC0415
result = await spotify.async_browse_media(hass, None, None)
if result.children:
children.extend(result.children)
+14 -8
View File
@@ -20,7 +20,7 @@ from soco.ms_data_structures import MusicServiceItem
from sonos_websocket import CLIP_ID_KEY
from sonos_websocket.exception import SonosWebsocketError
from homeassistant.components import media_source, spotify
from homeassistant.components import media_source
from homeassistant.components.media_player import (
ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_ANNOUNCE,
@@ -41,10 +41,6 @@ from homeassistant.components.media_player import (
SearchMediaQuery,
async_process_play_media_url,
)
from homeassistant.components.plex import PLEX_URI_SCHEME
from homeassistant.components.plex.services import ( # pylint: disable=home-assistant-component-root-import
process_plex_payload,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
@@ -63,6 +59,7 @@ from .const import (
MODELS_LINEIN_ONLY,
MODELS_TV_ONLY,
PLAYABLE_MEDIA_TYPES,
PLEX_URI_SCHEME,
SONOS_CREATE_MEDIA_PLAYER,
SONOS_FAVORITES_UPDATED,
SONOS_MEDIA_UPDATED,
@@ -561,9 +558,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
},
)
if spotify.is_spotify_media_type(media_type):
media_type = spotify.resolve_spotify_media_type(media_type)
media_id = spotify.spotify_uri_from_media_browser_url(media_id)
# Spotify is only imported once set up, as it is heavy to load
if "spotify" in self.hass.config.components:
from homeassistant.components import spotify # noqa: PLC0415
if spotify.is_spotify_media_type(media_type):
media_type = spotify.resolve_spotify_media_type(media_type)
media_id = spotify.spotify_uri_from_media_browser_url(media_id)
await self.hass.async_add_executor_job(
partial(self._play_media, media_type, media_id, is_radio, **kwargs)
@@ -586,6 +587,11 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
soco = self.coordinator.soco
if media_id and media_id.startswith(PLEX_URI_SCHEME):
# Runs in the executor, only load Plex when playing Plex media
from homeassistant.components.plex.services import ( # noqa: PLC0415 # pylint: disable=home-assistant-component-root-import
process_plex_payload,
)
plex_plugin = self.speaker.plex_plugin
result = process_plex_payload(
self.hass, media_type, media_id, supports_playqueues=False
+113 -1
View File
@@ -1,7 +1,7 @@
"""Tests for the Sonos Media Browser."""
from functools import partial
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, patch
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -269,6 +269,118 @@ async def test_browse_media_root(
assert response["result"]["children"] == snapshot
@pytest.mark.parametrize(
("media_content_type", "media_content_id", "target", "expected_args", "kwargs"),
[
pytest.param(
"music",
"plex://1/2",
"homeassistant.components.plex.async_browse_media",
("music", "plex://1/2"),
{"platform": "sonos"},
id="plex_item",
),
pytest.param(
"plex",
"",
"homeassistant.components.plex.async_browse_media",
(None, None),
{"platform": "sonos"},
id="plex_root",
),
pytest.param(
"spotify://library",
"spotify://entry_id",
"homeassistant.components.spotify.async_browse_media",
("spotify://library", "spotify://entry_id"),
{"can_play_artist": False},
id="spotify",
),
],
)
@pytest.mark.usefixtures("soco_factory", "async_autosetup_sonos", "soco")
async def test_browse_media_plex_spotify(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
media_content_type: str,
media_content_id: str,
target: str,
expected_args: tuple[str | None, str | None],
kwargs: dict[str, str | bool],
) -> None:
"""Test browsing is passed to Plex and Spotify when they are set up."""
hass.config.components.update({"plex", "spotify"})
result = BrowseMedia(
title="Result",
media_class=MediaClass.DIRECTORY,
media_content_id="result",
media_content_type="result",
can_play=False,
can_expand=True,
)
client = await hass_ws_client()
with patch(target, return_value=result) as mock_browse:
await client.send_json(
{
"id": 1,
"type": "media_player/browse_media",
"entity_id": "media_player.zone_a",
"media_content_id": media_content_id,
"media_content_type": media_content_type,
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"]["title"] == "Result"
mock_browse.assert_called_once_with(hass, *expected_args, **kwargs)
@pytest.mark.usefixtures("soco_factory", "async_autosetup_sonos", "soco")
async def test_browse_media_root_spotify(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the root includes Spotify when it is set up."""
hass.config.components.add("spotify")
spotify_item = BrowseMedia(
title="Spotify",
media_class=MediaClass.APP,
media_content_id="spotify://entry_id",
media_content_type="spotify://library",
can_play=False,
can_expand=True,
)
spotify_root = BrowseMedia(
title="Spotify",
media_class=MediaClass.APP,
media_content_id="spotify://",
media_content_type="spotify",
can_play=False,
can_expand=True,
children=[spotify_item],
)
client = await hass_ws_client()
with patch(
"homeassistant.components.spotify.async_browse_media",
return_value=spotify_root,
):
await client.send_json(
{
"id": 1,
"type": "media_player/browse_media",
"entity_id": "media_player.zone_a",
}
)
response = await client.receive_json()
assert response["success"]
assert "spotify://entry_id" in [
child["media_content_id"] for child in response["result"]["children"]
]
async def test_browse_media_library(
hass: HomeAssistant,
soco_factory: SoCoMockFactory,
@@ -537,6 +537,28 @@ async def test_play_media_share_link_add(
)
@pytest.mark.usefixtures("soco_factory", "async_autosetup_sonos")
async def test_play_media_spotify_media_browser_url(
hass: HomeAssistant, soco_sharelink: MagicMock
) -> None:
"""Tests playing a Spotify media browser URL when Spotify is set up."""
hass.config.components.add("spotify")
await hass.services.async_call(
MP_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.zone_a",
ATTR_MEDIA_CONTENT_TYPE: "spotify://playlist",
ATTR_MEDIA_CONTENT_ID: f"spotify://01J5TX5A0FF6G5V0QJX6HBC94T/{_share_link}",
},
blocking=True,
)
assert soco_sharelink.add_share_link_to_queue.call_count == 1
assert (
soco_sharelink.add_share_link_to_queue.call_args_list[0].args[0] == _share_link
)
async def test_play_media_share_link_next(
hass: HomeAssistant,
soco_factory: SoCoMockFactory,