Improve Plex media search failure feedback (#67493)

This commit is contained in:
jjlawren 2022-03-17 15:57:22 -05:00 committed by GitHub
parent f75d621888
commit b34da1294c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 85 additions and 64 deletions

View File

@ -16,3 +16,7 @@ class ServerNotSpecified(PlexException):
class ShouldUpdateConfigEntry(PlexException):
"""Config entry data is out of date and should be updated."""
class MediaNotFound(PlexException):
"""Requested media was not found."""

View File

@ -19,6 +19,7 @@ from homeassistant.components.media_player.const import (
from homeassistant.components.media_player.errors import BrowseError
from .const import DOMAIN, PLEX_URI_SCHEME
from .errors import MediaNotFound
from .helpers import pretty_title
@ -115,9 +116,9 @@ def browse_media( # noqa: C901
def build_item_response(payload):
"""Create response payload for the provided media query."""
media = plex_server.lookup_media(**payload)
if media is None:
try:
media = plex_server.lookup_media(**payload)
except MediaNotFound:
return None
try:

View File

@ -50,6 +50,7 @@ from .const import (
SERVERS,
TRANSIENT_DEVICE_MODELS,
)
from .errors import MediaNotFound
from .media_browser import browse_media
_LOGGER = logging.getLogger(__name__)
@ -510,7 +511,7 @@ class PlexMediaPlayer(MediaPlayerEntity):
try:
playqueue = self.plex_server.get_playqueue(playqueue_id)
except plexapi.exceptions.NotFound as err:
raise HomeAssistantError(
raise MediaNotFound(
f"PlayQueue '{playqueue_id}' could not be found"
) from err
else:
@ -519,9 +520,6 @@ class PlexMediaPlayer(MediaPlayerEntity):
resume = src.pop("resume", False)
media = self.plex_server.lookup_media(media_type, **src)
if media is None:
raise HomeAssistantError(f"Media could not be found: {media_id}")
if resume and not offset:
offset = media.viewOffset

View File

@ -1,7 +1,13 @@
"""Helper methods to search for Plex media."""
from __future__ import annotations
import logging
from plexapi.base import PlexObject
from plexapi.exceptions import BadRequest, NotFound
from plexapi.library import LibrarySection
from .errors import MediaNotFound
LEGACY_PARAM_MAPPING = {
"show_name": "show.title",
@ -28,13 +34,19 @@ PREFERRED_LIBTYPE_ORDER = (
_LOGGER = logging.getLogger(__name__)
def search_media(media_type, library_section, allow_multiple=False, **kwargs):
def search_media(
media_type: str,
library_section: LibrarySection,
allow_multiple: bool = False,
**kwargs,
) -> PlexObject | list[PlexObject]:
"""Search for specified Plex media in the provided library section.
Returns a single media item or None.
Returns a media item or a list of items if `allow_multiple` is set.
If `allow_multiple` is `True`, return a list of matching items.
Raises MediaNotFound if the search was unsuccessful.
"""
original_query = kwargs.copy()
search_query = {}
libtype = kwargs.pop("libtype", None)
@ -61,11 +73,12 @@ def search_media(media_type, library_section, allow_multiple=False, **kwargs):
try:
results = library_section.search(**search_query)
except (BadRequest, NotFound) as exc:
_LOGGER.error("Problem in query %s: %s", search_query, exc)
return None
raise MediaNotFound(f"Problem in query {original_query}: {exc}") from exc
if not results:
return None
raise MediaNotFound(
f"No {media_type} results in '{library_section.title}' for {original_query}"
)
if len(results) > 1:
if allow_multiple:
@ -75,10 +88,8 @@ def search_media(media_type, library_section, allow_multiple=False, **kwargs):
exact_matches = [x for x in results if x.title.lower() == title.lower()]
if len(exact_matches) == 1:
return exact_matches[0]
_LOGGER.warning(
"Multiple matches, make content_id more specific or use `allow_multiple`: %s",
results,
raise MediaNotFound(
f"Multiple matches, make content_id more specific or use `allow_multiple`: {results}"
)
return None
return results[0]

View File

@ -42,7 +42,12 @@ from .const import (
X_PLEX_PRODUCT,
X_PLEX_VERSION,
)
from .errors import NoServersFound, ServerNotSpecified, ShouldUpdateConfigEntry
from .errors import (
MediaNotFound,
NoServersFound,
ServerNotSpecified,
ShouldUpdateConfigEntry,
)
from .media_search import search_media
from .models import PlexSession
@ -619,37 +624,34 @@ class PlexServer:
key = kwargs["plex_key"]
try:
return self.fetch_item(key)
except NotFound:
_LOGGER.error("Media for key %s not found", key)
return None
except NotFound as err:
raise MediaNotFound(f"Media for key {key} not found") from err
if media_type == MEDIA_TYPE_PLAYLIST:
try:
playlist_name = kwargs["playlist_name"]
return self.playlist(playlist_name)
except KeyError:
_LOGGER.error("Must specify 'playlist_name' for this search")
return None
except NotFound:
_LOGGER.error(
"Playlist '%s' not found",
playlist_name,
)
return None
except KeyError as err:
raise MediaNotFound(
"Must specify 'playlist_name' for this search"
) from err
except NotFound as err:
raise MediaNotFound(f"Playlist '{playlist_name}' not found") from err
try:
library_name = kwargs.pop("library_name")
library_section = self.library.section(library_name)
except KeyError:
_LOGGER.error("Must specify 'library_name' for this search")
return None
except NotFound:
except KeyError as err:
raise MediaNotFound("Must specify 'library_name' for this search") from err
except NotFound as err:
library_sections = [section.title for section in self.library.sections()]
_LOGGER.error(
"Library '%s' not found in %s", library_name, library_sections
)
return None
raise MediaNotFound(
f"Library '{library_name}' not found in {library_sections}"
) from err
_LOGGER.debug(
"Searching for %s in %s using: %s", media_type, library_section, kwargs
)
return search_media(media_type, library_section, **kwargs)
@property

View File

@ -16,6 +16,7 @@ from .const import (
SERVICE_REFRESH_LIBRARY,
SERVICE_SCAN_CLIENTS,
)
from .errors import MediaNotFound
REFRESH_LIBRARY_SCHEMA = vol.Schema(
{vol.Optional("server_name"): str, vol.Required("library_name"): str}
@ -115,15 +116,13 @@ def lookup_plex_media(hass, content_type, content_id):
try:
playqueue = plex_server.get_playqueue(playqueue_id)
except NotFound as err:
raise HomeAssistantError(
raise MediaNotFound(
f"PlayQueue '{playqueue_id}' could not be found"
) from err
return playqueue
shuffle = content.pop("shuffle", 0)
media = plex_server.lookup_media(content_type, **content)
if media is None:
raise HomeAssistantError(f"Plex media not found using payload: '{content_id}'")
if shuffle:
return plex_server.create_playqueue(media, shuffle=shuffle)

View File

@ -16,13 +16,11 @@ from homeassistant.components.media_player.const import (
SERVICE_PLAY_MEDIA,
)
from homeassistant.components.plex.const import DOMAIN
from homeassistant.components.plex.errors import MediaNotFound
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.exceptions import HomeAssistantError
async def test_media_lookups(
hass, mock_plex_server, requests_mock, playqueue_created, caplog
):
async def test_media_lookups(hass, mock_plex_server, requests_mock, playqueue_created):
"""Test media lookups to Plex server."""
# Plex Key searches
media_player_id = hass.states.async_entity_ids("media_player")[0]
@ -39,7 +37,7 @@ async def test_media_lookups(
},
True,
)
with pytest.raises(HomeAssistantError) as excinfo:
with pytest.raises(MediaNotFound) as excinfo:
with patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound):
assert await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
@ -51,10 +49,10 @@ async def test_media_lookups(
},
True,
)
assert "Media could not be found: 123" in str(excinfo.value)
assert "Media for key 123 not found" in str(excinfo.value)
# TV show searches
with pytest.raises(HomeAssistantError) as excinfo:
with pytest.raises(MediaNotFound) as excinfo:
payload = '{"library_name": "Not a Library", "show_name": "TV Show"}'
assert await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
@ -66,7 +64,7 @@ async def test_media_lookups(
},
True,
)
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert "Library 'Not a Library' not found in" in str(excinfo.value)
with patch("plexapi.library.LibrarySection.search") as search:
assert await hass.services.async_call(
@ -243,8 +241,21 @@ async def test_media_lookups(
)
search.assert_called_with(**{"title": "Movie 1", "libtype": None})
# TV show searches
with pytest.raises(HomeAssistantError) as excinfo:
with pytest.raises(MediaNotFound) as excinfo:
payload = '{"title": "Movie 1"}'
assert await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: media_player_id,
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_VIDEO,
ATTR_MEDIA_CONTENT_ID: payload,
},
True,
)
assert "Must specify 'library_name' for this search" in str(excinfo.value)
with pytest.raises(MediaNotFound) as excinfo:
payload = '{"library_name": "Movies", "title": "Not a Movie"}'
with patch("plexapi.library.LibrarySection.search", side_effect=BadRequest):
assert await hass.services.async_call(
@ -257,8 +268,7 @@ async def test_media_lookups(
},
True,
)
assert "Problem in query" in caplog.text
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert "Problem in query" in str(excinfo.value)
# Playlist searches
assert await hass.services.async_call(
@ -272,7 +282,7 @@ async def test_media_lookups(
True,
)
with pytest.raises(HomeAssistantError) as excinfo:
with pytest.raises(MediaNotFound) as excinfo:
payload = '{"playlist_name": "Not a Playlist"}'
assert await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
@ -284,10 +294,9 @@ async def test_media_lookups(
},
True,
)
assert "Playlist 'Not a Playlist' not found" in caplog.text
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert "Playlist 'Not a Playlist' not found" in str(excinfo.value)
with pytest.raises(HomeAssistantError) as excinfo:
with pytest.raises(MediaNotFound) as excinfo:
payload = "{}"
assert await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
@ -299,5 +308,4 @@ async def test_media_lookups(
},
True,
)
assert "Must specify 'playlist_name' for this search" in caplog.text
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert "Must specify 'playlist_name' for this search" in str(excinfo.value)

View File

@ -43,7 +43,6 @@ async def test_media_player_playback(
requests_mock,
playqueue_created,
player_plexweb_resources,
caplog,
):
"""Test playing media on a Plex media_player."""
requests_mock.get("http://1.2.3.5:32400/resources", text=player_plexweb_resources)
@ -68,7 +67,7 @@ async def test_media_player_playback(
},
True,
)
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert f"No {MEDIA_TYPE_MOVIE} results in 'Movies' for" in str(excinfo.value)
movie1 = MockPlexMedia("Movie", "movie")
movie2 = MockPlexMedia("Movie II", "movie")
@ -120,8 +119,7 @@ async def test_media_player_playback(
},
True,
)
assert f"Media could not be found: {payload}" in str(excinfo.value)
assert "Multiple matches, make content_id more specific" in caplog.text
assert "Multiple matches, make content_id more specific" in str(excinfo.value)
# Test multiple choices with allow_multiple
movies = [movie1, movie2, movie3]

View File

@ -168,7 +168,7 @@ async def test_lookup_media_for_other_integrations(
with patch("plexapi.library.LibrarySection.search", return_value=None):
with pytest.raises(HomeAssistantError) as excinfo:
lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_BAD_MEDIA)
assert "Plex media not found" in str(excinfo.value)
assert f"No {MEDIA_TYPE_MUSIC} results in 'Music' for" in str(excinfo.value)
# Test with playqueue
requests_mock.get("https://1.2.3.4:32400/playQueues/1234", text=playqueue_1234)