Merge dev

This commit is contained in:
Michael Shepanski 2017-02-12 22:38:56 -05:00
commit 44677e59a6
6 changed files with 79 additions and 28 deletions

View file

@ -2,25 +2,25 @@
import re
from plexapi import log, utils
from plexapi.compat import urlencode
from plexapi.exceptions import NotFound, UnknownType, Unsupported
from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported
OPERATORS = {
'exact': lambda v,q: v == q,
'iexact': lambda v,q: v.lower() == q.lower(),
'contains': lambda v,q: q in v,
'icontains': lambda v,q: q.lower() in v.lower(),
'in': lambda v,q: v in q,
'gt': lambda v,q: v > q,
'gte': lambda v,q: v >= q,
'lt': lambda v,q: v < q,
'lte': lambda v,q: v <= q,
'startswith': lambda v,q: v.startswith(q),
'istartswith': lambda v,q: v.lower().startswith(q),
'endswith': lambda v,q: v.endswith(q),
'iendswith': lambda v,q: v.lower().endswith(q),
'exists': lambda v,q: v is not None if q else v is None,
'regex': lambda v,q: re.match(q, v),
'iregex': lambda v,q: re.match(q, v, flags=re.IGNORECASE),
'exact': lambda v, q: v == q,
'iexact': lambda v, q: v.lower() == q.lower(),
'contains': lambda v, q: q in v,
'icontains': lambda v, q: q.lower() in v.lower(),
'in': lambda v, q: v in q,
'gt': lambda v, q: v > q,
'gte': lambda v, q: v >= q,
'lt': lambda v, q: v < q,
'lte': lambda v, q: v <= q,
'startswith': lambda v, q: v.startswith(q),
'istartswith': lambda v, q: v.lower().startswith(q),
'endswith': lambda v, q: v.endswith(q),
'iendswith': lambda v, q: v.lower().endswith(q),
'exists': lambda v, q: v is not None if q else v is None,
'regex': lambda v, q: re.match(q, v),
'iregex': lambda v, q: re.match(q, v, flags=re.IGNORECASE),
}
@ -87,7 +87,7 @@ class PlexObject(object):
in, the key will be translated to /library/metadata/<key>. This allows
fetching an item only knowing its key-id.
cls (:class:`~plexapi.base.PlexObject`): If you know the class of the
items to be fetched, passing this in will help the parser ensure
items to be fetched, passing this in will help the parser ensure
it only returns those items. By default we convert the xml elements
with the best guess PlexObjects based on tag and type attrs.
etag (str): Only fetch items with the specified tag.
@ -166,7 +166,9 @@ class PlexObject(object):
def reload(self, safe=False):
""" Reload the data for this object from self.key. """
if not self.key:
if safe: return None
if safe:
return None
raise Unsupported('Cannot reload an object not built from a URL.')
self._initpath = self.key
data = self._server.query(self.key)
@ -234,6 +236,14 @@ class PlexObject(object):
def _loadData(self, data):
raise NotImplementedError('Abstract method not implemented.')
def delete(self):
try:
return self._server.query(self.key, method=self._server._session.delete)
except BadRequest:
log.error('Failed to delete %s. This could be because you havnt allowed '
'items to be deleted' % self.key)
raise
class PlexPartialObject(PlexObject):
""" Not all objects in the Plex listings return the complete list of elements
@ -319,6 +329,15 @@ class PlexPartialObject(PlexObject):
""" Returns the :class:`~plexapi.library.LibrarySection` this item belongs to. """
return self._server.library.sectionByID(self.librarySectionID)
def delete(self):
"""Delete a media elemeent. This has to be enabled under settings > server > library in plex webui."""
try:
return self._server.query(self.key, method=self._server._session.delete)
except BadRequest: # pragma: no cover
log.error('Failed to delete %s. This could be because you havnt allowed '
'items to be deleted' % self.key)
raise
class Playable(object):
""" This is a general place to store functions specific to media that is Playable.
@ -395,7 +414,7 @@ class Playable(object):
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
""" Downloads this items media to the specified location. Returns a list of
filepaths that have been saved to disk.
Parameters:
savepath (str): Title of the track to return.
keep_orginal_name (bool): Set True to keep the original filename as stored in

View file

@ -180,6 +180,16 @@ class LibrarySection(PlexObject):
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
self.uuid = data.attrib.get('uuid')
def delete(self):
"""Delete a library section."""
try:
return self._server.query('/library/sections/%s' % self.key, method=self._server._session.delete)
except BadRequest: # pragma: no cover
msg = 'Failed to delete library %s' % self.key
msg += 'You may need to allow this permission in your Plex settings.'
log.error(msg)
raise
def get(self, title):
""" Returns the media item with the specified title.

View file

@ -47,9 +47,20 @@ def test_library_section_get_movie(pms): # fix me
assert m
def test_library_section_delete(monkeypatch, pms):
m = pms.library.section('Movies')
monkeypatch.delattr("requests.sessions.Session.request")
try:
m.delete()
except AttributeError:
pass # this will always raise because there is no request anymore.
def test_library_fetchItem(pms):
m = pms.library.fetchItem('/library/metadata/1')
f = pms.library.fetchItem(1)
assert m.title == '16 Blocks'
assert f == m
def test_library_onDeck(pms):

View file

@ -19,7 +19,7 @@ def test_server_attr(pms):
#assert pms.session == <requests.sessions.Session object at 0x029A5E10>
assert pms._token == os.environ.get('PLEX_TEST_TOKEN') or CONFIG.get('authentication.server_token')
assert pms.transcoderActiveVideoSessions == 0
# assert str(pms.updatedAt.date()) == '2017-01-20'
#assert str(pms.updatedAt.date()) == '2017-01-20'
assert pms.version == '1.3.3.3148-b38628e'

View file

@ -89,13 +89,14 @@ def test_utils_cast():
def test_utils_download(a_episode):
# this files is really getting downloaded..
without_session = utils.download(a_episode.getStreamURL(),
filename=a_episode.location, mocked=True)
filename=a_episode.location,
mocked=True)
assert without_session
with_session = utils.download(a_episode.getStreamURL(),
filename=a_episode.location, session=a_episode._server._session,
mocked=True)
filename=a_episode.location,
session=a_episode._server._session,
mocked=True)
assert with_session
img = utils.download(a_episode.thumbUrl, filename=a_episode.title, mocked=True)
assert img

View file

@ -7,6 +7,16 @@ def test_video_Movie(a_movie_section):
m = a_movie_section.get('Cars')
assert m.title == 'Cars'
def test_video_Movie_delete(monkeypatch, pms):
m = pms.library.section('Movies').get('16 blocks')
monkeypatch.delattr("requests.sessions.Session.request")
try:
m.delete()
except AttributeError:
# Silence this because it will always raise beause of monkeypatch
pass
def test_video_Movie_getStreamURL(a_movie):
assert a_movie.getStreamURL() == "http://138.68.157.5:32400/video/:/transcode/universal/start.m3u8?X-Plex-Platform=Chrome&copyts=1&mediaIndex=0&offset=0&path=%2Flibrary%2Fmetadata%2F1&X-Plex-Token={0}".format(os.environ.get('PLEX_TEST_TOKEN'))
@ -359,9 +369,9 @@ def test_video_Show_isWatched(a_show):
assert not a_show.isWatched
@pytest.mark.xfail
def test_video_Show_section(a_show): # BROKEN!
show = a_show.section()
def test_video_Show_section(a_show):
section = a_show.section()
assert section.title == 'TV Shows'
def test_video_Episode(a_show):