Finish documenting utils.py

This commit is contained in:
Michael Shepanski 2017-01-26 00:25:13 -05:00
parent b61d1888b4
commit 355f4686c9
2 changed files with 136 additions and 186 deletions

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from plexapi import log, utils
from plexapi import X_PLEX_CONTAINER_SIZE
from plexapi.compat import unquote
@ -156,7 +155,7 @@ class LibrarySection(object):
""" Base class for a single library section.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
server (:class:`~plexapi.server.PlexServer`): PlexServer object this library section is from.
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).

View file

@ -7,9 +7,8 @@ from plexapi.exceptions import NotFound, UnknownType, Unsupported
from threading import Thread
# Search Types - Plex uses these to filter specific media types when searching.
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3,
'episode': 4, 'artist': 8, 'album': 9, 'track': 10}
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3, 'episode': 4,
'artist': 8, 'album': 9, 'track': 10}
LIBRARY_TYPES = {}
@ -45,132 +44,102 @@ NA = _NA()
class PlexPartialObject(object):
"""Not all objects in the Plex listings return the complete list of elements
for the object.This object will allow you to assume each object is complete,
""" Not all objects in the Plex listings return the complete list of elements
for the object. This object will allow you to assume each object is complete,
and if the specified value you request is None it will fetch the full object
automatically and update itself.
Attributes:
initpath (str): Relative url to PMS
server (): Description
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
"""
def __init__(self, data, initpath, server=None):
"""
Args:
data (xml.etree.ElementTree.Element): passed from server.query
initpath (str): Relative path
server (None or Plexserver, optional): PMS class your connected to
"""
self.server = server
self.initpath = initpath
self._loadData(data)
def __eq__(self, other):
"""Summary
Args:
other (TYPE): Description
Returns:
TYPE: Description
"""
return other is not None and self.key == other.key
def __repr__(self):
"""Pretty repr."""
clsname = self.__class__.__name__
key = self.key.replace('/library/metadata/', '') if self.key else 'NA'
title = self.title.replace(' ', '.')[0:20].encode('utf8')
return '<%s:%s:%s>' % (clsname, key, title)
def __getattr__(self, attr):
"""Auto reload self, if the attribute is NA
Args:
attr (str): fx key
"""
# Auto reload self, from the full key (path) when needed.
if attr == 'key' or self.__dict__.get(attr) or self.isFullObject():
return self.__dict__.get(attr, NA)
self.reload()
return self.__dict__.get(attr, NA)
def __setattr__(self, attr, value):
"""Set attribute
Args:
attr (str): fx key
value (TYPE): Description
"""
if value != NA or self.isFullObject():
self.__dict__[attr] = value
def _loadData(self, data):
"""Uses a element to set a attrs.
Args:
data (Element): Used by attrs
"""
raise Exception('Abstract method not implemented.')
def isFullObject(self):
""" Retruns True if this is already a full object. A full object means all attributes
were populated from the api path representing only this item. For example, the
search result for a movie often only contain a portion of the attributes a full
object (main url) for that movie contain.
"""
return not self.key or self.key == self.initpath
def isPartialObject(self):
""" Returns True if this is NOT a full object. """
return not self.isFullObject()
def reload(self):
"""Reload the data for this object from PlexServer XML."""
""" Reload the data for this object from PlexServer XML. """
data = self.server.query(self.key)
self.initpath = self.key
self._loadData(data[0])
class Playable(object):
"""This is a general place to store functions specific to media that is Playable.
Things were getting mixed up a bit when dealing with Shows, Season,
Artists, Albums which are all not playable.
""" This is a general place to store functions specific to media that is Playable.
Things were getting mixed up a bit when dealing with Shows, Season, Artists,
Albums which are all not playable.
Attributes: # todo
player (Plexclient): Player
playlistItemID (int): Playlist item id
sessionKey (int): 1223
transcodeSession (str): 12312312
username (str): Fx Hellowlol
viewedAt (datetime): viewed at.
Attributes:
player (:class:`~plexapi.client.PlexClient`): Client object playing this item (for active sessions).
playlistItemID (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items).
sessionKey (int): Active session key.
transcodeSession (:class:`~plexapi.media.TranscodeSession`): Transcode Session object
if item is being transcoded (None otherwise).
username (str): Username of the person playing this item (for active sessions).
viewedAt (datetime): Datetime item was last viewed (history).
"""
def _loadData(self, data):
"""Set the class attributes
Args:
data (xml.etree.ElementTree.Element): usually from server.query
"""
# data for active sessions (/status/sessions)
# Load data for active sessions (/status/sessions)
self.sessionKey = cast(int, data.attrib.get('sessionKey', NA))
self.username = findUsername(data)
self.player = findPlayer(self.server, data)
self.transcodeSession = findTranscodeSession(self.server, data)
# data for history details (/status/sessions/history/all)
# Load data for history details (/status/sessions/history/all)
self.viewedAt = toDatetime(data.attrib.get('viewedAt', NA))
# data for playlist items
# Load data for playlist items
self.playlistItemID = cast(int, data.attrib.get('playlistItemID', NA))
def getStreamURL(self, **params):
"""Make a stream url that can be used by vlc.
""" Returns a stream url that may be used by external applications such as VLC.
Args:
**params (dict): Description
Returns:
string: ''
Parameters:
**params (dict): optional parameters to manipulate the playback when accessing
the stream. A few known parameters include: maxVideoBitrate, videoResolution
offset, copyts, protocol, mediaIndex, platform.
Raises:
Unsupported: Raises a error is the type is wrong.
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)
raise Unsupported('Fetching stream URL for %s is unsupported.' % self.TYPE)
mvb = params.get('maxVideoBitrate')
vr = params.get('videoResolution', '')
params = {
@ -189,32 +158,32 @@ class Playable(object):
return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(params)))
def iterParts(self):
"""Yield parts."""
""" Iterates over the parts of this media item. """
for item in self.media:
for part in item.parts:
yield part
def play(self, client):
"""Start playback on a client.
""" Start playback on the specified client.
Args:
client (PlexClient): The client to start playing on.
Parameters:
client (:class:`~plexapi.client.PlexClient`): Client to start playing on.
"""
client.playMedia(self)
def buildItem(server, elem, initpath, bytag=False):
"""Build classes used by the plexapi.
""" Factory function to build the objects used within the PlexAPI.
Args:
server (Plexserver): Your connected to.
elem (xml.etree.ElementTree.Element): xml from PMS
initpath (str): Relative path
bytag (bool, optional): Description # figure out what this do
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
elem (ElementTree): XML data needed to build the object.
initpath (str): Relative path requested when retrieving specified `data` (optional).
bytag (bool): Creates the object from the name specified by the tag instead of the
default which builds the object specified by the type attribute. <tag type='foo' />
Raises:
UnknownType: Unknown library type libtype
UnknownType: Unknown library type.
"""
libtype = elem.tag if bytag else elem.attrib.get('type')
if libtype == 'photo' and elem.tag == 'Directory':
@ -226,11 +195,11 @@ def buildItem(server, elem, initpath, bytag=False):
def cast(func, value):
"""Helper to change to the correct type
""" Cast the specified value to the specified type (returned by func).
Args:
func (function): function to used [int, bool float]
value (string, int, float): value to cast
Parameters:
func (func): Calback function to used cast to type (int, bool, float, etc).
value (any): value to be cast and returned.
"""
if value not in [None, NA]:
if func == bool:
@ -245,14 +214,14 @@ def cast(func, value):
def findKey(server, key):
"""Finds and builds a object based on ratingKey.
""" Finds and builds a object based on ratingKey.
Args:
server (Plexserver): PMS your connected to
key (int): key to look for
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
key (int): ratingKey to find and return.
Raises:
NotFound: Unable to find key. Key
NotFound: Unable to find key
"""
path = '/library/metadata/{0}'.format(key)
try:
@ -264,15 +233,15 @@ def findKey(server, key):
def findItem(server, path, title):
"""Finds and builds a object based on title.
""" Finds and builds a object based on title.
Args:
server (Plexserver): Description
path (str): Relative path
title (str): Fx 16 blocks
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
path (str): API path that returns item to search title for.
title (str): Title of the item to find and return.
Raises:
NotFound: Unable to find item: title
NotFound: Unable to find item.
"""
for elem in server.query(path):
if elem.attrib.get('title').lower() == title.lower():
@ -281,14 +250,12 @@ def findItem(server, path, title):
def findLocations(data, single=False):
"""Extract the path from a location tag
""" Returns a list of filepaths from a location tag.
Args:
data (xml.etree.ElementTree.Element): xml from PMS as Element
single (bool, optional): Only return one
Returns:
filepath string if single is True else list of filepaths
Parameters:
data (ElementTree): XML object to search for locations in.
single (bool): Set True to only return the first location found.
Return type will be a string if this is set to True.
"""
locations = []
for elem in data:
@ -300,33 +267,26 @@ def findLocations(data, single=False):
def findPlayer(server, data):
"""Find a player in a elementthee
""" Returns the :class:`~plexapi.client.PlexClient` object found in the specified data.
Args:
server (Plexserver): PMS your connected to
data (xml.etree.ElementTree.Element): xml from pms as a element
Returns:
PlexClient or None
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
data (:class:`ElementTree`): XML data to find Player in.
"""
elem = data.find('Player')
if elem is not None:
from plexapi.client import PlexClient
baseurl = 'http://%s:%s' % (elem.attrib.get('address'),
elem.attrib.get('port'))
baseurl = 'http://%s:%s' % (elem.attrib.get('address'), elem.attrib.get('port'))
return PlexClient(baseurl, server=server, data=elem)
return None
def findStreams(media, streamtype):
"""Find streams.
""" Returns a list of streams (str) found in media that match the specified streamtype.
Args:
media (Show, Movie, Episode): A item where find streams
streamtype (str): Possible options [movie, show, episode] # is this correct?
Returns:
list: of streams
Parameters:
media (:class:`~plexapi.utils.Playable`): Item to search for streams (show, movie, episode).
streamtype (str): Streamtype to return (videostream, audiostream, subtitlestream).
"""
streams = []
for mediaitem in media:
@ -338,14 +298,12 @@ def findStreams(media, streamtype):
def findTranscodeSession(server, data):
"""Find transcode session.
""" Returns a :class:`~plexapi.media.TranscodeSession` object if found within the specified
XML data.
Args:
server (Plexserver): PMS your connected to
data (xml.etree.ElementTree.Element): XML response from PMS as Element
Returns:
media.TranscodeSession or None
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
data (:class:`ElementTree`): XML data to find TranscodeSession in.
"""
elem = data.find('TranscodeSession')
@ -356,13 +314,10 @@ def findTranscodeSession(server, data):
def findUsername(data):
"""Find a username in a Element
""" Returns the username if found in the specified XML data. Returns None if not found.
Args:
data (xml.etree.ElementTree.Element): XML from PMS as a Element
Returns:
username or None
Parameters:
data (:class:`ElementTree`): XML data to find username in.
"""
elem = data.find('User')
if elem is not None:
@ -371,7 +326,7 @@ def findUsername(data):
def isInt(str):
"""Check of a string is a int"""
""" Returns True if the specified string passes as an int. """
try:
int(str)
return True
@ -380,14 +335,11 @@ def isInt(str):
def joinArgs(args):
"""Builds a query string where only
the value is quoted.
""" Returns a query string (uses for HTTP URLs) where only the value is URL encoded.
Example return value: '?genre=action&type=1337'.
Args:
args (dict): ex {'genre': 'action', 'type': 1337}
Returns:
string: ?genre=action&type=1337
Parameters:
args (dict): Arguments to include in query string.
"""
if not args:
return ''
@ -399,30 +351,25 @@ def joinArgs(args):
def listChoices(server, path):
"""ListChoices is by _cleanSort etc.
""" Returns a dict of {title:key} for all simple choices in a search filter.
Args:
server (Plexserver): Server your connected to
path (str): Relative path to PMS
Returns:
dict: title:key
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
path (str): Relative path to request XML data from.
"""
return {c.attrib['title']: c.attrib['key'] for c in server.query(path)}
def listItems(server, path, libtype=None, watched=None, bytag=False):
"""Return a list buildItem. See buildItem doc.
""" Returns a list of object built from :func:`~plexapi.utils.buildItem()` found
within the specified path.
Args:
server (Plexserver): PMS your connected to.
path (str): Relative path to PMS
libtype (None or string, optional): [movie, show, episode, music] # check me
watched (None, True, False, optional): Skip or include watched items
bytag (bool, optional): Dunno wtf this is used for # todo
Returns:
list: of buildItem
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
path (str): Relative path to request XML data from.
libtype (str): Optionally return only the specified library type.
watched (bool): Optionally return only watched or unwatched items.
bytag (bool): Set true if libtype is found in the XML tag (and not the 'type' attribute).
"""
items = []
for elem in server.query(path):
@ -440,6 +387,17 @@ def listItems(server, path, libtype=None, watched=None, bytag=False):
def rget(obj, attrstr, default=None, delim='.'):
""" Returns the value at the specified attrstr location within a nexted tree of
dicts, lists, tuples, functions, classes, etc. The lookup is done recursivley
for each key in attrstr (split by by the delimiter) This function is heavily
influenced by the lookups used in Django templates.
Parameters:
obj (any): Object to start the lookup in (dict, obj, list, tuple, etc).
attrstr (str): String to lookup (ex: 'foo.bar.baz.value')
default (any): Default value to return if not found.
delim (str): Delimiter separating keys in attrstr.
"""
try:
parts = attrstr.split(delim, 1)
attr = parts[0]
@ -460,19 +418,15 @@ def rget(obj, attrstr, default=None, delim='.'):
def searchType(libtype):
"""Map search type name to int using SEACHTYPES
Used when querying PMS.
""" Returns the integer value of the library string type.
Args:
libtype (str): Possible options see SEARCHTYPES
Returns:
int: fx 1
Parameters:
libtype (str): Library type to lookup (movie, show, season, episode,
artist, album, track)
Raises:
NotFound: Unknown libtype: libtype
NotFound: Unknown libtype
"""
libtype = str(libtype)
if libtype in [str(v) for v in SEARCHTYPES.values()]:
return libtype
@ -482,12 +436,12 @@ def searchType(libtype):
def threaded(callback, listargs):
"""Run some function in threads.
Args:
callback (function): funcion to run in thread
listargs (list): args parssed to the callback
""" Returns the result of <callback> for each set of *args in listargs. Each call
to <callback. is called concurrently in their own separate threads.
Parameters:
callback (func): Callback function to apply to each set of *args.
listargs (list): List of lists; *args to pass each thread.
"""
threads, results = [], []
for args in listargs:
@ -501,14 +455,11 @@ def threaded(callback, listargs):
def toDatetime(value, format=None):
"""Helper for datetime.
""" Returns a datetime object from the specified value.
Args:
value (str): value to use to make datetime
format (None, optional): string as strptime.
Returns:
datetime
Parameters:
value (str): value to return as a datetime
format (str): Format to pass strftime (optional; if value is a str).
"""
if value and value != NA:
if format: