Skip to content

Commit 8c2b3ce

Browse files
authored
Merge pull request #741 from JonnyWong16/feature/tag_items
Add ability to retrieve a list of items and collection object from media tags
2 parents 893a992 + 1139f5f commit 8c2b3ce

File tree

9 files changed

+258
-125
lines changed

9 files changed

+258
-125
lines changed

plexapi/library.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,12 @@ def _loadData(self, data):
16981698
self.tagValue = utils.cast(int, data.attrib.get('tagValue'))
16991699
self.thumb = data.attrib.get('thumb')
17001700

1701+
def items(self, *args, **kwargs):
1702+
""" Return the list of items within this tag. """
1703+
if not self.key:
1704+
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
1705+
return self.fetchItems(self.key)
1706+
17011707

17021708
@utils.registerPlexObject
17031709
class Tag(HubMediaTag):

plexapi/media.py

Lines changed: 114 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -648,57 +648,41 @@ class MediaTag(PlexObject):
648648
the construct used for things such as Country, Director, Genre, etc.
649649
650650
Attributes:
651-
server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
651+
filter (str): The library filter for the tag.
652652
id (id): Tag ID (This seems meaningless except to use it as a unique id).
653-
role (str): Unknown
653+
key (str): API URL (/library/section/<librarySectionID>/all?<filter>).
654+
role (str): The name of the character role for :class:`~plexapi.media.Role` only.
654655
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
655656
person for Directors and Roles (ex: Animation, Stephen Graham, etc).
656-
<Hub_Search_Attributes>: Attributes only applicable in search results from
657-
PlexServer :func:`~plexapi.server.PlexServer.search`. They provide details of which
658-
library section the tag was found as well as the url to dig deeper into the results.
659-
660-
* key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081).
661-
* librarySectionID (int): Section ID this tag was generated from.
662-
* librarySectionTitle (str): Library section title this tag was found.
663-
* librarySectionType (str): Media type of the library section this tag was found.
664-
* tagType (int): Tag type ID.
665-
* thumb (str): URL to thumbnail image.
657+
thumb (str): URL to thumbnail image for :class:`~plexapi.media.Role` only.
666658
"""
667659

668660
def _loadData(self, data):
669661
""" Load attribute values from Plex XML response. """
670662
self._data = data
663+
self.filter = data.attrib.get('filter')
671664
self.id = cast(int, data.attrib.get('id'))
665+
self.key = data.attrib.get('key')
672666
self.role = data.attrib.get('role')
673667
self.tag = data.attrib.get('tag')
674-
# additional attributes only from hub search
675-
self.key = data.attrib.get('key')
676-
self.librarySectionID = cast(int, data.attrib.get('librarySectionID'))
677-
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
678-
self.librarySectionType = data.attrib.get('librarySectionType')
679-
self.tagType = cast(int, data.attrib.get('tagType'))
680668
self.thumb = data.attrib.get('thumb')
681669

682-
def items(self, *args, **kwargs):
683-
""" Return the list of items within this tag. This function is only applicable
684-
in search results from PlexServer :func:`~plexapi.server.PlexServer.search`.
685-
"""
686-
if not self.key:
687-
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
688-
return self.fetchItems(self.key)
689-
690-
691-
class GuidTag(PlexObject):
692-
""" Base class for guid tags used only for Guids, as they contain only a string identifier
670+
parent = self._parent()
671+
self._librarySectionID = utils.cast(int, parent._data.attrib.get('librarySectionID'))
672+
self._librarySectionKey = parent._data.attrib.get('librarySectionKey')
673+
self._librarySectionTitle = parent._data.attrib.get('librarySectionTitle')
674+
self._parentType = parent.TYPE
693675

694-
Attributes:
695-
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB).
696-
"""
676+
if self._librarySectionKey and self.filter:
677+
self.key = '%s/all?%s&type=%s' % (
678+
self._librarySectionKey, self.filter, utils.searchType(self._parentType))
697679

698-
def _loadData(self, data):
699-
""" Load attribute values from Plex XML response. """
700-
self._data = data
701-
self.id = data.attrib.get('id')
680+
def items(self):
681+
""" Return the list of items within this tag. """
682+
if not self.key:
683+
raise BadRequest('Key is not defined for this tag: %s. '
684+
'Reload the parent object.' % self.tag)
685+
return self.fetchItems(self.key)
702686

703687

704688
@utils.registerPlexObject
@@ -712,36 +696,11 @@ class Collection(MediaTag):
712696
TAG = 'Collection'
713697
FILTER = 'collection'
714698

715-
716-
@utils.registerPlexObject
717-
class Label(MediaTag):
718-
""" Represents a single Label media tag.
719-
720-
Attributes:
721-
TAG (str): 'Label'
722-
FILTER (str): 'label'
723-
"""
724-
TAG = 'Label'
725-
FILTER = 'label'
726-
727-
728-
@utils.registerPlexObject
729-
class Tag(MediaTag):
730-
""" Represents a single Tag media tag.
731-
732-
Attributes:
733-
TAG (str): 'Tag'
734-
FILTER (str): 'tag'
735-
"""
736-
TAG = 'Tag'
737-
FILTER = 'tag'
738-
739-
def _loadData(self, data):
740-
self._data = data
741-
self.id = cast(int, data.attrib.get('id', 0))
742-
self.filter = data.attrib.get('filter')
743-
self.tag = data.attrib.get('tag')
744-
self.title = self.tag
699+
def collection(self):
700+
""" Return the :class:`~plexapi.collection.Collection` object for this collection tag.
701+
"""
702+
key = '%s/collections' % self._librarySectionKey
703+
return self.fetchItem(key, etag='Directory', index=self.id)
745704

746705

747706
@utils.registerPlexObject
@@ -781,13 +740,15 @@ class Genre(MediaTag):
781740

782741

783742
@utils.registerPlexObject
784-
class Guid(GuidTag):
785-
""" Represents a single Guid media tag.
743+
class Label(MediaTag):
744+
""" Represents a single Label media tag.
786745
787746
Attributes:
788-
TAG (str): 'Guid'
747+
TAG (str): 'Label'
748+
FILTER (str): 'label'
789749
"""
790-
TAG = "Guid"
750+
TAG = 'Label'
751+
FILTER = 'label'
791752

792753

793754
@utils.registerPlexObject
@@ -802,6 +763,42 @@ class Mood(MediaTag):
802763
FILTER = 'mood'
803764

804765

766+
@utils.registerPlexObject
767+
class Producer(MediaTag):
768+
""" Represents a single Producer media tag.
769+
770+
Attributes:
771+
TAG (str): 'Producer'
772+
FILTER (str): 'producer'
773+
"""
774+
TAG = 'Producer'
775+
FILTER = 'producer'
776+
777+
778+
@utils.registerPlexObject
779+
class Role(MediaTag):
780+
""" Represents a single Role (actor/actress) media tag.
781+
782+
Attributes:
783+
TAG (str): 'Role'
784+
FILTER (str): 'role'
785+
"""
786+
TAG = 'Role'
787+
FILTER = 'role'
788+
789+
790+
@utils.registerPlexObject
791+
class Similar(MediaTag):
792+
""" Represents a single Similar media tag.
793+
794+
Attributes:
795+
TAG (str): 'Similar'
796+
FILTER (str): 'similar'
797+
"""
798+
TAG = 'Similar'
799+
FILTER = 'similar'
800+
801+
805802
@utils.registerPlexObject
806803
class Style(MediaTag):
807804
""" Represents a single Style media tag.
@@ -814,6 +811,53 @@ class Style(MediaTag):
814811
FILTER = 'style'
815812

816813

814+
@utils.registerPlexObject
815+
class Tag(MediaTag):
816+
""" Represents a single Tag media tag.
817+
818+
Attributes:
819+
TAG (str): 'Tag'
820+
FILTER (str): 'tag'
821+
"""
822+
TAG = 'Tag'
823+
FILTER = 'tag'
824+
825+
826+
@utils.registerPlexObject
827+
class Writer(MediaTag):
828+
""" Represents a single Writer media tag.
829+
830+
Attributes:
831+
TAG (str): 'Writer'
832+
FILTER (str): 'writer'
833+
"""
834+
TAG = 'Writer'
835+
FILTER = 'writer'
836+
837+
838+
class GuidTag(PlexObject):
839+
""" Base class for guid tags used only for Guids, as they contain only a string identifier
840+
841+
Attributes:
842+
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB).
843+
"""
844+
845+
def _loadData(self, data):
846+
""" Load attribute values from Plex XML response. """
847+
self._data = data
848+
self.id = data.attrib.get('id')
849+
850+
851+
@utils.registerPlexObject
852+
class Guid(GuidTag):
853+
""" Represents a single Guid media tag.
854+
855+
Attributes:
856+
TAG (str): 'Guid'
857+
"""
858+
TAG = 'Guid'
859+
860+
817861
class BaseImage(PlexObject):
818862
""" Base class for all Art, Banner, and Poster objects.
819863
@@ -856,54 +900,6 @@ class Poster(BaseImage):
856900
""" Represents a single Poster object. """
857901

858902

859-
@utils.registerPlexObject
860-
class Producer(MediaTag):
861-
""" Represents a single Producer media tag.
862-
863-
Attributes:
864-
TAG (str): 'Producer'
865-
FILTER (str): 'producer'
866-
"""
867-
TAG = 'Producer'
868-
FILTER = 'producer'
869-
870-
871-
@utils.registerPlexObject
872-
class Role(MediaTag):
873-
""" Represents a single Role (actor/actress) media tag.
874-
875-
Attributes:
876-
TAG (str): 'Role'
877-
FILTER (str): 'role'
878-
"""
879-
TAG = 'Role'
880-
FILTER = 'role'
881-
882-
883-
@utils.registerPlexObject
884-
class Similar(MediaTag):
885-
""" Represents a single Similar media tag.
886-
887-
Attributes:
888-
TAG (str): 'Similar'
889-
FILTER (str): 'similar'
890-
"""
891-
TAG = 'Similar'
892-
FILTER = 'similar'
893-
894-
895-
@utils.registerPlexObject
896-
class Writer(MediaTag):
897-
""" Represents a single Writer media tag.
898-
899-
Attributes:
900-
TAG (str): 'Writer'
901-
FILTER (str): 'writer'
902-
"""
903-
TAG = 'Writer'
904-
FILTER = 'writer'
905-
906-
907903
@utils.registerPlexObject
908904
class Chapter(PlexObject):
909905
""" Represents a single Writer media tag.

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,11 @@ def movie(movies):
234234
@pytest.fixture()
235235
def collection(movies):
236236
try:
237-
return movies.collections(title="marvel")[0]
237+
return movies.collections(title="Marvel")[0]
238238
except IndexError:
239239
movie = movies.get("Elephants Dream")
240-
movie.addCollection("marvel")
241-
return movies.collections(title="marvel")[0]
240+
movie.addCollection("Marvel")
241+
return movies.collections(title="Marvel")[0]
242242

243243

244244
@pytest.fixture()

tests/test_audio.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
from . import conftest as utils
3-
from . import test_mixins
3+
from . import test_media, test_mixins
44

55

66
def test_audio_Artist_attr(artist):
@@ -87,6 +87,16 @@ def test_audio_Artist_mixins_tags(artist):
8787
test_mixins.edit_style(artist)
8888

8989

90+
def test_audio_Artist_media_tags(artist):
91+
artist.reload()
92+
test_media.tag_collection(artist)
93+
test_media.tag_country(artist)
94+
test_media.tag_genre(artist)
95+
test_media.tag_mood(artist)
96+
test_media.tag_similar(artist)
97+
test_media.tag_style(artist)
98+
99+
90100
def test_audio_Album_attrs(album):
91101
assert utils.is_datetime(album.addedAt)
92102
if album.art:
@@ -165,6 +175,15 @@ def test_audio_Album_mixins_tags(album):
165175
test_mixins.edit_style(album)
166176

167177

178+
def test_audio_Album_media_tags(album):
179+
album.reload()
180+
test_media.tag_collection(album)
181+
test_media.tag_genre(album)
182+
test_media.tag_label(album)
183+
test_media.tag_mood(album)
184+
test_media.tag_style(album)
185+
186+
168187
def test_audio_Track_attrs(album):
169188
track = album.get("As Colourful As Ever").reload()
170189
assert utils.is_datetime(track.addedAt)
@@ -294,6 +313,12 @@ def test_audio_Track_mixins_tags(track):
294313
test_mixins.edit_mood(track)
295314

296315

316+
def test_audio_Track_media_tags(track):
317+
track.reload()
318+
test_media.tag_collection(track)
319+
test_media.tag_mood(track)
320+
321+
297322
def test_audio_Audio_section(artist, album, track):
298323
assert artist.section()
299324
assert album.section()

tests/test_collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_Collection_attrs(collection):
3333
assert collection.summary == ""
3434
assert collection.thumb.startswith("/library/collections/%s/composite" % collection.ratingKey)
3535
assert collection.thumbBlurHash is None
36-
assert collection.title == "marvel"
36+
assert collection.title == "Marvel"
3737
assert collection.titleSort == collection.title
3838
assert collection.type == "collection"
3939
assert utils.is_datetime(collection.updatedAt)

0 commit comments

Comments
 (0)