Merge pull request #667 from JonnyWong16/feature/move_collection

Move collections to a separate module
This commit is contained in:
Steffen Fredriksen 2021-02-24 17:10:07 +01:00 committed by GitHub
commit 34606631ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 288 additions and 251 deletions

165
plexapi/collection.py Normal file
View file

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
from plexapi import media, utils
from plexapi.base import PlexPartialObject
from plexapi.exceptions import BadRequest
from plexapi.mixins import ArtMixin, PosterMixin
from plexapi.mixins import LabelMixin
from plexapi.settings import Setting
from plexapi.utils import deprecated
@utils.registerPlexObject
class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin):
""" Represents a single Collection.
Attributes:
TAG (str): 'Directory'
TYPE (str): 'collection'
addedAt (datetime): Datetime the collection was added to the library.
art (str): URL to artwork image (/library/metadata/<ratingKey>/art/<artid>).
artBlurHash (str): BlurHash string for artwork image.
childCount (int): Number of items in the collection.
collectionMode (str): How the items in the collection are displayed.
collectionSort (str): How to sort the items in the collection.
contentRating (str) Content rating (PG-13; NR; TV-G).
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
guid (str): Plex GUID for the collection (collection://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
index (int): Plex index number for the collection.
key (str): API URL (/library/metadata/<ratingkey>).
labels (List<:class:`~plexapi.media.Label`>): List of label objects.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key.
librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title.
maxYear (int): Maximum year for the items in the collection.
minYear (int): Minimum year for the items in the collection.
ratingKey (int): Unique key identifying the collection.
subtype (str): Media type of the items in the collection (movie, show, artist, or album).
summary (str): Summary of the collection.
thumb (str): URL to thumbnail image (/library/metadata/<ratingKey>/thumb/<thumbid>).
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Name of the collection.
titleSort (str): Title to use when sorting (defaults to title).
type (str): 'collection'
updatedAt (datatime): Datetime the collection was updated.
"""
TAG = 'Directory'
TYPE = 'collection'
def _loadData(self, data):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.childCount = utils.cast(int, data.attrib.get('childCount'))
self.collectionMode = utils.cast(int, data.attrib.get('collectionMode', '-1'))
self.collectionSort = utils.cast(int, data.attrib.get('collectionSort', '0'))
self.contentRating = data.attrib.get('contentRating')
self.fields = self.findItems(data, media.Field)
self.guid = data.attrib.get('guid')
self.index = utils.cast(int, data.attrib.get('index'))
self.key = data.attrib.get('key', '').replace('/children', '') # FIX_BUG_50
self.labels = self.findItems(data, media.Label)
self.librarySectionID = data.attrib.get('librarySectionID')
self.librarySectionKey = data.attrib.get('librarySectionKey')
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.maxYear = utils.cast(int, data.attrib.get('maxYear'))
self.minYear = utils.cast(int, data.attrib.get('minYear'))
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title)
self.type = data.attrib.get('type')
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
@property
@deprecated('use "items" instead')
def children(self):
return self.items()
@property
def thumbUrl(self):
""" Return the thumbnail url for the collection."""
return self._server.url(self.thumb, includeToken=True) if self.thumb else None
@property
def artUrl(self):
""" Return the art url for the collection."""
return self._server.url(self.art, includeToken=True) if self.art else None
def item(self, title):
""" Returns the item in the collection that matches the specified title.
Parameters:
title (str): Title of the item to return.
"""
key = '/library/metadata/%s/children' % self.ratingKey
return self.fetchItem(key, title__iexact=title)
def items(self):
""" Returns a list of all items in the collection. """
key = '/library/metadata/%s/children' % self.ratingKey
return self.fetchItems(key)
def get(self, title):
""" Alias to :func:`~plexapi.library.Collection.item`. """
return self.item(title)
def __len__(self):
return self.childCount
def _preferences(self):
""" Returns a list of :class:`~plexapi.settings.Preferences` objects. """
items = []
data = self._server.query(self._details_key)
for item in data.iter('Setting'):
items.append(Setting(data=item, server=self._server))
return items
def modeUpdate(self, mode=None):
""" Update Collection Mode
Parameters:
mode: default (Library default)
hide (Hide Collection)
hideItems (Hide Items in this Collection)
showItems (Show this Collection and its Items)
Example:
collection = 'plexapi.library.Collections'
collection.updateMode(mode="hide")
"""
mode_dict = {'default': -1,
'hide': 0,
'hideItems': 1,
'showItems': 2}
key = mode_dict.get(mode)
if key is None:
raise BadRequest('Unknown collection mode : %s. Options %s' % (mode, list(mode_dict)))
part = '/library/metadata/%s/prefs?collectionMode=%s' % (self.ratingKey, key)
return self._server.query(part, method=self._server._session.put)
def sortUpdate(self, sort=None):
""" Update Collection Sorting
Parameters:
sort: realease (Order Collection by realease dates)
alpha (Order Collection alphabetically)
custom (Custom collection order)
Example:
colleciton = 'plexapi.library.Collections'
collection.updateSort(mode="alpha")
"""
sort_dict = {'release': 0,
'alpha': 1,
'custom': 2}
key = sort_dict.get(sort)
if key is None:
raise BadRequest('Unknown sort dir: %s. Options: %s' % (sort, list(sort_dict)))
part = '/library/metadata/%s/prefs?collectionSort=%s' % (self.ratingKey, key)
return self._server.query(part, method=self._server._session.put)

View file

@ -2,10 +2,8 @@
from urllib.parse import quote, quote_plus, unquote, urlencode
from plexapi import X_PLEX_CONTAINER_SIZE, log, media, utils
from plexapi.base import OPERATORS, PlexObject, PlexPartialObject
from plexapi.base import OPERATORS, PlexObject
from plexapi.exceptions import BadRequest, NotFound
from plexapi.mixins import ArtMixin, PosterMixin
from plexapi.mixins import LabelMixin
from plexapi.settings import Setting
from plexapi.utils import deprecated
@ -1527,168 +1525,6 @@ class FirstCharacter(PlexObject):
self.title = data.attrib.get('title')
@utils.registerPlexObject
class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin):
""" Represents a single Collection.
Attributes:
TAG (str): 'Directory'
TYPE (str): 'collection'
addedAt (datetime): Datetime the collection was added to the library.
art (str): URL to artwork image (/library/metadata/<ratingKey>/art/<artid>).
artBlurHash (str): BlurHash string for artwork image.
childCount (int): Number of items in the collection.
collectionMode (str): How the items in the collection are displayed.
collectionSort (str): How to sort the items in the collection.
contentRating (str) Content rating (PG-13; NR; TV-G).
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
guid (str): Plex GUID for the collection (collection://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
index (int): Plex index number for the collection.
key (str): API URL (/library/metadata/<ratingkey>).
labels (List<:class:`~plexapi.media.Label`>): List of label objects.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
librarySectionKey (str): :class:`~plexapi.library.LibrarySection` key.
librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title.
maxYear (int): Maximum year for the items in the collection.
minYear (int): Minimum year for the items in the collection.
ratingKey (int): Unique key identifying the collection.
subtype (str): Media type of the items in the collection (movie, show, artist, or album).
summary (str): Summary of the collection.
thumb (str): URL to thumbnail image (/library/metadata/<ratingKey>/thumb/<thumbid>).
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Name of the collection.
titleSort (str): Title to use when sorting (defaults to title).
type (str): 'collection'
updatedAt (datatime): Datetime the collection was updated.
"""
TAG = 'Directory'
TYPE = 'collection'
def _loadData(self, data):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.childCount = utils.cast(int, data.attrib.get('childCount'))
self.collectionMode = data.attrib.get('collectionMode')
self.collectionSort = data.attrib.get('collectionSort')
self.contentRating = data.attrib.get('contentRating')
self.fields = self.findItems(data, media.Field)
self.guid = data.attrib.get('guid')
self.index = utils.cast(int, data.attrib.get('index'))
self.key = data.attrib.get('key', '').replace('/children', '') # FIX_BUG_50
self.labels = self.findItems(data, media.Label)
self.librarySectionID = data.attrib.get('librarySectionID')
self.librarySectionKey = data.attrib.get('librarySectionKey')
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.maxYear = utils.cast(int, data.attrib.get('maxYear'))
self.minYear = utils.cast(int, data.attrib.get('minYear'))
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title)
self.type = data.attrib.get('type')
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
@property
@deprecated('use "items" instead')
def children(self):
return self.fetchItems(self.key)
@property
def thumbUrl(self):
""" Return the thumbnail url for the collection."""
return self._server.url(self.thumb, includeToken=True) if self.thumb else None
@property
def artUrl(self):
""" Return the art url for the collection."""
return self._server.url(self.art, includeToken=True) if self.art else None
def item(self, title):
""" Returns the item in the collection that matches the specified title.
Parameters:
title (str): Title of the item to return.
"""
key = '/library/metadata/%s/children' % self.ratingKey
return self.fetchItem(key, title__iexact=title)
def items(self):
""" Returns a list of all items in the collection. """
key = '/library/metadata/%s/children' % self.ratingKey
return self.fetchItems(key)
def get(self, title):
""" Alias to :func:`~plexapi.library.Collection.item`. """
return self.item(title)
def __len__(self):
return self.childCount
def _preferences(self):
""" Returns a list of :class:`~plexapi.settings.Preferences` objects. """
items = []
data = self._server.query(self._details_key)
for item in data.iter('Setting'):
items.append(Setting(data=item, server=self._server))
return items
def delete(self):
part = '/library/metadata/%s' % self.ratingKey
return self._server.query(part, method=self._server._session.delete)
def modeUpdate(self, mode=None):
""" Update Collection Mode
Parameters:
mode: default (Library default)
hide (Hide Collection)
hideItems (Hide Items in this Collection)
showItems (Show this Collection and its Items)
Example:
collection = 'plexapi.library.Collections'
collection.updateMode(mode="hide")
"""
mode_dict = {'default': '-1',
'hide': '0',
'hideItems': '1',
'showItems': '2'}
key = mode_dict.get(mode)
if key is None:
raise BadRequest('Unknown collection mode : %s. Options %s' % (mode, list(mode_dict)))
part = '/library/metadata/%s/prefs?collectionMode=%s' % (self.ratingKey, key)
return self._server.query(part, method=self._server._session.put)
def sortUpdate(self, sort=None):
""" Update Collection Sorting
Parameters:
sort: realease (Order Collection by realease dates)
alpha (Order Collection Alphabetically)
Example:
colleciton = 'plexapi.library.Collections'
collection.updateSort(mode="alpha")
"""
sort_dict = {'release': '0',
'alpha': '1'}
key = sort_dict.get(sort)
if key is None:
raise BadRequest('Unknown sort dir: %s. Options: %s' % (sort, list(sort_dict)))
part = '/library/metadata/%s/prefs?collectionSort=%s' % (self.ratingKey, key)
return self._server.query(part, method=self._server._session.put)
# def edit(self, **kwargs):
# TODO
@utils.registerPlexObject
class Path(PlexObject):
""" Represents a single directory Path.

View file

@ -19,7 +19,8 @@ from plexapi.utils import cast
from requests.status_codes import _codes as codes
# Need these imports to populate utils.PLEXOBJECTS
from plexapi import audio as _audio # noqa: F401; noqa: F401
from plexapi import audio as _audio # noqa: F401
from plexapi import collection as _collection # noqa: F401
from plexapi import media as _media # noqa: F401
from plexapi import photo as _photo # noqa: F401
from plexapi import playlist as _playlist # noqa: F401

View file

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

117
tests/test_collection.py Normal file
View file

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
import pytest
from plexapi.exceptions import BadRequest
from . import conftest as utils
from . import test_mixins
def test_Collection_attrs(collection):
assert utils.is_datetime(collection.addedAt)
assert collection.art is None
assert collection.artBlurHash is None
assert collection.childCount == 1
assert collection.collectionMode == -1
assert collection.collectionSort == 0
assert collection.contentRating
assert not collection.fields
assert collection.guid.startswith("collection://")
assert utils.is_int(collection.index)
assert collection.key.startswith("/library/collections/")
assert not collection.labels
assert utils.is_int(collection.librarySectionID)
assert collection.librarySectionKey == "/library/sections/%s" % collection.librarySectionID
assert collection.librarySectionTitle == "Movies"
assert utils.is_int(collection.maxYear)
assert utils.is_int(collection.minYear)
assert utils.is_int(collection.ratingKey)
assert collection.subtype == "movie"
assert collection.summary == ""
assert collection.thumb.startswith("/library/collections/%s/composite" % collection.ratingKey)
assert collection.thumbBlurHash is None
assert collection.title == "marvel"
assert collection.titleSort == collection.title
assert collection.type == "collection"
assert utils.is_datetime(collection.updatedAt)
def test_Collection_modeUpdate(collection):
mode_dict = {"default": -1, "hide": 0, "hideItems": 1, "showItems": 2}
for key, value in mode_dict.items():
collection.modeUpdate(mode=key)
collection.reload()
assert collection.collectionMode == value
with pytest.raises(BadRequest):
collection.modeUpdate(mode="bad-mode")
collection.modeUpdate("default")
def test_Collection_sortUpdate(collection):
sort_dict = {"release": 0, "alpha": 1, "custom": 2}
for key, value in sort_dict.items():
collection.sortUpdate(sort=key)
collection.reload()
assert collection.collectionSort == value
with pytest.raises(BadRequest):
collection.sortUpdate(sort="bad-sort")
collection.sortUpdate("release")
def test_Collection_edit(collection):
edits = {"titleSort.value": "New Title Sort", "titleSort.locked": 1}
collectionTitleSort = collection.titleSort
collection.edit(**edits)
collection.reload()
for field in collection.fields:
if field.name == "titleSort":
assert collection.titleSort == "New Title Sort"
assert field.locked is True
collection.edit(**{"titleSort.value": collectionTitleSort, "titleSort.locked": 0})
def test_Collection_delete(movies):
delete_collection = "delete_collection"
movie = movies.get("Sintel")
movie.addCollection(delete_collection)
collections = movies.collections(title=delete_collection)
assert len(collections) == 1
collections[0].delete()
collections = movies.collections(title=delete_collection)
assert len(collections) == 0
def test_Collection_item(collection):
item1 = collection.item("Elephants Dream")
assert item1.title == "Elephants Dream"
item2 = collection.get("Elephants Dream")
assert item2.title == "Elephants Dream"
assert item1 == item2
def test_Collection_items(collection):
items = collection.items()
assert len(items) == 1
def test_Collection_thumbUrl(collection):
assert utils.SERVER_BASEURL in collection.thumbUrl
assert "/library/collections/" in collection.thumbUrl
assert "/composite/" in collection.thumbUrl
def test_Collection_artUrl(collection):
assert collection.artUrl is None # Collections don't have default art
def test_Collection_posters(collection):
posters = collection.posters()
assert posters
def test_Collection_art(collection):
arts = collection.arts()
assert not arts # Collection has no default art
def test_Collection_mixins_tags(collection):
test_mixins.edit_label(collection)

View file

@ -4,7 +4,6 @@ import pytest
from plexapi.exceptions import NotFound
from . import conftest as utils
from . import test_mixins
def test_library_Library_section(plex):
@ -259,85 +258,6 @@ def test_library_editAdvanced_default(movies):
assert str(setting.value) == str(setting.default)
def test_library_Collection_modeUpdate(collection):
mode_dict = {"default": "-1", "hide": "0", "hideItems": "1", "showItems": "2"}
for key, value in mode_dict.items():
collection.modeUpdate(key)
collection.reload()
assert collection.collectionMode == value
def test_library_Colletion_sortAlpha(collection):
collection.sortUpdate(sort="alpha")
collection.reload()
assert collection.collectionSort == "1"
def test_library_Colletion_sortRelease(collection):
collection.sortUpdate(sort="release")
collection.reload()
assert collection.collectionSort == "0"
def test_library_Colletion_edit(collection):
edits = {'titleSort.value': 'New Title Sort', 'titleSort.locked': 1}
collectionTitleSort = collection.titleSort
collection.edit(**edits)
collection.reload()
for field in collection.fields:
if field.name == 'titleSort':
assert collection.titleSort == 'New Title Sort'
assert field.locked is True
collection.edit(**{'titleSort.value': collectionTitleSort, 'titleSort.locked': 0})
def test_library_Collection_delete(movies, movie):
delete_collection = 'delete_collection'
movie.addCollection(delete_collection)
collections = movies.collections(title=delete_collection)
assert len(collections) == 1
collections[0].delete()
collections = movies.collections(title=delete_collection)
assert len(collections) == 0
def test_library_Collection_item(collection):
item1 = collection.item("Elephants Dream")
assert item1.title == "Elephants Dream"
item2 = collection.get("Elephants Dream")
assert item2.title == "Elephants Dream"
assert item1 == item2
def test_library_Collection_items(collection):
items = collection.items()
assert len(items) == 1
def test_library_Collection_thumbUrl(collection):
assert utils.SERVER_BASEURL in collection.thumbUrl
assert "/library/collections/" in collection.thumbUrl
assert "/composite/" in collection.thumbUrl
def test_library_Collection_artUrl(collection):
assert collection.artUrl is None # Collections don't have default art
def test_library_Collection_posters(collection):
posters = collection.posters()
assert posters
def test_library_Collection_art(collection):
arts = collection.arts()
assert not arts # Collection has no default art
def test_library_Collection_mixins_tags(collection):
test_mixins.edit_label(collection)
def test_search_with_weird_a(plex):
ep_title = "Coup de Grâce"
result_root = plex.search(ep_title)