Merge pull request #741 from JonnyWong16/feature/tag_items

Add ability to retrieve a list of items and collection object from media tags
This commit is contained in:
JonnyWong16 2021-05-23 19:01:07 -07:00 committed by GitHub
commit 8c2b3ce063
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 258 additions and 125 deletions

View file

@ -1698,6 +1698,12 @@ class HubMediaTag(PlexObject):
self.tagValue = utils.cast(int, data.attrib.get('tagValue'))
self.thumb = data.attrib.get('thumb')
def items(self, *args, **kwargs):
""" Return the list of items within this tag. """
if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
return self.fetchItems(self.key)
@utils.registerPlexObject
class Tag(HubMediaTag):

View file

@ -648,59 +648,43 @@ class MediaTag(PlexObject):
the construct used for things such as Country, Director, Genre, etc.
Attributes:
server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
filter (str): The library filter for the tag.
id (id): Tag ID (This seems meaningless except to use it as a unique id).
role (str): Unknown
key (str): API URL (/library/section/<librarySectionID>/all?<filter>).
role (str): The name of the character role for :class:`~plexapi.media.Role` only.
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
person for Directors and Roles (ex: Animation, Stephen Graham, etc).
<Hub_Search_Attributes>: Attributes only applicable in search results from
PlexServer :func:`~plexapi.server.PlexServer.search`. They provide details of which
library section the tag was found as well as the url to dig deeper into the results.
* key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081).
* librarySectionID (int): Section ID this tag was generated from.
* librarySectionTitle (str): Library section title this tag was found.
* librarySectionType (str): Media type of the library section this tag was found.
* tagType (int): Tag type ID.
* thumb (str): URL to thumbnail image.
thumb (str): URL to thumbnail image for :class:`~plexapi.media.Role` only.
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.filter = data.attrib.get('filter')
self.id = cast(int, data.attrib.get('id'))
self.key = data.attrib.get('key')
self.role = data.attrib.get('role')
self.tag = data.attrib.get('tag')
# additional attributes only from hub search
self.key = data.attrib.get('key')
self.librarySectionID = cast(int, data.attrib.get('librarySectionID'))
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.librarySectionType = data.attrib.get('librarySectionType')
self.tagType = cast(int, data.attrib.get('tagType'))
self.thumb = data.attrib.get('thumb')
def items(self, *args, **kwargs):
""" Return the list of items within this tag. This function is only applicable
in search results from PlexServer :func:`~plexapi.server.PlexServer.search`.
"""
parent = self._parent()
self._librarySectionID = utils.cast(int, parent._data.attrib.get('librarySectionID'))
self._librarySectionKey = parent._data.attrib.get('librarySectionKey')
self._librarySectionTitle = parent._data.attrib.get('librarySectionTitle')
self._parentType = parent.TYPE
if self._librarySectionKey and self.filter:
self.key = '%s/all?%s&type=%s' % (
self._librarySectionKey, self.filter, utils.searchType(self._parentType))
def items(self):
""" Return the list of items within this tag. """
if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
raise BadRequest('Key is not defined for this tag: %s. '
'Reload the parent object.' % self.tag)
return self.fetchItems(self.key)
class GuidTag(PlexObject):
""" Base class for guid tags used only for Guids, as they contain only a string identifier
Attributes:
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB).
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.id = data.attrib.get('id')
@utils.registerPlexObject
class Collection(MediaTag):
""" Represents a single Collection media tag.
@ -712,36 +696,11 @@ class Collection(MediaTag):
TAG = 'Collection'
FILTER = 'collection'
@utils.registerPlexObject
class Label(MediaTag):
""" Represents a single Label media tag.
Attributes:
TAG (str): 'Label'
FILTER (str): 'label'
"""
TAG = 'Label'
FILTER = 'label'
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.
Attributes:
TAG (str): 'Tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
def _loadData(self, data):
self._data = data
self.id = cast(int, data.attrib.get('id', 0))
self.filter = data.attrib.get('filter')
self.tag = data.attrib.get('tag')
self.title = self.tag
def collection(self):
""" Return the :class:`~plexapi.collection.Collection` object for this collection tag.
"""
key = '%s/collections' % self._librarySectionKey
return self.fetchItem(key, etag='Directory', index=self.id)
@utils.registerPlexObject
@ -781,13 +740,15 @@ class Genre(MediaTag):
@utils.registerPlexObject
class Guid(GuidTag):
""" Represents a single Guid media tag.
class Label(MediaTag):
""" Represents a single Label media tag.
Attributes:
TAG (str): 'Guid'
TAG (str): 'Label'
FILTER (str): 'label'
"""
TAG = "Guid"
TAG = 'Label'
FILTER = 'label'
@utils.registerPlexObject
@ -802,6 +763,42 @@ class Mood(MediaTag):
FILTER = 'mood'
@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
FILTER = 'producer'
@utils.registerPlexObject
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
FILTER = 'role'
@utils.registerPlexObject
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
FILTER = 'similar'
@utils.registerPlexObject
class Style(MediaTag):
""" Represents a single Style media tag.
@ -814,6 +811,53 @@ class Style(MediaTag):
FILTER = 'style'
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.
Attributes:
TAG (str): 'Tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
@utils.registerPlexObject
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
FILTER = 'writer'
class GuidTag(PlexObject):
""" Base class for guid tags used only for Guids, as they contain only a string identifier
Attributes:
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB).
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.id = data.attrib.get('id')
@utils.registerPlexObject
class Guid(GuidTag):
""" Represents a single Guid media tag.
Attributes:
TAG (str): 'Guid'
"""
TAG = 'Guid'
class BaseImage(PlexObject):
""" Base class for all Art, Banner, and Poster objects.
@ -856,54 +900,6 @@ class Poster(BaseImage):
""" Represents a single Poster object. """
@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
FILTER = 'producer'
@utils.registerPlexObject
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
FILTER = 'role'
@utils.registerPlexObject
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
FILTER = 'similar'
@utils.registerPlexObject
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
FILTER = 'writer'
@utils.registerPlexObject
class Chapter(PlexObject):
""" Represents a single Writer media tag.

View file

@ -234,11 +234,11 @@ def movie(movies):
@pytest.fixture()
def collection(movies):
try:
return movies.collections(title="marvel")[0]
return movies.collections(title="Marvel")[0]
except IndexError:
movie = movies.get("Elephants Dream")
movie.addCollection("marvel")
return movies.collections(title="marvel")[0]
movie.addCollection("Marvel")
return movies.collections(title="Marvel")[0]
@pytest.fixture()

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from . import conftest as utils
from . import test_mixins
from . import test_media, test_mixins
def test_audio_Artist_attr(artist):
@ -87,6 +87,16 @@ def test_audio_Artist_mixins_tags(artist):
test_mixins.edit_style(artist)
def test_audio_Artist_media_tags(artist):
artist.reload()
test_media.tag_collection(artist)
test_media.tag_country(artist)
test_media.tag_genre(artist)
test_media.tag_mood(artist)
test_media.tag_similar(artist)
test_media.tag_style(artist)
def test_audio_Album_attrs(album):
assert utils.is_datetime(album.addedAt)
if album.art:
@ -165,6 +175,15 @@ def test_audio_Album_mixins_tags(album):
test_mixins.edit_style(album)
def test_audio_Album_media_tags(album):
album.reload()
test_media.tag_collection(album)
test_media.tag_genre(album)
test_media.tag_label(album)
test_media.tag_mood(album)
test_media.tag_style(album)
def test_audio_Track_attrs(album):
track = album.get("As Colourful As Ever").reload()
assert utils.is_datetime(track.addedAt)
@ -294,6 +313,12 @@ def test_audio_Track_mixins_tags(track):
test_mixins.edit_mood(track)
def test_audio_Track_media_tags(track):
track.reload()
test_media.tag_collection(track)
test_media.tag_mood(track)
def test_audio_Audio_section(artist, album, track):
assert artist.section()
assert album.section()

View file

@ -33,7 +33,7 @@ def test_Collection_attrs(collection):
assert collection.summary == ""
assert collection.thumb.startswith("/library/collections/%s/composite" % collection.ratingKey)
assert collection.thumbBlurHash is None
assert collection.title == "marvel"
assert collection.title == "Marvel"
assert collection.titleSort == collection.title
assert collection.type == "collection"
assert utils.is_datetime(collection.updatedAt)

54
tests/test_media.py Normal file
View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
def _test_media_tag(obj, attr):
tags = getattr(obj, attr)
if tags:
assert obj in tags[0].items()
def tag_collection(obj):
_test_media_tag(obj, "collections")
def tag_country(obj):
_test_media_tag(obj, "countries")
def tag_director(obj):
_test_media_tag(obj, "directors")
def tag_genre(obj):
_test_media_tag(obj, "genres")
def tag_label(obj):
_test_media_tag(obj, "labels")
def tag_mood(obj):
_test_media_tag(obj, "moods")
def tag_producer(obj):
_test_media_tag(obj, "producers")
def tag_role(obj):
_test_media_tag(obj, "roles")
def tag_similar(obj):
_test_media_tag(obj, "similar")
def tag_style(obj):
_test_media_tag(obj, "styles")
def tag_tag(obj):
_test_media_tag(obj, "tags")
def tag_writer(obj):
_test_media_tag(obj, "writers")

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from . import test_mixins
from . import test_media, test_mixins
def test_photo_Photoalbum(photoalbum):
@ -20,3 +20,8 @@ def test_photo_Photoalbum_mixins_images(photoalbum):
def test_photo_Photo_mixins_tags(photo):
test_mixins.edit_tag(photo)
def test_photo_Photo_media_tags(photo):
photo.reload()
test_media.tag_tag(photo)

View file

@ -135,6 +135,7 @@ def test_server_search(plex, movie):
assert hub_tag.tagType == 1
assert hub_tag.tagValue is None
assert hub_tag.thumb is None
assert movie in hub_tag.items()
# Test director search
director = movie.directors[0]
assert plex.search(director.tag, mediatype="director")

View file

@ -8,7 +8,7 @@ import pytest
from plexapi.exceptions import BadRequest, NotFound
from . import conftest as utils
from . import test_mixins
from . import test_media, test_mixins
def test_video_Movie(movies, movie):
@ -59,6 +59,30 @@ def test_video_Movie_mixins_tags(movie):
test_mixins.edit_writer(movie)
def test_video_Movie_media_tags(movie):
movie.reload()
test_media.tag_collection(movie)
test_media.tag_country(movie)
test_media.tag_director(movie)
test_media.tag_genre(movie)
test_media.tag_label(movie)
test_media.tag_producer(movie)
test_media.tag_role(movie)
test_media.tag_similar(movie)
test_media.tag_writer(movie)
def test_video_Movie_media_tags_Exception(movie):
with pytest.raises(BadRequest):
movie.genres[0].items()
def test_video_Movie_media_tags_collection(movie, collection):
movie.reload()
collection_tag = next(c for c in movie.collections if c.tag == "Marvel")
assert collection == collection_tag.collection()
def test_video_Movie_getStreamURL(movie, account):
key = movie.ratingKey
assert movie.getStreamURL() == (
@ -741,6 +765,15 @@ def test_video_Show_mixins_tags(show):
test_mixins.edit_label(show)
def test_video_Show_media_tags(show):
show.reload()
test_media.tag_collection(show)
test_media.tag_genre(show)
test_media.tag_label(show)
test_media.tag_role(show)
test_media.tag_similar(show)
def test_video_Episode(show):
episode = show.episode("Winter Is Coming")
assert episode == show.episode(season=1, episode=1)
@ -900,6 +933,13 @@ def test_video_Episode_mixins_tags(episode):
test_mixins.edit_writer(episode)
def test_video_Episode_media_tags(episode):
episode.reload()
test_media.tag_collection(episode)
test_media.tag_director(episode)
test_media.tag_writer(episode)
def test_video_Season(show):
seasons = show.seasons()
assert len(seasons) == 2
@ -998,6 +1038,12 @@ def test_video_Season_mixins_tags(show):
test_mixins.edit_collection(season)
def test_video_Season_media_tags(show):
season = show.season(season=1)
season.reload()
test_media.tag_collection(season)
def test_that_reload_return_the_same_object(plex):
# we want to check this that all the urls are correct
movie_library_search = plex.library.section("Movies").search("Elephants Dream")[0]