diff --git a/plexapi/base.py b/plexapi/base.py index ede56efd..5f2359ef 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -2,26 +2,25 @@ import re from plexapi import log, utils from plexapi.compat import urlencode -from plexapi.exceptions import BadRequest, NotFound -from plexapi.exceptions import 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), + '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), 'ismissing': None, # special case in _checkAttrs - 'regex': lambda v,q: re.match(q, v), - 'iregex': lambda v,q: re.match(q, v, flags=re.IGNORECASE), + 'regex': lambda v, q: re.match(q, v), + 'iregex': lambda v, q: re.match(q, v, flags=re.IGNORECASE), } @@ -52,9 +51,10 @@ class PlexObject(object): for attr in attrs: value = self.__dict__.get(attr) if value: - value = str(value).replace(' ','-') - value = value.replace('/library/metadata/','') - value = value.replace('/children','') + value = value.encode('utf-8') + value = str(value).replace(' ', '-') + value = value.replace('/library/metadata/', '') + value = value.replace('/children', '') return value[:20] def _buildItem(self, elem, cls=None, initpath=None, bytag=False): @@ -103,7 +103,7 @@ class PlexObject(object): in, the key will be translated to /library/metadata/. 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 to the best guess PlexObjects based on the type attr or tag. bytag (bool): Setting this to True tells the build-items function to guess @@ -161,7 +161,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) @@ -221,6 +223,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 @@ -306,6 +316,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. @@ -382,7 +401,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 diff --git a/plexapi/library.py b/plexapi/library.py index c9b52120..0663bcc3 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -186,8 +186,15 @@ class LibrarySection(PlexObject): self.uuid = data.attrib.get('uuid') def __repr__(self): - return '<%s>' % ':'.join([p for p in [self.__class__.__name__, - self.key, self.librarySectionTitle] if p]) + return '<%s>' % ':'.join([p for p in [self.__class__.__name__, self.key, self.librarySectionTitle] if p]) + + def delete(self): + """Delete a library section.""" + try: + return self._server.query('/library/sections/%s' % self.key, method=self._server._session.delete) + log.error('Failed to delete library %s. This could be because you havnt allowed ' + 'items to be deleted' % self.key) + raise def get(self, title): """ Returns the media item with the specified title. diff --git a/tests/test_library.py b/tests/test_library.py index bea1603e..ddadf0bf 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -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): diff --git a/tests/test_video.py b/tests/test_video.py index 048ef9dc..bdfefaf0 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -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©ts=1&mediaIndex=0&offset=0&path=%2Flibrary%2Fmetadata%2F1&X-Plex-Token={0}".format(os.environ.get('PLEX_TEST_TOKEN'))