New CoreState to hold all core states

- Introduce a CoreState class that holds all core states
- Move xState classes to internal
- Use validation.check_instance for consistent error messages
- Store tlid instead of TlTrack to restore last played track
This commit is contained in:
Jens Luetjen 2016-01-10 13:24:14 +01:00
parent 74344f2b19
commit 4869619bb9
16 changed files with 390 additions and 315 deletions

View File

@ -18,9 +18,9 @@ from mopidy.core.mixer import MixerController
from mopidy.core.playback import PlaybackController
from mopidy.core.playlists import PlaylistsController
from mopidy.core.tracklist import TracklistController
from mopidy.internal import versioning
from mopidy.internal import storage, validation, versioning
from mopidy.internal.deprecation import deprecated_property
from mopidy.models import storage
from mopidy.internal.models import CoreState
logger = logging.getLogger(__name__)
@ -163,7 +163,7 @@ class Core(
if len(coverage):
self.load_state('persistent', coverage)
except Exception as e:
logger.warn('setup: Unexpected error: %s', str(e))
logger.warn('Restore state: Unexpected error: %s', str(e))
def teardown(self):
try:
@ -172,7 +172,7 @@ class Core(
if amount and 'off' != amount:
self.save_state('persistent')
except Exception as e:
logger.warn('teardown: Unexpected error: %s', str(e))
logger.warn('Export state: Unexpected error: %s', str(e))
def save_state(self, name):
"""
@ -191,12 +191,13 @@ class Core(
data = {}
data['version'] = mopidy.__version__
data['tracklist'] = self.tracklist._export_state()
data['history'] = self.history._export_state()
data['playback'] = self.playback._export_state()
data['mixer'] = self.mixer._export_state()
data['state'] = CoreState(
tracklist=self.tracklist._export_state(),
history=self.history._export_state(),
playback=self.playback._export_state(),
mixer=self.mixer._export_state())
storage.save(file_name, data)
logger.debug('Save state done')
logger.debug('Save state done.')
def load_state(self, name, coverage):
"""
@ -226,15 +227,14 @@ class Core(
logger.info('Load state from %s', file_name)
data = storage.load(file_name)
if 'history' in data:
self.history._restore_state(data['history'], coverage)
if 'tracklist' in data:
self.tracklist._restore_state(data['tracklist'], coverage)
if 'playback' in data:
if 'state' in data:
core_state = data['state']
validation.check_instance(core_state, CoreState)
self.history._restore_state(core_state.history, coverage)
self.tracklist._restore_state(core_state.tracklist, coverage)
# playback after tracklist
self.playback._restore_state(data['playback'], coverage)
if 'mixer' in data:
self.mixer._restore_state(data['mixer'], coverage)
self.playback._restore_state(core_state.playback, coverage)
self.mixer._restore_state(core_state.mixer, coverage)
logger.debug('Load state done.')

View File

@ -5,7 +5,7 @@ import logging
import time
from mopidy import models
from mopidy.internal.models import HistoryState, HistoryTrack
logger = logging.getLogger(__name__)
@ -62,15 +62,13 @@ class HistoryController(object):
"""Internal method for :class:`mopidy.Core`."""
history_list = []
for timestamp, track in self._history:
history_list.append(models.HistoryTrack(
history_list.append(HistoryTrack(
timestamp=timestamp, track=track))
return models.HistoryState(history=history_list)
return HistoryState(history=history_list)
def _restore_state(self, state, coverage):
"""Internal method for :class:`mopidy.Core`."""
if state:
if not isinstance(state, models.HistoryState):
raise TypeError('Expect an argument of type "HistoryState"')
if 'history' in coverage:
self._history = []
for htrack in state.history:

View File

@ -5,7 +5,7 @@ import logging
from mopidy import exceptions
from mopidy.internal import validation
from mopidy.models import MixerState
from mopidy.internal.models import MixerState
logger = logging.getLogger(__name__)
@ -108,8 +108,6 @@ class MixerController(object):
def _restore_state(self, state, coverage):
"""Internal method for :class:`mopidy.Core`."""
if state:
if not isinstance(state, MixerState):
raise TypeError('Expect an argument of type "MixerState"')
if 'volume' in coverage:
if state.volume:
self.set_volume(state.volume)

View File

@ -2,11 +2,10 @@ from __future__ import absolute_import, unicode_literals
import logging
from mopidy import models
from mopidy.audio import PlaybackState
from mopidy.compat import urllib
from mopidy.core import listener
from mopidy.internal import deprecation, validation
from mopidy.internal import deprecation, models, validation
logger = logging.getLogger(__name__)
@ -552,14 +551,13 @@ class PlaybackController(object):
def _restore_state(self, state, coverage):
"""Internal method for :class:`mopidy.Core`."""
if state:
if not isinstance(state, models.PlaybackState):
raise TypeError('Expect an argument of type "PlaybackState"')
new_state = None
if 'play-always' in coverage:
new_state = PlaybackState.PLAYING
if 'play-last' in coverage:
new_state = state.state
if state.tlid is not None:
# TODO: restore to 'paused' state
if PlaybackState.PLAYING == new_state:
self.play(tlid=state.tlid)
# TODO: seek to state.position?

View File

@ -6,7 +6,8 @@ import random
from mopidy import exceptions
from mopidy.core import listener
from mopidy.internal import deprecation, validation
from mopidy.models import TlTrack, Track, TracklistState
from mopidy.internal.models import TracklistState
from mopidy.models import TlTrack, Track
logger = logging.getLogger(__name__)
@ -658,8 +659,6 @@ class TracklistController(object):
def _restore_state(self, state, coverage):
"""Internal method for :class:`mopidy.Core`."""
if state:
if not isinstance(state, TracklistState):
raise TypeError('Expect an argument of type "TracklistState"')
if 'mode' in coverage:
self.set_consume(state.consume)
self.set_random(state.random)
@ -670,5 +669,9 @@ class TracklistController(object):
self._next_tlid = state.next_tlid
self._tl_tracks = []
for track in state.tl_tracks:
# TODO: check if any backend will play the track.
# Could be an issue with music streaming services
# (login), disabled extensions and automatically
# generated playlists (pandora).
self._tl_tracks.append(track)
self._trigger_tracklist_changed()

142
mopidy/internal/models.py Normal file
View File

@ -0,0 +1,142 @@
from __future__ import absolute_import, unicode_literals
from mopidy.internal import validation
from mopidy.models import Ref, TlTrack, fields
from mopidy.models.immutable import ValidatedImmutableObject
_MODELS = ['HistoryTrack', 'HistoryState', 'MixerState', 'PlaybackState',
'TracklistState', 'CoreState']
class HistoryTrack(ValidatedImmutableObject):
"""
A history track. Wraps a :class:`Ref` and it's timestamp.
:param timestamp: the timestamp
:type timestamp: int
:param track: the track reference
:type track: :class:`Ref`
"""
# The timestamp. Read-only.
timestamp = fields.Integer()
# The track reference. Read-only.
track = fields.Field(type=Ref)
class HistoryState(ValidatedImmutableObject):
"""
State of the history controller.
Internally used for import/export of current state.
:param history: the track history
:type history: list of :class:`HistoryTrack`
"""
# The tracks. Read-only.
history = fields.Collection(type=HistoryTrack, container=tuple)
class MixerState(ValidatedImmutableObject):
"""
State of the mixer controller.
Internally used for import/export of current state.
:param volume: the volume
:type volume: int
"""
# The volume. Read-only.
volume = fields.Integer(min=0, max=100)
class PlaybackState(ValidatedImmutableObject):
"""
State of the playback controller.
Internally used for import/export of current state.
:param tlid: current track tlid
:type tlid: int
:param position: play position
:type position: int
:param state: playback state
:type state: :class:`validation.PLAYBACK_STATES`
"""
# The tlid of current playing track. Read-only.
tlid = fields.Integer(min=1)
# The playback position. Read-only.
position = fields.Integer(min=0)
# The playback state. Read-only.
state = fields.Field(choices=validation.PLAYBACK_STATES)
class TracklistState(ValidatedImmutableObject):
"""
State of the tracklist controller.
Internally used for import/export of current state.
:param repeat: the repeat mode
:type repeat: bool
:param consume: the consume mode
:type consume: bool
:param random: the random mode
:type random: bool
:param single: the single mode
:type single: bool
:param next_tlid: the single mode
:type next_tlid: bool
:param tl_tracks: the single mode
:type tl_tracks: list of :class:`TlTrack`
"""
# The repeat mode. Read-only.
repeat = fields.Boolean()
# The consume mode. Read-only.
consume = fields.Boolean()
# The random mode. Read-only.
random = fields.Boolean()
# The single mode. Read-only.
single = fields.Boolean()
# The repeat mode. Read-only.
next_tlid = fields.Integer(min=0)
# The list of tracks. Read-only.
tl_tracks = fields.Collection(type=TlTrack, container=tuple)
class CoreState(ValidatedImmutableObject):
"""
State of all Core controller.
Internally used for import/export of current state.
:param history: State of the history controller
:type history: :class:`HistorState`
:param mixer: State of the mixer controller
:type mixer: :class:`MixerState`
:param playback: State of the playback controller
:type playback: :class:`PlaybackState`
:param tracklist: State of the tracklist controller
:type tracklist: :class:`TracklistState`
"""
# State of the history controller.
history = fields.Field(type=HistoryState)
# State of the mixer controller.
mixer = fields.Field(type=MixerState)
# State of the playback controller.
playback = fields.Field(type=PlaybackState)
# State of the tracklist controller.
tracklist = fields.Field(type=TracklistState)

View File

@ -9,6 +9,7 @@ import sys
import mopidy
from mopidy import compat, local, models
from mopidy import internal
from mopidy.internal import timer
from mopidy.local import search, storage, translator
@ -98,7 +99,7 @@ class JsonLibrary(local.Library):
self._json_file)
self._tracks = {}
else:
library = models.storage.load(self._json_file)
library = internal.storage.load(self._json_file)
self._tracks = dict((t.uri, t) for t in
library.get('tracks', []))
with timer.time_logger('Building browse cache'):
@ -166,9 +167,9 @@ class JsonLibrary(local.Library):
self._tracks.pop(uri, None)
def close(self):
models.storage.save(self._json_file,
{'version': mopidy.__version__,
'tracks': self._tracks.values()})
internal.storage.save(self._json_file,
{'version': mopidy.__version__,
'tracks': self._tracks.values()})
def clear(self):
try:

View File

@ -1,7 +1,6 @@
from __future__ import absolute_import, unicode_literals
from mopidy import compat
from mopidy.internal import validation
from mopidy.models import fields
from mopidy.models.immutable import ImmutableObject, ValidatedImmutableObject
from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
@ -9,8 +8,7 @@ from mopidy.models.serialize import ModelJSONEncoder, model_json_decoder
__all__ = [
'ImmutableObject', 'Ref', 'Image', 'Artist', 'Album', 'track', 'TlTrack',
'Playlist', 'SearchResult', 'model_json_decoder', 'ModelJSONEncoder',
'ValidatedImmutableObject', 'HistoryTrack', 'HistoryState', 'MixerState',
'PlaybackState', 'TracklistState']
'ValidatedImmutableObject']
class Ref(ValidatedImmutableObject):
@ -366,108 +364,3 @@ class SearchResult(ValidatedImmutableObject):
# The albums matching the search query. Read-only.
albums = fields.Collection(type=Album, container=tuple)
class HistoryTrack(ValidatedImmutableObject):
"""
A history track. Wraps a :class:`Ref` and it's timestamp.
:param timestamp: the timestamp
:type timestamp: int
:param track: the track reference
:type track: :class:`Ref`
"""
# The timestamp. Read-only.
timestamp = fields.Integer()
# The track reference. Read-only.
track = fields.Field(type=Ref)
class HistoryState(ValidatedImmutableObject):
"""
State of the history controller.
Internally used for import/export of current state.
:param history: the track history
:type history: list of :class:`HistoryTrack`
"""
# The tracks. Read-only.
history = fields.Collection(type=HistoryTrack, container=tuple)
class MixerState(ValidatedImmutableObject):
"""
State of the mixer controller.
Internally used for import/export of current state.
:param volume: the volume
:type volume: int
"""
# The volume. Read-only.
volume = fields.Integer(min=0, max=100)
class PlaybackState(ValidatedImmutableObject):
"""
State of the playback controller.
Internally used for import/export of current state.
:param tlid: current track tlid
:type tlid: int
:param position: play position
:type position: int
:param state: playback state
:type state: :class:`validation.PLAYBACK_STATES`
"""
# The tlid of current playing track. Read-only.
tlid = fields.Integer(min=1)
# The playback position. Read-only.
position = fields.Integer(min=0)
# The playback state. Read-only.
state = fields.Field(choices=validation.PLAYBACK_STATES)
class TracklistState(ValidatedImmutableObject):
"""
State of the tracklist controller.
Internally used for import/export of current state.
:param repeat: the repeat mode
:type repeat: bool
:param consume: the consume mode
:type consume: bool
:param random: the random mode
:type random: bool
:param single: the single mode
:type single: bool
:param next_tlid: the single mode
:type next_tlid: bool
:param tl_tracks: the single mode
:type tl_tracks: list of :class:`TlTrack`
"""
# The repeat mode. Read-only.
repeat = fields.Boolean()
# The consume mode. Read-only.
consume = fields.Boolean()
# The random mode. Read-only.
random = fields.Boolean()
# The single mode. Read-only.
single = fields.Boolean()
# The repeat mode. Read-only.
next_tlid = fields.Integer(min=0)
# The list of tracks. Read-only.
tl_tracks = fields.Collection(type=TlTrack, container=tuple)

View File

@ -4,9 +4,7 @@ import json
from mopidy.models import immutable
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist',
'HistoryTrack', 'HistoryState', 'MixerState', 'PlaybackState',
'TracklistState']
_MODELS = ['Ref', 'Artist', 'Album', 'Track', 'TlTrack', 'Playlist']
class ModelJSONEncoder(json.JSONEncoder):
@ -46,4 +44,7 @@ def model_json_decoder(dct):
model_name = dct.pop('__model__')
if model_name in _MODELS:
return getattr(models, model_name)(**dct)
from mopidy import internal
if model_name in internal.models._MODELS:
return getattr(internal.models, model_name)(**dct)
return dct

View File

@ -4,7 +4,8 @@ import unittest
from mopidy import compat
from mopidy.core import HistoryController
from mopidy.models import Artist, HistoryState, HistoryTrack, Ref, Track
from mopidy.internal.models import HistoryState, HistoryTrack
from mopidy.models import Artist, Ref, Track
class PlaybackHistoryTest(unittest.TestCase):

View File

@ -7,7 +7,7 @@ import mock
import pykka
from mopidy import core, mixer
from mopidy.models import MixerState
from mopidy.internal.models import MixerState
from tests import dummy_mixer

View File

@ -8,7 +8,8 @@ import pykka
from mopidy import backend, core
from mopidy.internal import deprecation
from mopidy.models import PlaybackState, Track
from mopidy.internal.models import PlaybackState
from mopidy.models import Track
from tests import dummy_audio

View File

@ -6,7 +6,8 @@ import mock
from mopidy import backend, core
from mopidy.internal import deprecation
from mopidy.models import TlTrack, Track, TracklistState
from mopidy.internal.models import TracklistState
from mopidy.models import TlTrack, Track
class TracklistTest(unittest.TestCase):

View File

@ -0,0 +1,200 @@
from __future__ import absolute_import, unicode_literals
import json
import unittest
from mopidy.internal.models import (
HistoryState, HistoryTrack, MixerState, PlaybackState, TracklistState)
from mopidy.models import (
ModelJSONEncoder, Ref, TlTrack, Track, model_json_decoder)
class HistoryTrackTest(unittest.TestCase):
def test_track(self):
track = Ref.track()
result = HistoryTrack(track=track)
self.assertEqual(result.track, track)
with self.assertRaises(AttributeError):
result.track = None
def test_timestamp(self):
timestamp = 1234
result = HistoryTrack(timestamp=timestamp)
self.assertEqual(result.timestamp, timestamp)
with self.assertRaises(AttributeError):
result.timestamp = None
def test_to_json_and_back(self):
result = HistoryTrack(track=Ref.track(), timestamp=1234)
serialized = json.dumps(result, cls=ModelJSONEncoder)
deserialized = json.loads(serialized, object_hook=model_json_decoder)
self.assertEqual(result, deserialized)
class HistoryStateTest(unittest.TestCase):
def test_history_list(self):
history = (HistoryTrack(),
HistoryTrack())
result = HistoryState(history=history)
self.assertEqual(result.history, history)
with self.assertRaises(AttributeError):
result.history = None
def test_history_string_fail(self):
history = 'not_a_valid_history'
with self.assertRaises(TypeError):
HistoryState(history=history)
def test_to_json_and_back(self):
result = HistoryState(history=(HistoryTrack(), HistoryTrack()))
serialized = json.dumps(result, cls=ModelJSONEncoder)
deserialized = json.loads(serialized, object_hook=model_json_decoder)
self.assertEqual(result, deserialized)
class MixerStateTest(unittest.TestCase):
def test_volume(self):
volume = 37
result = MixerState(volume=volume)
self.assertEqual(result.volume, volume)
with self.assertRaises(AttributeError):
result.volume = None
def test_volume_invalid(self):
volume = 105
with self.assertRaises(ValueError):
MixerState(volume=volume)
def test_to_json_and_back(self):
result = MixerState(volume=77)
serialized = json.dumps(result, cls=ModelJSONEncoder)
deserialized = json.loads(serialized, object_hook=model_json_decoder)
self.assertEqual(result, deserialized)
class PlaybackStateTest(unittest.TestCase):
def test_position(self):
position = 123456
result = PlaybackState(position=position)
self.assertEqual(result.position, position)
with self.assertRaises(AttributeError):
result.position = None
def test_position_invalid(self):
position = -1
with self.assertRaises(ValueError):
PlaybackState(position=position)
def test_tl_track(self):
tlid = 42
result = PlaybackState(tlid=tlid)
self.assertEqual(result.tlid, tlid)
with self.assertRaises(AttributeError):
result.tlid = None
def test_tl_track_none(self):
tlid = None
result = PlaybackState(tlid=tlid)
self.assertEqual(result.tlid, tlid)
with self.assertRaises(AttributeError):
result.tl_track = None
def test_tl_track_invalid(self):
tl_track = Track()
with self.assertRaises(TypeError):
PlaybackState(tlid=tl_track)
def test_state(self):
state = 'playing'
result = PlaybackState(state=state)
self.assertEqual(result.state, state)
with self.assertRaises(AttributeError):
result.state = None
def test_state_invalid(self):
state = 'not_a_state'
with self.assertRaises(TypeError):
PlaybackState(state=state)
def test_to_json_and_back(self):
result = PlaybackState(state='playing', tlid=4321)
serialized = json.dumps(result, cls=ModelJSONEncoder)
deserialized = json.loads(serialized, object_hook=model_json_decoder)
self.assertEqual(result, deserialized)
class TracklistStateTest(unittest.TestCase):
def test_repeat_true(self):
repeat = True
result = TracklistState(repeat=repeat)
self.assertEqual(result.repeat, repeat)
with self.assertRaises(AttributeError):
result.repeat = None
def test_repeat_false(self):
repeat = False
result = TracklistState(repeat=repeat)
self.assertEqual(result.repeat, repeat)
with self.assertRaises(AttributeError):
result.repeat = None
def test_repeat_invalid(self):
repeat = 33
with self.assertRaises(TypeError):
TracklistState(repeat=repeat)
def test_consume_true(self):
val = True
result = TracklistState(consume=val)
self.assertEqual(result.consume, val)
with self.assertRaises(AttributeError):
result.repeat = None
def test_random_true(self):
val = True
result = TracklistState(random=val)
self.assertEqual(result.random, val)
with self.assertRaises(AttributeError):
result.random = None
def test_single_true(self):
val = True
result = TracklistState(single=val)
self.assertEqual(result.single, val)
with self.assertRaises(AttributeError):
result.single = None
def test_next_tlid(self):
val = 654
result = TracklistState(next_tlid=val)
self.assertEqual(result.next_tlid, val)
with self.assertRaises(AttributeError):
result.next_tlid = None
def test_next_tlid_invalid(self):
val = -1
with self.assertRaises(ValueError):
TracklistState(next_tlid=val)
def test_tracks(self):
tracks = (TlTrack(), TlTrack())
result = TracklistState(tl_tracks=tracks)
self.assertEqual(result.tl_tracks, tracks)
with self.assertRaises(AttributeError):
result.tl_tracks = None
def test_tracks_invalid(self):
tracks = (Track(), Track())
with self.assertRaises(TypeError):
TracklistState(tl_tracks=tracks)
def test_to_json_and_back(self):
result = TracklistState(tl_tracks=(TlTrack(), TlTrack()), next_tlid=4)
serialized = json.dumps(result, cls=ModelJSONEncoder)
deserialized = json.loads(serialized, object_hook=model_json_decoder)
self.assertEqual(result, deserialized)

View File

@ -4,9 +4,8 @@ import json
import unittest
from mopidy.models import (
Album, Artist, HistoryState, HistoryTrack, Image, MixerState,
ModelJSONEncoder, PlaybackState, Playlist,
Ref, SearchResult, TlTrack, Track, TracklistState, model_json_decoder)
Album, Artist, Image, ModelJSONEncoder, Playlist,
Ref, SearchResult, TlTrack, Track, model_json_decoder)
class InheritanceTest(unittest.TestCase):
@ -1169,164 +1168,3 @@ class SearchResultTest(unittest.TestCase):
self.assertDictEqual(
{'__model__': 'SearchResult', 'uri': 'uri'},
SearchResult(uri='uri').serialize())
class HistoryTrackTest(unittest.TestCase):
def test_track(self):
track = Ref.track()
result = HistoryTrack(track=track)
self.assertEqual(result.track, track)
with self.assertRaises(AttributeError):
result.track = None
def test_timestamp(self):
timestamp = 1234
result = HistoryTrack(timestamp=timestamp)
self.assertEqual(result.timestamp, timestamp)
with self.assertRaises(AttributeError):
result.timestamp = None
class HistoryStateTest(unittest.TestCase):
def test_history_list(self):
history = (HistoryTrack(),
HistoryTrack())
result = HistoryState(history=history)
self.assertEqual(result.history, history)
with self.assertRaises(AttributeError):
result.history = None
def test_history_string_fail(self):
history = 'not_a_valid_history'
with self.assertRaises(TypeError):
HistoryState(history=history)
class MixerStateTest(unittest.TestCase):
def test_volume(self):
volume = 37
result = MixerState(volume=volume)
self.assertEqual(result.volume, volume)
with self.assertRaises(AttributeError):
result.volume = None
def test_volume_invalid(self):
volume = 105
with self.assertRaises(ValueError):
MixerState(volume=volume)
class PlaybackStateTest(unittest.TestCase):
def test_position(self):
position = 123456
result = PlaybackState(position=position)
self.assertEqual(result.position, position)
with self.assertRaises(AttributeError):
result.position = None
def test_position_invalid(self):
position = -1
with self.assertRaises(ValueError):
PlaybackState(position=position)
def test_tl_track(self):
tl_track = TlTrack()
result = PlaybackState(tl_track=tl_track)
self.assertEqual(result.tl_track, tl_track)
with self.assertRaises(AttributeError):
result.tl_track = None
def test_tl_track_none(self):
tl_track = None
result = PlaybackState(tl_track=tl_track)
self.assertEqual(result.tl_track, tl_track)
with self.assertRaises(AttributeError):
result.tl_track = None
def test_tl_track_invalid(self):
tl_track = Track()
with self.assertRaises(TypeError):
PlaybackState(tl_track=tl_track)
def test_state(self):
state = 'playing'
result = PlaybackState(state=state)
self.assertEqual(result.state, state)
with self.assertRaises(AttributeError):
result.state = None
def test_state_invalid(self):
state = 'not_a_state'
with self.assertRaises(TypeError):
PlaybackState(state=state)
class TracklistStateTest(unittest.TestCase):
def test_repeat_true(self):
repeat = True
result = TracklistState(repeat=repeat)
self.assertEqual(result.repeat, repeat)
with self.assertRaises(AttributeError):
result.repeat = None
def test_repeat_false(self):
repeat = False
result = TracklistState(repeat=repeat)
self.assertEqual(result.repeat, repeat)
with self.assertRaises(AttributeError):
result.repeat = None
def test_repeat_invalid(self):
repeat = 33
with self.assertRaises(TypeError):
TracklistState(repeat=repeat)
def test_consume_true(self):
val = True
result = TracklistState(consume=val)
self.assertEqual(result.consume, val)
with self.assertRaises(AttributeError):
result.repeat = None
def test_random_true(self):
val = True
result = TracklistState(random=val)
self.assertEqual(result.random, val)
with self.assertRaises(AttributeError):
result.random = None
def test_single_true(self):
val = True
result = TracklistState(single=val)
self.assertEqual(result.single, val)
with self.assertRaises(AttributeError):
result.single = None
def test_next_tlid(self):
val = 654
result = TracklistState(next_tlid=val)
self.assertEqual(result.next_tlid, val)
with self.assertRaises(AttributeError):
result.next_tlid = None
def test_next_tlid_invalid(self):
val = -1
with self.assertRaises(ValueError):
TracklistState(next_tlid=val)
def test_tracks(self):
tracks = (TlTrack(), TlTrack())
result = TracklistState(tl_tracks=tracks)
self.assertEqual(result.tl_tracks, tracks)
with self.assertRaises(AttributeError):
result.tl_tracks = None
def test_tracks_invalid(self):
tracks = (Track(), Track())
with self.assertRaises(TypeError):
TracklistState(tl_tracks=tracks)