Merge branch 'master' into andy/add-python-versions

This commit is contained in:
Steffen Fredriksen 2020-08-02 16:12:17 +02:00 committed by GitHub
commit 74dff2791d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 18 deletions

View file

@ -53,7 +53,7 @@ the top left above your available libraries.
plex = account.resource('<SERVERNAME>').connect() # returns a PlexServer instance
If you want to avoid logging into MyPlex and you already know your auth token
string, you can use the PlexServer object directly as above, but passing in
string, you can use the PlexServer object directly as above, by passing in
the baseurl and auth token directly.
.. code-block:: python

View file

@ -11,6 +11,7 @@ class Audio(PlexPartialObject):
Attributes:
addedAt (datetime): Datetime this item was added to the library.
fields (list): List of :class:`~plexapi.media.Field`.
index (sting): Index Number (often the track number).
key (str): API URL (/library/metadata/<ratingkey>).
lastViewedAt (datetime): Datetime item was last accessed.
@ -33,6 +34,7 @@ class Audio(PlexPartialObject):
self._data = data
self.listType = 'audio'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.fields = self.findItems(data, etag='Field')
self.index = data.attrib.get('index')
self.key = data.attrib.get('key')
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))

View file

@ -69,7 +69,7 @@ class PlexClient(PlexObject):
self._proxyThroughServer = False
self._commandId = 0
self._last_call = 0
if not any([data, initpath, baseurl, token]):
if not any([data is not None, initpath, baseurl, token]):
self._baseurl = CONFIG.get('auth.client_baseurl', 'http://localhost:32433')
self._token = logfilter.add_secret(CONFIG.get('auth.client_token'))
if connect and self._baseurl:

View file

@ -2,7 +2,7 @@
from urllib.parse import quote, quote_plus, unquote, urlencode
from plexapi import X_PLEX_CONTAINER_SIZE, log, utils
from plexapi.base import PlexObject
from plexapi.base import PlexObject, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound
from plexapi.media import MediaTag
from plexapi.settings import Setting
@ -657,6 +657,11 @@ class LibrarySection(PlexObject):
raise BadRequest('Unknown sort dir: %s' % sdir)
return '%s:%s' % (lookup[scol], sdir)
def _locations(self):
""" Returns a list of :class:`~plexapi.library.Location` objects
"""
return self.findItems(self._data, etag='Location')
def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, sort=None, libtype=None,
**kwargs):
""" Add current library section as sync item for specified device.
@ -1054,6 +1059,23 @@ class FilterChoice(PlexObject):
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
@utils.registerPlexObject
class Location(PlexObject):
""" Represents a single library Location.
Attributes:
TAG (str): 'Location'
id (int): Location path ID.
path (str): Path used for library..
"""
TAG = 'Location'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.id = utils.cast(int, data.attrib.get('id'))
self.path = data.attrib.get('path')
@utils.registerPlexObject
class Hub(PlexObject):
@ -1084,7 +1106,38 @@ class Hub(PlexObject):
@utils.registerPlexObject
class Collections(PlexObject):
class Collections(PlexPartialObject):
""" Represents a single Collection.
Attributes:
TAG (str): 'Directory'
TYPE (str): 'collection'
ratingKey (int): Unique key identifying this item.
addedAt (datetime): Datetime this item was added to the library.
childCount (int): Count of child object(s)
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): List of :class:`~plexapi.media.Field`.
guid (str): Plex GUID (collection://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
index (int): Unknown
key (str): API URL (/library/metadata/<ratingkey>).
labels (List<:class:`~plexapi.media.Label`>): List of field objects.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
librarySectionKey (str): API URL (/library/sections/<sectionkey>).
librarySectionTitle (str): Section Title
maxYear (int): Year
minYear (int): Year
subtype (str): Media type
summary (str): Summary of the collection
thumb (str): URL to thumbnail image.
title (str): Collection Title
titleSort (str): Title to use when sorting (defaults to title).
type (str): Hardcoded 'collection'
updatedAt (datatime): Datetime this item was updated.
"""
TAG = 'Directory'
TYPE = 'collection'
@ -1093,20 +1146,29 @@ class Collections(PlexObject):
def _loadData(self, data):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self._details_key = "/library/metadata/%s%s" % (self.ratingKey, self._include)
self.key = data.attrib.get('key')
self.type = data.attrib.get('type')
self.title = data.attrib.get('title')
self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.index = utils.cast(int, data.attrib.get('index'))
self.thumb = data.attrib.get('thumb')
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
self.art = data.attrib.get('art')
self.childCount = utils.cast(int, data.attrib.get('childCount'))
self.minYear = utils.cast(int, data.attrib.get('minYear'))
self.maxYear = utils.cast(int, data.attrib.get('maxYear'))
self.collectionMode = data.attrib.get('collectionMode')
self.collectionSort = data.attrib.get('collectionSort')
self.contentRating = data.attrib.get('contentRating')
self.fields = self.findItems(data, etag='Field')
self.guid = data.attrib.get('guid')
self.index = utils.cast(int, data.attrib.get('index'))
self.key = data.attrib.get('key')
self.labels = self.findItems(data, etag='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.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort')
self.type = data.attrib.get('type')
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
@property
def children(self):

View file

@ -139,7 +139,7 @@ class MyPlexAccount(PlexObject):
roles = data.find('roles')
self.roles = []
if roles:
if roles is not None:
for role in roles.iter('role'):
self.roles.append(role.attrib.get('id'))

View file

@ -16,6 +16,7 @@ class Photoalbum(PlexPartialObject):
addedAt (datetime): Datetime this item was added to the library.
art (str): Photo art (/library/metadata/<ratingkey>/art/<artid>)
composite (str): Unknown
fields (list): List of :class:`~plexapi.media.Field`.
guid (str): Unknown (unique ID)
index (sting): Index number of this album.
key (str): API URL (/library/metadata/<ratingkey>).
@ -37,6 +38,7 @@ class Photoalbum(PlexPartialObject):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.composite = data.attrib.get('composite')
self.fields = self.findItems(data, etag='Field')
self.guid = data.attrib.get('guid')
self.index = utils.cast(int, data.attrib.get('index'))
self.key = data.attrib.get('key')
@ -81,6 +83,7 @@ class Photo(PlexPartialObject):
TAG (str): 'Photo'
TYPE (str): 'photo'
addedAt (datetime): Datetime this item was added to the library.
fields (list): List of :class:`~plexapi.media.Field`.
index (sting): Index number of this photo.
key (str): API URL (/library/metadata/<ratingkey>).
listType (str): Hardcoded as 'photo' (useful for search filters).
@ -104,6 +107,7 @@ class Photo(PlexPartialObject):
""" Load attribute values from Plex XML response. """
self.listType = 'photo'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.fields = self.findItems(data, etag='Field')
self.index = utils.cast(int, data.attrib.get('index'))
self.key = data.attrib.get('key')
self.originallyAvailableAt = utils.toDatetime(

View file

@ -110,7 +110,7 @@ def lowerFirst(s):
def rget(obj, attrstr, default=None, delim='.'): # pragma: no cover
""" Returns the value at the specified attrstr location within a nexted tree of
dicts, lists, tuples, functions, classes, etc. The lookup is done recursivley
dicts, lists, tuples, functions, classes, etc. The lookup is done recursively
for each key in attrstr (split by by the delimiter) This function is heavily
influenced by the lookups used in Django templates.

View file

@ -14,6 +14,7 @@ class Video(PlexPartialObject):
Attributes:
addedAt (datetime): Datetime this item was added to the library.
fields (list): List of :class:`~plexapi.media.Field`.
key (str): API URL (/library/metadata/<ratingkey>).
lastViewedAt (datetime): Datetime item was last accessed.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
@ -33,6 +34,8 @@ class Video(PlexPartialObject):
self._data = data
self.listType = 'video'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.fields = self.findItems(data, etag='Field')
self.key = data.attrib.get('key', '')
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
self.librarySectionID = data.attrib.get('librarySectionID')
@ -126,8 +129,9 @@ class Video(PlexPartialObject):
policyValue="", policyUnwatched=0, videoQuality=None, deviceProfile=None):
""" Optimize item
locationID (int): -1 in folder with orginal items
2 library path
locationID (int): -1 in folder with original items
2 library path id
library path id is found in library.locations[i].id
target (str): custom quality name.
if none provided use "Custom: {deviceProfile}"
@ -157,6 +161,13 @@ class Video(PlexPartialObject):
if targetTagID not in tagIDs and (deviceProfile is None or videoQuality is None):
raise BadRequest('Unexpected or missing quality profile.')
libraryLocationIDs = [location.id for location in self.section()._locations()]
libraryLocationIDs.append(-1)
if locationID not in libraryLocationIDs:
raise BadRequest('Unexpected library path ID. %s not in %s' %
(locationID, libraryLocationIDs))
if isinstance(targetTagID, str):
tagIndex = tagKeys.index(targetTagID)
targetTagID = tagValues[tagIndex]

View file

@ -41,5 +41,6 @@ setup(
classifiers=[
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: BSD License',
]
)

View file

@ -238,6 +238,18 @@ def test_library_Colletion_sortRelease(collection):
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 == True
collection.edit(**{'titleSort.value': collectionTitleSort, 'titleSort.locked': 0})
def test_search_with_weird_a(plex):
ep_title = "Coup de Grâce"
result_root = plex.search(ep_title)

View file

@ -880,6 +880,27 @@ def test_video_exists_accessible(movie, episode):
assert episode.media[0].parts[0].accessible is True
def test_video_edits_locked(movie, episode):
edits = {'titleSort.value':'New Title Sort', 'titleSort.locked': 1}
movieTitleSort = movie.titleSort
movie.edit(**edits)
movie.reload()
for field in movie.fields:
if field.name == 'titleSort':
assert movie.titleSort == 'New Title Sort'
assert field.locked == True
movie.edit(**{'titleSort.value': movieTitleSort, 'titleSort.locked': 0})
episodeTitleSort = episode.titleSort
episode.edit(**edits)
episode.reload()
for field in episode.fields:
if field.name == 'titleSort':
assert episode.titleSort == 'New Title Sort'
assert field.locked == True
episode.edit(**{'titleSort.value': episodeTitleSort, 'titleSort.locked': 0})
@pytest.mark.skip(
reason="broken? assert len(plex.conversions()) == 1 may fail on some builds"
)