diff --git a/plexapi/alert.py b/plexapi/alert.py index 1a5469abe..bf6e5394f 100644 --- a/plexapi/alert.py +++ b/plexapi/alert.py @@ -54,7 +54,7 @@ def run(self): def stop(self): """ Stop the AlertListener thread. Once the notifier is stopped, it cannot be directly - started again. You must call :func:`plexapi.server.PlexServer.startAlertListener()` + started again. You must call :func:`~plexapi.server.PlexServer.startAlertListener` from a PlexServer instance. """ log.info('Stopping AlertListener.') diff --git a/plexapi/audio.py b/plexapi/audio.py index 8a8d38627..08742884f 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -11,6 +11,8 @@ class Audio(PlexPartialObject): Attributes: addedAt (datetime): Datetime this item was added to the library. + art (str): URL to artwork image. + artBlurHash (str): BlurHash string for artwork image. fields (list): List of :class:`~plexapi.media.Field`. index (sting): Index Number (often the track number). key (str): API URL (/library/metadata/). @@ -20,6 +22,7 @@ class Audio(PlexPartialObject): ratingKey (int): Unique key identifying this item. summary (str): Summary of the artist, track, or album. thumb (str): URL to thumbnail image. + thumbBlurHash (str): BlurHash string for thumbnail image. title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.) titleSort (str): Title to use when sorting (defaults to title). type (str): 'artist', 'album', or 'track'. @@ -34,6 +37,8 @@ def _loadData(self, data): self._data = data self.listType = 'audio' self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) + self.art = data.attrib.get('art') + self.artBlurHash = data.attrib.get('artBlurHash') self.fields = self.findItems(data, etag='Field') self.index = data.attrib.get('index') self.key = data.attrib.get('key') @@ -42,6 +47,7 @@ def _loadData(self, data): self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) 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') @@ -70,20 +76,20 @@ def _defaultSyncTitle(self): def sync(self, bitrate, client=None, clientId=None, limit=None, title=None): """ Add current audio (artist, album or track) as sync item for specified device. - See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. + See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions. Parameters: bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the - module :mod:`plexapi.sync`. - client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see - :func:`plexapi.myplex.MyPlexAccount.sync`. - clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. + module :mod:`~plexapi.sync`. + client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see + :func:`~plexapi.myplex.MyPlexAccount.sync`. + clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`. limit (int): maximum count of items to sync, unlimited if `None`. - title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be + title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be generated from metadata of current media. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. """ from plexapi.sync import SyncItem, Policy, MediaSettings @@ -112,7 +118,6 @@ class Artist(Audio): Attributes: TAG (str): 'Directory' TYPE (str): 'artist' - art (str): Artist artwork (/library/metadata//art/) countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents. genres (list): List of :class:`~plexapi.media.Genre` objects this artist respresents. guid (str): Unknown (unique ID; com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en) @@ -126,7 +131,6 @@ class Artist(Audio): def _loadData(self, data): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) - self.art = data.attrib.get('art') self.guid = data.attrib.get('guid') self.key = self.key.replace('/children', '') # FIX_BUG_50 self.locations = self.listAttrs(data, 'path', etag='Location') @@ -179,7 +183,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): keep_original_name (bool): Set True to keep the original filename as stored in the Plex server. False will create a new filename with the format " - ". - kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will + kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will be returned and the additional arguments passed in will be sent to that function. If kwargs is not specified, the media items will be downloaded and saved to disk. @@ -198,7 +202,6 @@ class Album(Audio): Attributes: TAG (str): 'Directory' TYPE (str): 'album' - art (str): Album artwork (/library/metadata//art/) genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents. key (str): API URL (/library/metadata/). originallyAvailableAt (datetime): Datetime this album was released. @@ -219,7 +222,6 @@ def __iter__(self): def _loadData(self, data): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) - self.art = data.attrib.get('art') self.key = self.key.replace('/children', '') # fixes bug #50 self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d') self.parentKey = data.attrib.get('parentKey') @@ -262,7 +264,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): keep_original_name (bool): Set True to keep the original filename as stored in the Plex server. False will create a new filename with the format " - ". - kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will + kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will be returned and the additional arguments passed in will be sent to that function. If kwargs is not specified, the media items will be downloaded and saved to disk. @@ -284,7 +286,6 @@ class Track(Audio, Playable): Attributes: TAG (str): 'Directory' TYPE (str): 'track' - art (str): Track artwork (/library/metadata//art/) chapterSource (TYPE): Unknown duration (int): Length of this album in seconds. grandparentArt (str): Album artist artwork. @@ -319,7 +320,6 @@ def _loadData(self, data): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) Playable._loadData(self, data) - self.art = data.attrib.get('art') self.chapterSource = data.attrib.get('chapterSource') self.duration = utils.cast(int, data.attrib.get('duration')) self.grandparentArt = data.attrib.get('grandparentArt') diff --git a/plexapi/base.py b/plexapi/base.py index b8bfbd681..c9853baf6 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -44,9 +44,9 @@ def __init__(self, server, data, initpath=None): self._server = server self._data = data self._initpath = initpath or self.key - self._details_key = '' if data is not None: self._loadData(data) + self._details_key = self._buildDetailsKey() def __repr__(self): uid = self._clean(self.firstAttr('_baseurl', 'key', 'id', 'playQueueID', 'uri')) @@ -81,7 +81,7 @@ def _buildItem(self, elem, cls=None, initpath=None): raise UnknownType("Unknown library type <%s type='%s'../>" % (elem.tag, etype)) def _buildItemOrNone(self, elem, cls=None, initpath=None): - """ Calls :func:`~plexapi.base.PlexObject._buildItem()` but returns + """ Calls :func:`~plexapi.base.PlexObject._buildItem` but returns None if elem is an unknown type. """ try: @@ -89,6 +89,22 @@ def _buildItemOrNone(self, elem, cls=None, initpath=None): except UnknownType: return None + def _buildDetailsKey(self, **kwargs): + """ Builds the details key with the XML include parameters. + All parameters are included by default with the option to override each parameter + or disable each parameter individually by setting it to False or 0. + """ + details_key = self.key + if hasattr(self, '_INCLUDES'): + includes = {} + for k, v in self._INCLUDES.items(): + value = kwargs.get(k, v) + if value not in [False, 0, '0']: + includes[k] = 1 if value is True else value + if includes: + details_key += '?' + urlencode(sorted(includes.items())) + return details_key + def fetchItem(self, ekey, cls=None, **kwargs): """ Load the specified key to find and build the first item with the specified tag and attrs. If no tag or attrs are specified then @@ -203,9 +219,39 @@ def listAttrs(self, data, attr, **kwargs): results.append(elem.attrib.get(attr)) return results - def reload(self, key=None): - """ Reload the data for this object from self.key. """ - key = key or self._details_key or self.key + def reload(self, key=None, **kwargs): + """ Reload the data for this object from self.key. + + Parameters: + key (string, optional): Override the key to reload. + **kwargs (dict): A dictionary of XML include parameters to exclude or override. + All parameters are included by default with the option to override each parameter + or disable each parameter individually by setting it to False or 0. + See :class:`~plexapi.base.PlexPartialObject` for all the available include parameters. + + Example: + + .. code-block:: python + + from plexapi.server import PlexServer + plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx') + movie = plex.library.section('Movies').get('Cars') + + # Partial reload of the movie without the `checkFiles` parameter. + # Excluding `checkFiles` will prevent the Plex server from reading the + # file to check if the file still exists and is accessible. + # The movie object will remain as a partial object. + movie.reload(checkFiles=False) + movie.isPartialObject() # Returns True + + # Full reload of the movie with all include parameters. + # The movie object will be a full object. + movie.reload() + movie.isFullObject() # Returns True + + """ + details_key = self._buildDetailsKey(**kwargs) if kwargs else self._details_key + key = key or details_key or self.key if not key: raise Unsupported('Cannot reload an object not built from a URL.') self._initpath = key @@ -281,6 +327,27 @@ class PlexPartialObject(PlexObject): and if the specified value you request is None it will fetch the full object automatically and update itself. """ + _INCLUDES = { + 'checkFiles': 1, + 'includeAllConcerts': 1, + 'includeBandwidths': 1, + 'includeChapters': 1, + 'includeChildren': 1, + 'includeConcerts': 1, + 'includeExternalMedia': 1, + 'includeExtras': 1, + 'includeFields': 'thumbBlurHash,artBlurHash', + 'includeGeolocation': 1, + 'includeLoudnessRamps': 1, + 'includeMarkers': 1, + 'includeOnDeck': 1, + 'includePopularLeaves': 1, + 'includePreferences': 1, + 'includeRelated': 1, + 'includeRelatedCount': 1, + 'includeReviews': 1, + 'includeStations': 1 + } def __eq__(self, other): return other not in [None, []] and self.key == other.key @@ -629,7 +696,7 @@ def getStreamURL(self, **params): offset, copyts, protocol, mediaIndex, platform. Raises: - :class:`plexapi.exceptions.Unsupported`: When the item doesn't support fetching a stream URL. + :exc:`plexapi.exceptions.Unsupported`: When the item doesn't support fetching a stream URL. """ if self.TYPE not in ('movie', 'episode', 'track'): raise Unsupported('Fetching stream URL for %s is unsupported.' % self.TYPE) @@ -694,7 +761,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): keep_original_name (bool): Set True to keep the original filename as stored in the Plex server. False will create a new filename with the format " - ". - kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will + kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will be returned and the additional arguments passed in will be sent to that function. If kwargs is not specified, the media items will be downloaded and saved to disk. diff --git a/plexapi/client.py b/plexapi/client.py index 4405c230e..83ed6e59a 100644 --- a/plexapi/client.py +++ b/plexapi/client.py @@ -53,7 +53,7 @@ class PlexClient(PlexObject): _token (str): Token used to access this client. _session (obj): Requests session object used to access this client. _proxyThroughServer (bool): Set to True after calling - :func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False). + :func:`~plexapi.client.PlexClient.proxyThroughServer` (default False). """ TAG = 'Player' key = '/resources' @@ -140,7 +140,7 @@ def proxyThroughServer(self, value=True, server=None): value (bool): Enable or disable proxying (optional, default True). Raises: - :class:`plexapi.exceptions.Unsupported`: Cannot use client proxy with unknown server. + :exc:`plexapi.exceptions.Unsupported`: Cannot use client proxy with unknown server. """ if server: self._server = server @@ -173,7 +173,7 @@ def query(self, path, method=None, headers=None, timeout=None, **kwargs): return ElementTree.fromstring(data) if data.strip() else None def sendCommand(self, command, proxy=None, **params): - """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily + """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query` to more easily send simple commands to the client. Returns an ElementTree object containing the response. @@ -183,7 +183,7 @@ def sendCommand(self, command, proxy=None, **params): **params (dict): Additional GET parameters to include with the command. Raises: - :class:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability. + :exc:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability. """ command = command.strip('/') controller = command.split('/')[0] @@ -299,7 +299,7 @@ def goToMedia(self, media, **params): **params (dict): Additional GET parameters to include with the command. Raises: - :class:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. + :exc:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. """ if not self._server: raise Unsupported('A server must be specified before using this command.') @@ -469,7 +469,7 @@ def playMedia(self, media, offset=0, **params): also: https://github.com/plexinc/plex-media-player/wiki/Remote-control-API#modified-commands Raises: - :class:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. + :exc:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. """ if not self._server: raise Unsupported('A server must be specified before using this command.') diff --git a/plexapi/library.py b/plexapi/library.py index 4a7c4b848..b48fb2f5e 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -450,13 +450,13 @@ def all(self, sort=None, **kwargs): return self.fetchItems(key, **kwargs) def folders(self): - """ Returns a list of available `:class:`~plexapi.library.Folder` for this library section. + """ Returns a list of available :class:`~plexapi.library.Folder` for this library section. """ key = '/library/sections/%s/folder' % self.key return self.fetchItems(key, Folder) def hubs(self): - """ Returns a list of available `:class:`~plexapi.library.Hub` for this library section. + """ Returns a list of available :class:`~plexapi.library.Hub` for this library section. """ key = '/hubs/sections/%s' % self.key return self.fetchItems(key) @@ -467,7 +467,7 @@ def _filters(self): return self.fetchItems(key, cls=Filter) def _sorts(self, mediaType=None): - """ Returns a list of available `:class:`~plexapi.library.Sort` for this library section. + """ Returns a list of available :class:`~plexapi.library.Sort` for this library section. """ items = [] for data in self.listChoices('sorts', mediaType): @@ -477,7 +477,7 @@ def _sorts(self, mediaType=None): return items def filterFields(self, mediaType=None): - """ Returns a list of available `:class:`~plexapi.library.FilterField` for this library section. + """ Returns a list of available :class:`~plexapi.library.FilterField` for this library section. """ items = [] key = '/library/sections/%s/filters?includeMeta=1' % self.key @@ -496,7 +496,7 @@ def filterFields(self, mediaType=None): return items def agents(self): - """ Returns a list of available `:class:`~plexapi.media.Agent` for this library section. + """ Returns a list of available :class:`~plexapi.media.Agent` for this library section. """ return self._server.agents(utils.searchType(self.type)) @@ -605,7 +605,7 @@ def deleteMediaPreviews(self): def listChoices(self, category, libtype=None, **kwargs): """ Returns a list of :class:`~plexapi.library.FilterChoice` objects for the specified category and libtype. kwargs can be any of the same kwargs in - :func:`plexapi.library.LibraySection.search()` to help narrow down the choices + :func:`~plexapi.library.LibraySection.search` to help narrow down the choices to only those that matter in your current context. Parameters: @@ -614,7 +614,7 @@ def listChoices(self, category, libtype=None, **kwargs): **kwargs (dict): Additional kwargs to narrow down the choices. Raises: - :class:`plexapi.exceptions.BadRequest`: Cannot include kwarg equal to specified category. + :exc:`plexapi.exceptions.BadRequest`: Cannot include kwarg equal to specified category. """ # TODO: Should this be moved to base? if category in kwargs: @@ -661,7 +661,7 @@ def search(self, title=None, sort=None, maxresults=None, * year: List of years to search within ([yyyy, ...]). [all] Raises: - :class:`plexapi.exceptions.BadRequest`: when applying unknown filter + :exc:`plexapi.exceptions.BadRequest`: when applying unknown filter """ # cleanup the core arguments args = {} @@ -755,20 +755,20 @@ def _locations(self): 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. - See description of :func:`~plexapi.library.LibrarySection.search()` for details about filtering / sorting - and :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. + See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting + and :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions. Parameters: - policy (:class:`plexapi.sync.Policy`): policy of syncing the media (how many items to sync and process + policy (:class:`~plexapi.sync.Policy`): policy of syncing the media (how many items to sync and process watched media or not), generated automatically when method called on specific LibrarySection object. - mediaSettings (:class:`plexapi.sync.MediaSettings`): Transcoding settings used for the media, generated + mediaSettings (:class:`~plexapi.sync.MediaSettings`): Transcoding settings used for the media, generated automatically when method called on specific LibrarySection object. - client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see - :func:`plexapi.myplex.MyPlexAccount.sync`. - clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. - title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be + client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see + :func:`~plexapi.myplex.MyPlexAccount.sync`. + clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`. + title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be generated from metadata of current media. sort (str): formatted as `column:dir`; column can be any of {`addedAt`, `originallyAvailableAt`, `lastViewedAt`, `titleSort`, `rating`, `mediaHeight`, `duration`}. dir can be `asc` or @@ -777,10 +777,10 @@ def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, so `track`). Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Raises: - :class:`plexapi.exceptions.BadRequest`: when the library is not allowed to sync + :exc:`plexapi.exceptions.BadRequest`: when the library is not allowed to sync Example: @@ -855,17 +855,17 @@ def collection(self, **kwargs): def sync(self, videoQuality, limit=None, unwatched=False, **kwargs): """ Add current Movie library section as sync item for specified device. - See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and - :func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. + See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and + :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions. Parameters: videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in - :mod:`plexapi.sync` module. + :mod:`~plexapi.sync` module. limit (int): maximum count of movies to sync, unlimited if `None`. unwatched (bool): if `True` watched videos wouldn't be synced. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Example: @@ -903,11 +903,11 @@ class ShowSection(LibrarySection): CONTENT_TYPE = 'video' def searchShows(self, **kwargs): - """ Search for a show. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for a show. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='show', **kwargs) def searchEpisodes(self, **kwargs): - """ Search for an episode. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for an episode. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='episode', **kwargs) def recentlyAdded(self, libtype='episode', maxresults=50): @@ -924,17 +924,17 @@ def collection(self, **kwargs): def sync(self, videoQuality, limit=None, unwatched=False, **kwargs): """ Add current Show library section as sync item for specified device. - See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and - :func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. + See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and + :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions. Parameters: videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in - :mod:`plexapi.sync` module. + :mod:`~plexapi.sync` module. limit (int): maximum count of episodes to sync, unlimited if `None`. unwatched (bool): if `True` watched videos wouldn't be synced. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Example: @@ -982,15 +982,15 @@ def stations(self): return self.fetchItems(key, cls=Station) def searchArtists(self, **kwargs): - """ Search for an artist. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='artist', **kwargs) def searchAlbums(self, **kwargs): - """ Search for an album. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='album', **kwargs) def searchTracks(self, **kwargs): - """ Search for a track. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for a track. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='track', **kwargs) def collection(self, **kwargs): @@ -999,16 +999,16 @@ def collection(self, **kwargs): def sync(self, bitrate, limit=None, **kwargs): """ Add current Music library section as sync item for specified device. - See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and - :func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. + See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and + :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions. Parameters: bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the - module :mod:`plexapi.sync`. + module :mod:`~plexapi.sync`. limit (int): maximum count of tracks to sync, unlimited if `None`. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Example: @@ -1045,25 +1045,25 @@ class PhotoSection(LibrarySection): METADATA_TYPE = 'photo' def searchAlbums(self, title, **kwargs): - """ Search for an album. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='photoalbum', title=title, **kwargs) def searchPhotos(self, title, **kwargs): - """ Search for a photo. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ + """ Search for a photo. See :func:`~plexapi.library.LibrarySection.search` for usage. """ return self.search(libtype='photo', title=title, **kwargs) def sync(self, resolution, limit=None, **kwargs): """ Add current Music library section as sync item for specified device. - See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and - :func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. + See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and + :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions. Parameters: resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the - module :mod:`plexapi.sync`. + module :mod:`~plexapi.sync`. limit (int): maximum count of tracks to sync, unlimited if `None`. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Example: @@ -1090,7 +1090,7 @@ def sync(self, resolution, limit=None, **kwargs): class FilterChoice(PlexObject): """ Represents a single filter choice. These objects are gathered when using filters while searching for library items and is the object returned in the result set of - :func:`~plexapi.library.LibrarySection.listChoices()`. + :func:`~plexapi.library.LibrarySection.listChoices`. Attributes: TAG (str): 'Directory' @@ -1330,7 +1330,7 @@ def _loadData(self, data): self.title = data.attrib.get('title') def subfolders(self): - """ Returns a list of available `:class:`~plexapi.library.Folder` for this folder. + """ Returns a list of available :class:`~plexapi.library.Folder` for this folder. Continue down subfolders until a mediaType is found. """ if self.key.startswith('/library/metadata'): @@ -1339,8 +1339,8 @@ def subfolders(self): return self.fetchItems(self.key, Folder) def allSubfolders(self): - """ Returns a list of all available `:class:`~plexapi.library.Folder` for this folder. - Only returns `:class:`~plexapi.library.Folder`. + """ Returns a list of all available :class:`~plexapi.library.Folder` for this folder. + Only returns :class:`~plexapi.library.Folder`. """ folders = [] for folder in self.subfolders(): @@ -1403,6 +1403,8 @@ class Collections(PlexPartialObject): ratingKey (int): Unique key identifying this item. addedAt (datetime): Datetime this item was added to the library. + art (str): URL to artwork image. + artBlurHash (str): BlurHash string for artwork image. 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. @@ -1420,6 +1422,7 @@ class Collections(PlexPartialObject): subtype (str): Media type summary (str): Summary of the collection thumb (str): URL to thumbnail image. + thumbBlurHash (str): BlurHash string for thumbnail image. title (str): Collection Title titleSort (str): Title to use when sorting (defaults to title). type (str): Hardcoded 'collection' @@ -1429,13 +1432,12 @@ class Collections(PlexPartialObject): TAG = 'Directory' TYPE = 'collection' - _include = "?includeExternalMedia=1&includePreferences=1" 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.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') @@ -1443,7 +1445,7 @@ def _loadData(self, data): 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.key = data.attrib.get('key').replace('/children', '') # FIX_BUG_50 self.labels = self.findItems(data, etag='Label') self.librarySectionID = data.attrib.get('librarySectionID') self.librarySectionKey = data.attrib.get('librarySectionKey') @@ -1453,6 +1455,7 @@ def _loadData(self, data): 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.type = data.attrib.get('type') diff --git a/plexapi/media.py b/plexapi/media.py index 7a106232e..5ac90a5c2 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -477,7 +477,7 @@ class MediaTag(PlexObject): 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). : Attributes only applicable in search results from - PlexServer :func:`~plexapi.server.PlexServer.search()`. They provide details of which + 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). @@ -504,7 +504,7 @@ def _loadData(self, data): 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()`. + in search results from PlexServer :func:`~plexapi.server.PlexServer.search`. """ if not self.key: raise BadRequest('Key is not defined for this tag: %s' % self.tag) diff --git a/plexapi/myplex.py b/plexapi/myplex.py index 8806fdb55..07df639e2 100644 --- a/plexapi/myplex.py +++ b/plexapi/myplex.py @@ -544,7 +544,7 @@ def optOut(self, playback=None, library=None): return self.query(url, method=self._session.put, data=params) def syncItems(self, client=None, clientId=None): - """ Returns an instance of :class:`plexapi.sync.SyncList` for specified client. + """ Returns an instance of :class:`~plexapi.sync.SyncList` for specified client. Parameters: client (:class:`~plexapi.myplex.MyPlexDevice`): a client to query SyncItems for. @@ -564,22 +564,22 @@ def syncItems(self, client=None, clientId=None): def sync(self, sync_item, client=None, clientId=None): """ Adds specified sync item for the client. It's always easier to use methods defined directly in the media - objects, e.g. :func:`plexapi.video.Video.sync`, :func:`plexapi.audio.Audio.sync`. + objects, e.g. :func:`~plexapi.video.Video.sync`, :func:`~plexapi.audio.Audio.sync`. Parameters: client (:class:`~plexapi.myplex.MyPlexDevice`): a client for which you need to add SyncItem to. clientId (str): an identifier of a client for which you need to add SyncItem to. - sync_item (:class:`plexapi.sync.SyncItem`): prepared SyncItem object with all fields set. + sync_item (:class:`~plexapi.sync.SyncItem`): prepared SyncItem object with all fields set. If both `client` and `clientId` provided the client would be preferred. If neither `client` nor `clientId` provided the clientId would be set to current clients`s identifier. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. Raises: - :class:`plexapi.exceptions.BadRequest`: when client with provided clientId wasn`t found. - :class:`plexapi.exceptions.BadRequest`: provided client doesn`t provides `sync-target`. + :exc:`plexapi.exceptions.BadRequest`: when client with provided clientId wasn`t found. + :exc:`plexapi.exceptions.BadRequest`: provided client doesn`t provides `sync-target`. """ if not client and not clientId: clientId = X_PLEX_IDENTIFIER @@ -686,7 +686,7 @@ def tidal(self): class MyPlexUser(PlexObject): """ This object represents non-signed in users such as friends and linked - accounts. NOTE: This should not be confused with the :class:`~myplex.MyPlexAccount` + accounts. NOTE: This should not be confused with the :class:`~plexapi.myplex.MyPlexAccount` which is your specific account. The raw xml for the data presented here can be found at: https://plex.tv/api/users/ @@ -885,7 +885,7 @@ class MyPlexResource(PlexObject): key (str): 'https://plex.tv/api/resources?includeHttps=1&includeRelay=1' accessToken (str): This resources accesstoken. clientIdentifier (str): Unique ID for this resource. - connections (list): List of :class:`~myplex.ResourceConnection` objects + connections (list): List of :class:`~plexapi.myplex.ResourceConnection` objects for this resource. createdAt (datetime): Timestamp this resource first connected to your server. device (str): Best guess on the type of device this is (PS, iPhone, Linux, etc). @@ -930,7 +930,7 @@ def _loadData(self, data): self.sourceTitle = data.attrib.get('sourceTitle') # owners plex username. def connect(self, ssl=None, timeout=None): - """ Returns a new :class:`~server.PlexServer` or :class:`~client.PlexClient` object. + """ Returns a new :class:`~plexapi.server.PlexServer` or :class:`~plexapi.client.PlexClient` object. Often times there is more than one address specified for a server or client. This function will prioritize local connections before remote and HTTPS before HTTP. After trying to connect to all available addresses for this resource and @@ -942,7 +942,7 @@ def connect(self, ssl=None, timeout=None): HTTP or HTTPS connection. Raises: - :class:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource. + :exc:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource. """ # Sort connections from (https, local) to (http, remote) # Only check non-local connections unless we own the resource @@ -965,7 +965,7 @@ def connect(self, ssl=None, timeout=None): class ResourceConnection(PlexObject): """ Represents a Resource Connection object found within the - :class:`~myplex.MyPlexResource` objects. + :class:`~plexapi.myplex.MyPlexResource` objects. Attributes: TAG (str): 'Connection' @@ -1049,7 +1049,7 @@ def connect(self, timeout=None): at least one connection was successful, the PlexClient object is built and returned. Raises: - :class:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device. + :exc:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device. """ cls = PlexServer if 'server' in self.provides else PlexClient listargs = [[cls, url, self.token, timeout] for url in self.connections] @@ -1063,10 +1063,10 @@ def delete(self): self._server.query(key, self._server._session.delete) def syncItems(self): - """ Returns an instance of :class:`plexapi.sync.SyncList` for current device. + """ Returns an instance of :class:`~plexapi.sync.SyncList` for current device. Raises: - :class:`plexapi.exceptions.BadRequest`: when the device doesn`t provides `sync-target`. + :exc:`plexapi.exceptions.BadRequest`: when the device doesn`t provides `sync-target`. """ if 'sync-target' not in self.provides: raise BadRequest('Requested syncList for device which do not provides sync-target') @@ -1082,12 +1082,12 @@ class MyPlexPinLogin(object): This helper class supports a polling, threaded and callback approach. - The polling approach expects the developer to periodically check if the PIN login was - successful using :func:`plexapi.myplex.MyPlexPinLogin.checkLogin`. + successful using :func:`~plexapi.myplex.MyPlexPinLogin.checkLogin`. - The threaded approach expects the developer to call - :func:`plexapi.myplex.MyPlexPinLogin.run` and then at a later time call - :func:`plexapi.myplex.MyPlexPinLogin.waitForLogin` to wait for and check the result. + :func:`~plexapi.myplex.MyPlexPinLogin.run` and then at a later time call + :func:`~plexapi.myplex.MyPlexPinLogin.waitForLogin` to wait for and check the result. - The callback approach is an extension of the threaded approach and expects the developer - to pass the `callback` parameter to the call to :func:`plexapi.myplex.MyPlexPinLogin.run`. + to pass the `callback` parameter to the call to :func:`~plexapi.myplex.MyPlexPinLogin.run`. The callback will be called when the thread waiting for the PIN login to succeed either finishes or expires. The parameter passed to the callback is the received authentication token or `None` if the login expired. diff --git a/plexapi/photo.py b/plexapi/photo.py index 019b8b26f..301ec319f 100644 --- a/plexapi/photo.py +++ b/plexapi/photo.py @@ -184,20 +184,20 @@ def iterParts(self): def sync(self, resolution, client=None, clientId=None, limit=None, title=None): """ Add current photo as sync item for specified device. - See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. + See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions. Parameters: resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the - module :mod:`plexapi.sync`. - client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see - :func:`plexapi.myplex.MyPlexAccount.sync`. - clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. + module :mod:`~plexapi.sync`. + client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see + :func:`~plexapi.myplex.MyPlexAccount.sync`. + clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`. limit (int): maximum count of items to sync, unlimited if `None`. - title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be + title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be generated from metadata of current photo. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. """ from plexapi.sync import SyncItem, Policy, MediaSettings diff --git a/plexapi/playlist.py b/plexapi/playlist.py index 04912bbe8..fd865af70 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -168,7 +168,7 @@ def create(cls, server, title, items=None, section=None, limit=None, smart=False **kwargs (dict): is passed to the filters. For a example see the search method. Returns: - :class:`plexapi.playlist.Playlist`: an instance of created Playlist. + :class:`~plexapi.playlist.Playlist`: an instance of created Playlist. """ if smart: return cls._createSmart(server, title, section, limit, **kwargs) @@ -222,29 +222,29 @@ def copyToUser(self, user): def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, client=None, clientId=None, limit=None, unwatched=False, title=None): """ Add current playlist as sync item for specified device. - See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. + See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions. Parameters: videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in - :mod:`plexapi.sync` module. Used only when playlist contains video. + :mod:`~plexapi.sync` module. Used only when playlist contains video. photoResolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in - the module :mod:`plexapi.sync`. Used only when playlist contains photos. + the module :mod:`~plexapi.sync`. Used only when playlist contains photos. audioBitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values - from the module :mod:`plexapi.sync`. Used only when playlist contains audio. - client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see - :func:`plexapi.myplex.MyPlexAccount.sync`. - clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. + from the module :mod:`~plexapi.sync`. Used only when playlist contains audio. + client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see + :func:`~plexapi.myplex.MyPlexAccount.sync`. + clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`. limit (int): maximum count of items to sync, unlimited if `None`. unwatched (bool): if `True` watched videos wouldn't be synced. - title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be + title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be generated from metadata of current photo. Raises: - :class:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync. - :class:`plexapi.exceptions.Unsupported`: when playlist content is unsupported. + :exc:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync. + :exc:`plexapi.exceptions.Unsupported`: when playlist content is unsupported. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. """ if not self.allowSync: diff --git a/plexapi/server.py b/plexapi/server.py index a55582bfc..a9439bda6 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -199,7 +199,7 @@ def activities(self): return activities def agents(self, mediaType=None): - """ Returns the `:class:`~plexapi.media.Agent` objects this server has available. """ + """ Returns the :class:`~plexapi.media.Agent` objects this server has available. """ key = '/system/agents' if mediaType: key += '?mediaType=%s' % mediaType @@ -270,7 +270,7 @@ def client(self, name): name (str): Name of the client to return. Raises: - :class:`plexapi.exceptions.NotFound`: Unknown client name + :exc:`plexapi.exceptions.NotFound`: Unknown client name """ for client in self.clients(): if client and client.title == name: @@ -393,7 +393,7 @@ def playlist(self, title): title (str): Title of the playlist to return. Raises: - :class:`plexapi.exceptions.NotFound`: Invalid playlist title + :exc:`plexapi.exceptions.NotFound`: Invalid playlist title """ return self.fetchItem('/playlists', title=title) @@ -494,8 +494,8 @@ def startAlertListener(self, callback=None): Parameters: callback (func): Callback function to call on recieved messages. - raises: - :class:`plexapi.exception.Unsupported`: Websocket-client not installed. + Raises: + :exc:`plexapi.exception.Unsupported`: Websocket-client not installed. """ notifier = AlertListener(self, callback) notifier.start() diff --git a/plexapi/settings.py b/plexapi/settings.py index a62460bdf..8416f8718 100644 --- a/plexapi/settings.py +++ b/plexapi/settings.py @@ -21,7 +21,10 @@ def __init__(self, server, data, initpath=None): def __getattr__(self, attr): if attr.startswith('_'): - return self.__dict__[attr] + try: + return self.__dict__[attr] + except KeyError: + raise AttributeError return self.get(attr).value def __setattr__(self, attr, value): diff --git a/plexapi/sync.py b/plexapi/sync.py index 0f739860c..f88fefb23 100644 --- a/plexapi/sync.py +++ b/plexapi/sync.py @@ -78,7 +78,7 @@ def _loadData(self, data): self.location = data.find('Location').attrib.get('uri', '') def server(self): - """ Returns :class:`plexapi.myplex.MyPlexResource` with server of current item. """ + """ Returns :class:`~plexapi.myplex.MyPlexResource` with server of current item. """ server = [s for s in self._server.resources() if s.clientIdentifier == self.machineIdentifier] if len(server) == 0: raise NotFound('Unable to find server with uuid %s' % self.machineIdentifier) @@ -201,7 +201,7 @@ def createVideo(videoQuality): videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in this module. Raises: - :class:`plexapi.exceptions.BadRequest`: when provided unknown video quality. + :exc:`plexapi.exceptions.BadRequest`: when provided unknown video quality. """ if videoQuality == VIDEO_QUALITY_ORIGINAL: return MediaSettings('', '', '') @@ -231,7 +231,7 @@ def createPhoto(resolution): module. Raises: - :class:`plexapi.exceptions.BadRequest` when provided unknown video quality. + :exc:`plexapi.exceptions.BadRequest` when provided unknown video quality. """ if resolution in PHOTO_QUALITIES: return MediaSettings(photoQuality=PHOTO_QUALITIES[resolution], photoResolution=resolution) diff --git a/plexapi/utils.py b/plexapi/utils.py index 24a9c7dde..6512b7f31 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -146,7 +146,7 @@ def searchType(libtype): libtype (str): LibType to lookup (movie, show, season, episode, artist, album, track, collection) Raises: - :class:`plexapi.exceptions.NotFound`: Unknown libtype + :exc:`plexapi.exceptions.NotFound`: Unknown libtype """ libtype = str(libtype) if libtype in [str(v) for v in SEARCHTYPES.values()]: diff --git a/plexapi/video.py b/plexapi/video.py index e2b542301..65c55804f 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -14,6 +14,8 @@ class Video(PlexPartialObject): Attributes: addedAt (datetime): Datetime this item was added to the library. + art (str): URL to artwork image. + artBlurHash (str): BlurHash string for artwork image. fields (list): List of :class:`~plexapi.media.Field`. key (str): API URL (/library/metadata/). lastViewedAt (datetime): Datetime item was last accessed. @@ -22,6 +24,7 @@ class Video(PlexPartialObject): ratingKey (int): Unique key identifying this item. summary (str): Summary of the artist, track, or album. thumb (str): URL to thumbnail image. + thumbBlurHash (str): BlurHash string for thumbnail image. title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.) titleSort (str): Title to use when sorting (defaults to title). type (str): 'artist', 'album', or 'track'. @@ -35,6 +38,7 @@ def _loadData(self, data): self.listType = 'video' self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) self.art = data.attrib.get('art') + self.artBlurHash = data.attrib.get('artBlurHash') self.fields = self.findItems(data, etag='Field') self.key = data.attrib.get('key', '') self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt')) @@ -42,6 +46,7 @@ def _loadData(self, data): self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) 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') @@ -211,21 +216,21 @@ def optimize(self, title=None, target="", targetTagID=None, locationID=-1, polic def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=False, title=None): """ Add current video (movie, tv-show, season or episode) as sync item for specified device. - See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. + See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions. Parameters: videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in - :mod:`plexapi.sync` module. - client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see - :func:`plexapi.myplex.MyPlexAccount.sync`. - clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. + :mod:`~plexapi.sync` module. + client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see + :func:`~plexapi.myplex.MyPlexAccount.sync`. + clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`. limit (int): maximum count of items to sync, unlimited if `None`. unwatched (bool): if `True` watched videos wouldn't be synced. - title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be + title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be generated from metadata of current media. Returns: - :class:`plexapi.sync.SyncItem`: an instance of created syncItem. + :class:`~plexapi.sync.SyncItem`: an instance of created syncItem. """ from plexapi.sync import SyncItem, Policy, MediaSettings @@ -286,16 +291,12 @@ class Movie(Playable, Video): TAG = 'Video' TYPE = 'movie' METADATA_TYPE = 'movie' - _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' - '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeConcerts=1&includePreferences=1') def _loadData(self, data): """ Load attribute values from Plex XML response. """ Video._loadData(self, data) Playable._loadData(self, data) - self._details_key = self.key + self._include self.art = data.attrib.get('art') self.audienceRating = utils.cast(float, data.attrib.get('audienceRating')) self.audienceRatingImage = data.attrib.get('audienceRatingImage') @@ -350,7 +351,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): savepath (str): Defaults to current working dir. keep_original_name (bool): True to keep the original file name otherwise a friendlier is generated. - **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. + **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`. """ filepaths = [] locations = [i for i in self.iterParts() if i] @@ -401,10 +402,6 @@ class Show(Video): TYPE = 'show' METADATA_TYPE = 'episode' - _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' - '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeMarkers=1&includeConcerts=1&includePreferences=1') - def __iter__(self): for season in self.seasons(): yield season @@ -414,7 +411,6 @@ def _loadData(self, data): Video._loadData(self, data) # fix key if loaded from search self.key = self.key.replace('/children', '') - self._details_key = self.key + self._include self.art = data.attrib.get('art') self.banner = data.attrib.get('banner') self.childCount = utils.cast(int, data.attrib.get('childCount')) @@ -519,14 +515,14 @@ def episodes(self, **kwargs): def episode(self, title=None, season=None, episode=None): """ Find a episode using a title or season and episode. - Parameters: + Parameters: title (str): Title of the episode to return season (int): Season number (default:None; required if title not specified). episode (int): Episode number (default:None; required if title not specified). - Raises: - :class:`plexapi.exceptions.BadRequest`: If season and episode is missing. - :class:`plexapi.exceptions.NotFound`: If the episode is missing. + Raises: + :exc:`plexapi.exceptions.BadRequest`: If season and episode is missing. + :exc:`plexapi.exceptions.NotFound`: If the episode is missing. """ if title: key = '/library/metadata/%s/allLeaves' % self.ratingKey @@ -547,7 +543,7 @@ def unwatched(self): return self.episodes(viewCount=0) def get(self, title=None, season=None, episode=None): - """ Alias to :func:`~plexapi.video.Show.episode()`. """ + """ Alias to :func:`~plexapi.video.Show.episode`. """ return self.episode(title, season, episode) def download(self, savepath=None, keep_original_name=False, **kwargs): @@ -557,7 +553,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): savepath (str): Defaults to current working dir. keep_original_name (bool): True to keep the original file name otherwise a friendlier is generated. - **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. + **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`. """ filepaths = [] for episode in self.episodes(): @@ -636,7 +632,7 @@ def episode(self, title=None, episode=None): return self.fetchItem(key, parentIndex=self.index, index=episode) def get(self, title=None, episode=None): - """ Alias to :func:`~plexapi.video.Season.episode()`. """ + """ Alias to :func:`~plexapi.video.Season.episode`. """ return self.episode(title, episode) def show(self): @@ -658,7 +654,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): savepath (str): Defaults to current working dir. keep_original_name (bool): True to keep the original file name otherwise a friendlier is generated. - **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. + **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`. """ filepaths = [] for episode in self.episodes(): @@ -707,15 +703,10 @@ class Episode(Playable, Video): TYPE = 'episode' METADATA_TYPE = 'episode' - _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' - '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeMarkers=1&includeConcerts=1&includePreferences=1') - def _loadData(self, data): """ Load attribute values from Plex XML response. """ Video._loadData(self, data) Playable._loadData(self, data) - self._details_key = self.key + self._include self._seasonNumber = None # cached season number self.art = data.attrib.get('art') self.chapterSource = data.attrib.get('chapterSource') diff --git a/tests/test_video.py b/tests/test_video.py index 21ca259b2..44cf97f77 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -66,6 +66,8 @@ def test_video_Movie_getStreamURL(movie, account): def test_video_Movie_isFullObject_and_reload(plex): movie = plex.library.section("Movies").get("Sita Sings the Blues") assert movie.isFullObject() is False + movie.reload(checkFiles=False) + assert movie.isFullObject() is False movie.reload() assert movie.isFullObject() is True movie_via_search = plex.library.search(movie.title)[0]