mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Create separate PlexSession objects (#931)
* Add base findItem method * Create new PlexSession objects * PlexSession objects are unique from their base media objects. These objects are built from `/status/sessions` instead of `/library/metadata/<ratingKey>`. This allows reloading data from sessions without overwriting them. * Add some media and client attributes for sessions * Add separater property to return the PlexSession user object * This speeds up building the `PlexSession` object since it doesn't need to lookup the user. * The user object is also cached for future lookups. * Remove PlexSession attributes from tests * Never auto reload a PlexSession object * Rename PlexSession.usernames for backwards compatibility Co-authored-by: jjlawren <jjlawren@users.noreply.github.com> * Don't cache myPlexAccount in PlexSession * Move session stop method to PlexSession Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>
This commit is contained in:
parent
d09cc47562
commit
925f573ced
9 changed files with 212 additions and 55 deletions
|
@ -3,7 +3,7 @@ import os
|
|||
from urllib.parse import quote_plus
|
||||
|
||||
from plexapi import media, utils
|
||||
from plexapi.base import Playable, PlexPartialObject
|
||||
from plexapi.base import Playable, PlexPartialObject, PlexSession
|
||||
from plexapi.exceptions import BadRequest
|
||||
from plexapi.mixins import (
|
||||
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
|
||||
|
@ -46,7 +46,6 @@ class Audio(PlexPartialObject):
|
|||
userRating (float): Rating of the item (0.0 - 10.0) equaling (0 stars - 5 stars).
|
||||
viewCount (int): Count of times the item was played.
|
||||
"""
|
||||
|
||||
METADATA_TYPE = 'track'
|
||||
|
||||
def _loadData(self, data):
|
||||
|
@ -468,3 +467,16 @@ class Track(
|
|||
def _getWebURL(self, base=None):
|
||||
""" Get the Plex Web URL with the correct parameters. """
|
||||
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey)
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class TrackSession(PlexSession, Track):
|
||||
""" Represents a single Track session
|
||||
loaded from :func:`~plexapi.server.PlexServer.sessions`.
|
||||
"""
|
||||
_SESSIONTYPE = True
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Track._loadData(self, data)
|
||||
PlexSession._loadData(self, data)
|
||||
|
|
161
plexapi/base.py
161
plexapi/base.py
|
@ -1,15 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import weakref
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
from urllib.parse import urlencode
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from plexapi import log, utils
|
||||
from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported
|
||||
|
||||
USER_DONT_RELOAD_FOR_KEYS = set()
|
||||
_DONT_RELOAD_FOR_KEYS = {'key', 'session'}
|
||||
_DONT_OVERWRITE_SESSION_KEYS = {'usernames', 'players', 'transcodeSessions', 'session'}
|
||||
_DONT_RELOAD_FOR_KEYS = {'key'}
|
||||
OPERATORS = {
|
||||
'exact': lambda v, q: v == q,
|
||||
'iexact': lambda v, q: v.lower() == q.lower(),
|
||||
|
@ -63,10 +62,6 @@ class PlexObject:
|
|||
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid, name] if p])
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
# Don't overwrite session specific attr with []
|
||||
if attr in _DONT_OVERWRITE_SESSION_KEYS and value == []:
|
||||
value = getattr(self, attr, [])
|
||||
|
||||
overwriteNone = self.__dict__.get('_overwriteNone')
|
||||
# Don't overwrite an attr with None unless it's a private variable or overwrite None is True
|
||||
if value is not None or attr.startswith('_') or attr not in self.__dict__ or overwriteNone:
|
||||
|
@ -90,6 +85,8 @@ class PlexObject:
|
|||
# cls is not specified, try looking it up in PLEXOBJECTS
|
||||
etype = elem.attrib.get('streamType', elem.attrib.get('tagType', elem.attrib.get('type')))
|
||||
ehash = '%s.%s' % (elem.tag, etype) if etype else elem.tag
|
||||
if initpath == '/status/sessions':
|
||||
ehash = '%s.%s' % (ehash, 'session')
|
||||
ecls = utils.PLEXOBJECTS.get(ehash, utils.PLEXOBJECTS.get(elem.tag))
|
||||
# log.debug('Building %s as %s', elem.tag, ecls.__name__)
|
||||
if ecls is not None:
|
||||
|
@ -171,14 +168,16 @@ class PlexObject:
|
|||
raise BadRequest('ekey was not provided')
|
||||
if isinstance(ekey, int):
|
||||
ekey = '/library/metadata/%s' % ekey
|
||||
|
||||
data = self._server.query(ekey)
|
||||
librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
|
||||
for elem in data:
|
||||
if self._checkAttrs(elem, **kwargs):
|
||||
item = self._buildItem(elem, cls, ekey)
|
||||
if librarySectionID:
|
||||
item.librarySectionID = librarySectionID
|
||||
return item
|
||||
item = self.findItem(data, cls, ekey, **kwargs)
|
||||
|
||||
if item:
|
||||
librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
|
||||
if librarySectionID:
|
||||
item.librarySectionID = librarySectionID
|
||||
return item
|
||||
|
||||
clsname = cls.__name__ if cls else 'None'
|
||||
raise NotFound('Unable to find elem: cls=%s, attrs=%s' % (clsname, kwargs))
|
||||
|
||||
|
@ -256,15 +255,16 @@ class PlexObject:
|
|||
fetchItem(ekey, Media__Part__file__startswith="D:\\Movies")
|
||||
|
||||
"""
|
||||
url_kw = {}
|
||||
if container_start is not None:
|
||||
url_kw["X-Plex-Container-Start"] = container_start
|
||||
if container_size is not None:
|
||||
url_kw["X-Plex-Container-Size"] = container_size
|
||||
|
||||
if ekey is None:
|
||||
raise BadRequest('ekey was not provided')
|
||||
data = self._server.query(ekey, params=url_kw)
|
||||
|
||||
params = {}
|
||||
if container_start is not None:
|
||||
params["X-Plex-Container-Start"] = container_start
|
||||
if container_size is not None:
|
||||
params["X-Plex-Container-Size"] = container_size
|
||||
|
||||
data = self._server.query(ekey, params=params)
|
||||
items = self.findItems(data, cls, ekey, **kwargs)
|
||||
|
||||
librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
|
||||
|
@ -273,6 +273,25 @@ class PlexObject:
|
|||
item.librarySectionID = librarySectionID
|
||||
return items
|
||||
|
||||
def findItem(self, data, cls=None, initpath=None, rtag=None, **kwargs):
|
||||
""" Load the specified data to find and build the first items with the specified tag
|
||||
and attrs. See :func:`~plexapi.base.PlexObject.fetchItem` for more details
|
||||
on how this is used.
|
||||
"""
|
||||
# filter on cls attrs if specified
|
||||
if cls and cls.TAG and 'tag' not in kwargs:
|
||||
kwargs['etag'] = cls.TAG
|
||||
if cls and cls.TYPE and 'type' not in kwargs:
|
||||
kwargs['type'] = cls.TYPE
|
||||
# rtag to iter on a specific root tag
|
||||
if rtag:
|
||||
data = next(data.iter(rtag), [])
|
||||
# loop through all data elements to find matches
|
||||
for elem in data:
|
||||
if self._checkAttrs(elem, **kwargs):
|
||||
item = self._buildItemOrNone(elem, cls, initpath)
|
||||
return item
|
||||
|
||||
def findItems(self, data, cls=None, initpath=None, rtag=None, **kwargs):
|
||||
""" Load the specified data to find and build all items with the specified tag
|
||||
and attrs. See :func:`~plexapi.base.PlexObject.fetchItem` for more details
|
||||
|
@ -468,11 +487,11 @@ class PlexPartialObject(PlexObject):
|
|||
value = super(PlexPartialObject, self).__getattribute__(attr)
|
||||
# Check a few cases where we don't want to reload
|
||||
if attr in _DONT_RELOAD_FOR_KEYS: return value
|
||||
if attr in _DONT_OVERWRITE_SESSION_KEYS: return value
|
||||
if attr in USER_DONT_RELOAD_FOR_KEYS: return value
|
||||
if attr.startswith('_'): return value
|
||||
if value not in (None, []): return value
|
||||
if self.isFullObject(): return value
|
||||
if isinstance(self, PlexSession): return value
|
||||
if self._autoReload is False: return value
|
||||
# Log the reload.
|
||||
clsname = self.__class__.__name__
|
||||
|
@ -655,12 +674,6 @@ class Playable:
|
|||
Albums which are all not playable.
|
||||
|
||||
Attributes:
|
||||
sessionKey (int): Active session key.
|
||||
usernames (str): Username of the person playing this item (for active sessions).
|
||||
players (:class:`~plexapi.client.PlexClient`): Client objects playing this item (for active sessions).
|
||||
session (:class:`~plexapi.media.Session`): Session object, for a playing media file.
|
||||
transcodeSessions (:class:`~plexapi.media.TranscodeSession`): Transcode Session object
|
||||
if item is being transcoded (None otherwise).
|
||||
viewedAt (datetime): Datetime item was last viewed (history).
|
||||
accountID (int): The associated :class:`~plexapi.server.SystemAccount` ID.
|
||||
deviceID (int): The associated :class:`~plexapi.server.SystemDevice` ID.
|
||||
|
@ -669,11 +682,6 @@ class Playable:
|
|||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey')) # session
|
||||
self.usernames = self.listAttrs(data, 'title', etag='User') # session
|
||||
self.players = self.findItems(data, etag='Player') # session
|
||||
self.transcodeSessions = self.findItems(data, etag='TranscodeSession') # session
|
||||
self.session = self.findItems(data, etag='Session') # session
|
||||
self.viewedAt = utils.toDatetime(data.attrib.get('viewedAt')) # history
|
||||
self.accountID = utils.cast(int, data.attrib.get('accountID')) # history
|
||||
self.deviceID = utils.cast(int, data.attrib.get('deviceID')) # history
|
||||
|
@ -774,11 +782,6 @@ class Playable:
|
|||
|
||||
return filepaths
|
||||
|
||||
def stop(self, reason=''):
|
||||
""" Stop playback for a media item. """
|
||||
key = '/status/sessions/terminate?sessionId=%s&reason=%s' % (self.session[0].id, quote_plus(reason))
|
||||
return self._server.query(key)
|
||||
|
||||
def updateProgress(self, time, state='stopped'):
|
||||
""" Set the watched progress for this video.
|
||||
|
||||
|
@ -814,6 +817,88 @@ class Playable:
|
|||
self._reload(_overwriteNone=False)
|
||||
|
||||
|
||||
class PlexSession(object):
|
||||
""" This is a general place to store functions specific to media that is a Plex Session.
|
||||
|
||||
Attributes:
|
||||
live (bool): True if this is a live tv session.
|
||||
player (:class:`~plexapi.client.PlexClient`): PlexClient object for the session.
|
||||
session (:class:`~plexapi.media.Session`): Session object for the session
|
||||
if the session is using bandwidth (None otherwise).
|
||||
sessionKey (int): The session key for the session.
|
||||
transcodeSession (:class:`~plexapi.media.TranscodeSession`): TranscodeSession object
|
||||
if item is being transcoded (None otherwise).
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
self.live = utils.cast(bool, data.attrib.get('live', '0'))
|
||||
self.player = self.findItem(data, etag='Player')
|
||||
self.session = self.findItem(data, etag='Session')
|
||||
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey'))
|
||||
self.transcodeSession = self.findItem(data, etag='TranscodeSession')
|
||||
|
||||
user = data.find('User')
|
||||
self._username = user.attrib.get('title')
|
||||
self._userId = utils.cast(int, user.attrib.get('id'))
|
||||
self._user = None # Cache for user object
|
||||
|
||||
# For backwards compatibility
|
||||
self.players = [self.player] if self.player else []
|
||||
self.sessions = [self.session] if self.session else []
|
||||
self.transcodeSessions = [self.transcodeSession] if self.transcodeSession else []
|
||||
self.usernames = [self._username] if self._username else []
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
""" Returns the :class:`~plexapi.myplex.MyPlexAccount` object (for admin)
|
||||
or :class:`~plexapi.myplex.MyPlexUser` object (for users) for this session.
|
||||
"""
|
||||
if self._user is None:
|
||||
myPlexAccount = self._server.myPlexAccount()
|
||||
if self._userId == 1:
|
||||
self._user = myPlexAccount
|
||||
else:
|
||||
self._user = myPlexAccount.user(self._username)
|
||||
return self._user
|
||||
|
||||
def reload(self):
|
||||
""" Reload the data for the session.
|
||||
Note: This will return the object as-is if the session is no longer active.
|
||||
"""
|
||||
return self._reload()
|
||||
|
||||
def _reload(self, _autoReload=False, **kwargs):
|
||||
""" Perform the actual reload. """
|
||||
# Do not auto reload sessions
|
||||
if _autoReload:
|
||||
return self
|
||||
|
||||
key = self._initpath
|
||||
data = self._server.query(key)
|
||||
for elem in data:
|
||||
if elem.attrib.get('sessionKey') == str(self.sessionKey):
|
||||
self._loadData(elem)
|
||||
break
|
||||
return self
|
||||
|
||||
def source(self):
|
||||
""" Return the source media object for the session. """
|
||||
return self.fetchItem(self._details_key)
|
||||
|
||||
def stop(self, reason=''):
|
||||
""" Stop playback for the session.
|
||||
|
||||
Parameters:
|
||||
reason (str): Message displayed to the user for stopping playback.
|
||||
"""
|
||||
params = {
|
||||
'sessionId': self.session.id,
|
||||
'reason': reason,
|
||||
}
|
||||
key = '/status/sessions/terminate'
|
||||
return self._server.query(key, params=params)
|
||||
|
||||
|
||||
class MediaContainer(PlexObject):
|
||||
""" Represents a single MediaContainer.
|
||||
|
||||
|
|
|
@ -136,12 +136,15 @@ class PlexClient(PlexObject):
|
|||
# Add this in next breaking release.
|
||||
# if self._initpath == 'status/sessions':
|
||||
self.device = data.attrib.get('device') # session
|
||||
self.profile = data.attrib.get('profile') # session
|
||||
self.model = data.attrib.get('model') # session
|
||||
self.state = data.attrib.get('state') # session
|
||||
self.vendor = data.attrib.get('vendor') # session
|
||||
self.version = data.attrib.get('version') # session
|
||||
self.local = utils.cast(bool, data.attrib.get('local', 0))
|
||||
self.address = data.attrib.get('address') # session
|
||||
self.local = utils.cast(bool, data.attrib.get('local', 0)) # session
|
||||
self.relayed = utils.cast(bool, data.attrib.get('relayed', 0)) # session
|
||||
self.secure = utils.cast(bool, data.attrib.get('secure', 0)) # session
|
||||
self.address = data.attrib.get('address') # session
|
||||
self.remotePublicAddress = data.attrib.get('remotePublicAddress')
|
||||
self.userID = data.attrib.get('userID')
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ class Media(PlexObject):
|
|||
self.optimizedForStreaming = utils.cast(bool, data.attrib.get('optimizedForStreaming'))
|
||||
self.parts = self.findItems(data, MediaPart)
|
||||
self.proxyType = utils.cast(int, data.attrib.get('proxyType'))
|
||||
self.selected = utils.cast(bool, data.attrib.get('selected'))
|
||||
self.target = data.attrib.get('target')
|
||||
self.title = data.attrib.get('title')
|
||||
self.videoCodec = data.attrib.get('videoCodec')
|
||||
|
@ -71,6 +72,7 @@ class Media(PlexObject):
|
|||
self.videoProfile = data.attrib.get('videoProfile')
|
||||
self.videoResolution = data.attrib.get('videoResolution')
|
||||
self.width = utils.cast(int, data.attrib.get('width'))
|
||||
self.uuid = data.attrib.get('uuid')
|
||||
|
||||
if self._isChildOf(etag='Photo'):
|
||||
self.aperture = data.attrib.get('aperture')
|
||||
|
@ -146,7 +148,9 @@ class MediaPart(PlexObject):
|
|||
self.key = data.attrib.get('key')
|
||||
self.optimizedForStreaming = utils.cast(bool, data.attrib.get('optimizedForStreaming'))
|
||||
self.packetLength = utils.cast(int, data.attrib.get('packetLength'))
|
||||
self.protocol = data.attrib.get('protocol')
|
||||
self.requiredBandwidths = data.attrib.get('requiredBandwidths')
|
||||
self.selected = utils.cast(bool, data.attrib.get('selected'))
|
||||
self.size = utils.cast(int, data.attrib.get('size'))
|
||||
self.streams = self._buildStreams(data)
|
||||
self.syncItemId = utils.cast(int, data.attrib.get('syncItemId'))
|
||||
|
@ -239,15 +243,17 @@ class MediaPartStream(PlexObject):
|
|||
self._data = data
|
||||
self.bitrate = utils.cast(int, data.attrib.get('bitrate'))
|
||||
self.codec = data.attrib.get('codec')
|
||||
self.decision = data.attrib.get('decision')
|
||||
self.default = utils.cast(bool, data.attrib.get('default'))
|
||||
self.displayTitle = data.attrib.get('displayTitle')
|
||||
self.extendedDisplayTitle = data.attrib.get('extendedDisplayTitle')
|
||||
self.key = data.attrib.get('key')
|
||||
self.id = utils.cast(int, data.attrib.get('id'))
|
||||
self.index = utils.cast(int, data.attrib.get('index', '-1'))
|
||||
self.key = data.attrib.get('key')
|
||||
self.language = data.attrib.get('language')
|
||||
self.languageCode = data.attrib.get('languageCode')
|
||||
self.languageTag = data.attrib.get('languageTag')
|
||||
self.location = data.attrib.get('location')
|
||||
self.requiredBandwidths = data.attrib.get('requiredBandwidths')
|
||||
self.selected = utils.cast(bool, data.attrib.get('selected', '0'))
|
||||
self.streamType = utils.cast(int, data.attrib.get('streamType'))
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
from urllib.parse import quote_plus
|
||||
|
||||
from plexapi import media, utils, video
|
||||
from plexapi.base import Playable, PlexPartialObject
|
||||
from plexapi.base import Playable, PlexPartialObject, PlexSession
|
||||
from plexapi.exceptions import BadRequest
|
||||
from plexapi.mixins import (
|
||||
RatingMixin,
|
||||
|
@ -291,3 +291,16 @@ class Photo(
|
|||
def _getWebURL(self, base=None):
|
||||
""" Get the Plex Web URL with the correct parameters. """
|
||||
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey, legacy=1)
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class PhotoSession(PlexSession, Photo):
|
||||
""" Represents a single Photo session
|
||||
loaded from :func:`~plexapi.server.PlexServer.sessions`.
|
||||
"""
|
||||
_SESSIONTYPE = True
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Photo._loadData(self, data)
|
||||
PlexSession._loadData(self, data)
|
||||
|
|
|
@ -61,6 +61,8 @@ def registerPlexObject(cls):
|
|||
"""
|
||||
etype = getattr(cls, 'STREAMTYPE', getattr(cls, 'TAGTYPE', cls.TYPE))
|
||||
ehash = '%s.%s' % (cls.TAG, etype) if etype else cls.TAG
|
||||
if getattr(cls, '_SESSIONTYPE', None):
|
||||
ehash = '%s.%s' % (ehash, 'session')
|
||||
if ehash in PLEXOBJECTS:
|
||||
raise Exception('Ambiguous PlexObject definition %s(tag=%s, type=%s) with %s' %
|
||||
(cls.__name__, cls.TAG, etype, PLEXOBJECTS[ehash].__name__))
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
from urllib.parse import quote_plus, urlencode
|
||||
|
||||
from plexapi import media, utils
|
||||
from plexapi.base import Playable, PlexPartialObject
|
||||
from plexapi.base import Playable, PlexPartialObject, PlexSession
|
||||
from plexapi.exceptions import BadRequest
|
||||
from plexapi.mixins import (
|
||||
AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin,
|
||||
|
@ -980,3 +980,42 @@ class Extra(Clip):
|
|||
def _prettyfilename(self):
|
||||
""" Returns a filename for use in download. """
|
||||
return '%s (%s)' % (self.title, self.subtype)
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class MovieSession(PlexSession, Movie):
|
||||
""" Represents a single Movie session
|
||||
loaded from :func:`~plexapi.server.PlexServer.sessions`.
|
||||
"""
|
||||
_SESSIONTYPE = True
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Movie._loadData(self, data)
|
||||
PlexSession._loadData(self, data)
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class EpisodeSession(PlexSession, Episode):
|
||||
""" Represents a single Episode session
|
||||
loaded from :func:`~plexapi.server.PlexServer.sessions`.
|
||||
"""
|
||||
_SESSIONTYPE = True
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Episode._loadData(self, data)
|
||||
PlexSession._loadData(self, data)
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class ClipSession(PlexSession, Clip):
|
||||
""" Represents a single Clip session
|
||||
loaded from :func:`~plexapi.server.PlexServer.sessions`.
|
||||
"""
|
||||
_SESSIONTYPE = True
|
||||
|
||||
def _loadData(self, data):
|
||||
""" Load attribute values from Plex XML response. """
|
||||
Clip._loadData(self, data)
|
||||
PlexSession._loadData(self, data)
|
||||
|
|
|
@ -306,14 +306,12 @@ def test_audio_Track_attrs(album):
|
|||
assert track.ratingCount is None or utils.is_int(track.ratingCount)
|
||||
assert utils.is_int(track.ratingKey)
|
||||
assert track._server._baseurl == utils.SERVER_BASEURL
|
||||
assert track.sessionKey is None
|
||||
assert track.skipCount is None
|
||||
assert track.summary == ""
|
||||
if track.thumb:
|
||||
assert utils.is_thumb(track.thumb)
|
||||
assert track.title == "As Colourful as Ever"
|
||||
assert track.titleSort == "As Colourful as Ever"
|
||||
assert not track.transcodeSessions
|
||||
assert track.type == "track"
|
||||
assert utils.is_datetime(track.updatedAt)
|
||||
assert utils.is_int(track.viewCount, gte=0)
|
||||
|
|
|
@ -87,7 +87,6 @@ def test_video_Movie_attrs(movies):
|
|||
assert utils.is_metadata(movie.primaryExtraKey)
|
||||
assert movie.ratingKey >= 1
|
||||
assert movie._server._baseurl == utils.SERVER_BASEURL
|
||||
assert movie.sessionKey is None
|
||||
assert movie.studio == "Nina Paley"
|
||||
assert utils.is_string(movie.summary, gte=100)
|
||||
assert movie.tagline == "The Greatest Break-Up Story Ever Told."
|
||||
|
@ -96,7 +95,6 @@ def test_video_Movie_attrs(movies):
|
|||
assert utils.is_thumb(movie.thumb)
|
||||
assert movie.title == "Sita Sings the Blues"
|
||||
assert movie.titleSort == "Sita Sings the Blues"
|
||||
assert not movie.transcodeSessions
|
||||
assert movie.type == "movie"
|
||||
assert movie.updatedAt > datetime(2017, 1, 1)
|
||||
assert movie.useOriginalTitle == -1
|
||||
|
@ -1074,11 +1072,13 @@ def test_video_Episode_updateTimeline(episode, patched_http_call):
|
|||
) # 2 minutes.
|
||||
|
||||
|
||||
def test_video_Episode_stop(episode, mocker, patched_http_call):
|
||||
mocker.patch.object(
|
||||
episode, "session", return_value=list(mocker.MagicMock(id="hello"))
|
||||
)
|
||||
episode.stop(reason="It's past bedtime!")
|
||||
def test_video_Episode(show):
|
||||
episode = show.episode("Winter Is Coming")
|
||||
assert episode == show.episode(season=1, episode=1)
|
||||
with pytest.raises(BadRequest):
|
||||
show.episode()
|
||||
with pytest.raises(NotFound):
|
||||
show.episode(season=1337, episode=1337)
|
||||
|
||||
|
||||
def test_video_Episode_history(episode):
|
||||
|
@ -1173,7 +1173,6 @@ def test_video_Episode_attrs(episode):
|
|||
assert utils.is_thumb(episode.thumb)
|
||||
assert episode.title == "Winter Is Coming"
|
||||
assert episode.titleSort == "Winter Is Coming"
|
||||
assert not episode.transcodeSessions
|
||||
assert episode.type == "episode"
|
||||
assert utils.is_datetime(episode.updatedAt)
|
||||
assert episode.userRating is None
|
||||
|
|
Loading…
Reference in a new issue