mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Merge branch 'master' into andy/add-python-versions
This commit is contained in:
commit
74dff2791d
11 changed files with 131 additions and 18 deletions
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
1
setup.py
1
setup.py
|
@ -41,5 +41,6 @@ setup(
|
|||
classifiers=[
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue