Merge pull request #637 from jodal/feature/move-backend-api

Move backend API to mopidy.backend
This commit is contained in:
Thomas Adamcik 2014-01-11 10:40:04 -08:00
commit 719c67341b
21 changed files with 395 additions and 355 deletions

View File

@ -4,46 +4,46 @@
Backend API
***********
.. module:: mopidy.backends.base
.. module:: mopidy.backend
:synopsis: The API implemented by backends
The backend API is the interface that must be implemented when you create a
backend. If you are working on a frontend and need to access the backend, see
the :ref:`core-api`.
backend. If you are working on a frontend and need to access the backends, see
the :ref:`core-api` instead.
Backend class
=============
.. autoclass:: mopidy.backends.base.Backend
.. autoclass:: mopidy.backend.Backend
:members:
Playback provider
=================
.. autoclass:: mopidy.backends.base.BasePlaybackProvider
.. autoclass:: mopidy.backend.PlaybackProvider
:members:
Playlists provider
==================
.. autoclass:: mopidy.backends.base.BasePlaylistsProvider
.. autoclass:: mopidy.backend.PlaylistsProvider
:members:
Library provider
================
.. autoclass:: mopidy.backends.base.BaseLibraryProvider
.. autoclass:: mopidy.backend.LibraryProvider
:members:
Backend listener
================
.. autoclass:: mopidy.backends.listener.BackendListener
.. autoclass:: mopidy.backend.BackendListener
:members:

View File

@ -20,6 +20,28 @@ v0.18.0 (UNRELEASED)
virtual file system of tracks. Backends can implement support for this by
implementing :meth:`mopidy.backends.base.BaseLibraryController.browse`.
**Backend API**
- Move the backend API classes from :mod:`mopidy.backends.base` to
:mod:`mopidy.backend` and remove the ``Base`` prefix from the class names:
- From :class:`mopidy.backends.base.Backend`
to :class:`mopidy.backend.Backend`
- From :class:`mopidy.backends.base.BaseLibraryProvider`
to :class:`mopidy.backend.LibraryProvider`
- From :class:`mopidy.backends.base.BasePlaybackProvider`
to :class:`mopidy.backend.PlaybackProvider`
- From :class:`mopidy.backends.base.BasePlaylistsProvider`
to :class:`mopidy.backend.PlaylistsProvider`
- From :class:`mopidy.backends.listener.BackendListener`
to :class:`mopidy.backend.BackendListener`
Imports from the old locations still works, but are deprecated.
**Configuration**
- The default for the :option:`mopidy --config` option has been updated to

View File

@ -344,10 +344,10 @@ passed a reference to the core API when it's created. See the
import pykka
from mopidy.core import CoreListener
from mopidy import core
class SoundspotFrontend(pykka.ThreadingActor, CoreListener):
class SoundspotFrontend(pykka.ThreadingActor, core.CoreListener):
def __init__(self, core):
super(SoundspotFrontend, self).__init__()
self.core = core
@ -370,10 +370,10 @@ details.
import pykka
from mopidy.backends import base
from mopidy import backend
class SoundspotBackend(pykka.ThreadingActor, base.BaseBackend):
class SoundspotBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, audio):
super(SoundspotBackend, self).__init__()
self.audio = audio

294
mopidy/backend.py Normal file
View File

@ -0,0 +1,294 @@
from __future__ import unicode_literals
import copy
from mopidy import listener
class Backend(object):
#: Actor proxy to an instance of :class:`mopidy.audio.Audio`.
#:
#: Should be passed to the backend constructor as the kwarg ``audio``,
#: which will then set this field.
audio = None
#: The library provider. An instance of
#: :class:`~mopidy.backend.LibraryProvider`, or :class:`None` if
#: the backend doesn't provide a library.
library = None
#: The playback provider. An instance of
#: :class:`~mopidy.backend.PlaybackProvider`, or :class:`None` if
#: the backend doesn't provide playback.
playback = None
#: The playlists provider. An instance of
#: :class:`~mopidy.backend.PlaylistsProvider`, or class:`None` if
#: the backend doesn't provide playlists.
playlists = None
#: List of URI schemes this backend can handle.
uri_schemes = []
# Because the providers is marked as pykka_traversible, we can't get() them
# from another actor, and need helper methods to check if the providers are
# set or None.
def has_library(self):
return self.library is not None
def has_playback(self):
return self.playback is not None
def has_playlists(self):
return self.playlists is not None
class LibraryProvider(object):
"""
:param backend: backend the controller is a part of
:type backend: :class:`mopidy.backend.Backend`
"""
pykka_traversable = True
root_directory_name = None
"""
Name of the library's root directory in Mopidy's virtual file system.
*MUST be set by any class that implements :meth:`browse`.*
"""
def __init__(self, backend):
self.backend = backend
def browse(self, path):
"""
See :meth:`mopidy.core.LibraryController.browse`.
If you implement this method, make sure to also set
:attr:`root_directory_name`.
*MAY be implemented by subclass.*
"""
return []
# TODO: replace with search(query, exact=True, ...)
def find_exact(self, query=None, uris=None):
"""
See :meth:`mopidy.core.LibraryController.find_exact`.
*MAY be implemented by subclass.*
"""
pass
def lookup(self, uri):
"""
See :meth:`mopidy.core.LibraryController.lookup`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def refresh(self, uri=None):
"""
See :meth:`mopidy.core.LibraryController.refresh`.
*MAY be implemented by subclass.*
"""
pass
def search(self, query=None, uris=None):
"""
See :meth:`mopidy.core.LibraryController.search`.
*MAY be implemented by subclass.*
"""
pass
class PlaybackProvider(object):
"""
:param audio: the audio actor
:type audio: actor proxy to an instance of :class:`mopidy.audio.Audio`
:param backend: the backend
:type backend: :class:`mopidy.backend.Backend`
"""
pykka_traversable = True
def __init__(self, audio, backend):
self.audio = audio
self.backend = backend
def pause(self):
"""
Pause playback.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.pause_playback().get()
def play(self, track):
"""
Play given track.
*MAY be reimplemented by subclass.*
:param track: the track to play
:type track: :class:`mopidy.models.Track`
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.prepare_change()
self.change_track(track)
return self.audio.start_playback().get()
def change_track(self, track):
"""
Swith to provided track.
*MAY be reimplemented by subclass.*
:param track: the track to play
:type track: :class:`mopidy.models.Track`
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.set_uri(track.uri).get()
return True
def resume(self):
"""
Resume playback at the same time position playback was paused.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.start_playback().get()
def seek(self, time_position):
"""
Seek to a given time position.
*MAY be reimplemented by subclass.*
:param time_position: time position in milliseconds
:type time_position: int
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.set_position(time_position).get()
def stop(self):
"""
Stop playback.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.stop_playback().get()
def get_time_position(self):
"""
Get the current time position in milliseconds.
*MAY be reimplemented by subclass.*
:rtype: int
"""
return self.audio.get_position().get()
class PlaylistsProvider(object):
"""
:param backend: backend the controller is a part of
:type backend: :class:`mopidy.backend.Backend` instance
"""
pykka_traversable = True
def __init__(self, backend):
self.backend = backend
self._playlists = []
@property
def playlists(self):
"""
Currently available playlists.
Read/write. List of :class:`mopidy.models.Playlist`.
"""
return copy.copy(self._playlists)
@playlists.setter # noqa
def playlists(self, playlists):
self._playlists = playlists
def create(self, name):
"""
See :meth:`mopidy.core.PlaylistsController.create`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def delete(self, uri):
"""
See :meth:`mopidy.core.PlaylistsController.delete`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def lookup(self, uri):
"""
See :meth:`mopidy.core.PlaylistsController.lookup`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def refresh(self):
"""
See :meth:`mopidy.core.PlaylistsController.refresh`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def save(self, playlist):
"""
See :meth:`mopidy.core.PlaylistsController.save`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
class BackendListener(listener.Listener):
"""
Marker interface for recipients of events sent by the backend actors.
Any Pykka actor that mixes in this class will receive calls to the methods
defined here when the corresponding events happen in the core actor. This
interface is used both for looking up what actors to notify of the events,
and for providing default implementations for those listeners that are not
interested in all events.
Normally, only the Core actor should mix in this class.
"""
@staticmethod
def send(event, **kwargs):
"""Helper to allow calling of backend listener events"""
listener.send_async(BackendListener, event, **kwargs)
def playlists_loaded(self):
"""
Called when playlists are loaded or refreshed.
*MAY* be implemented by actor.
"""
pass

View File

@ -1,265 +1,17 @@
from __future__ import unicode_literals
import copy
class Backend(object):
#: Actor proxy to an instance of :class:`mopidy.audio.Audio`.
#:
#: Should be passed to the backend constructor as the kwarg ``audio``,
#: which will then set this field.
audio = None
#: The library provider. An instance of
#: :class:`~mopidy.backends.base.BaseLibraryProvider`, or :class:`None` if
#: the backend doesn't provide a library.
library = None
#: The playback provider. An instance of
#: :class:`~mopidy.backends.base.BasePlaybackProvider`, or :class:`None` if
#: the backend doesn't provide playback.
playback = None
#: The playlists provider. An instance of
#: :class:`~mopidy.backends.base.BasePlaylistsProvider`, or class:`None` if
#: the backend doesn't provide playlists.
playlists = None
#: List of URI schemes this backend can handle.
uri_schemes = []
# Because the providers is marked as pykka_traversible, we can't get() them
# from another actor, and need helper methods to check if the providers are
# set or None.
def has_library(self):
return self.library is not None
def has_playback(self):
return self.playback is not None
def has_playlists(self):
return self.playlists is not None
class BaseLibraryProvider(object):
"""
:param backend: backend the controller is a part of
:type backend: :class:`mopidy.backends.base.Backend`
"""
pykka_traversable = True
root_directory_name = None
"""
Name of the library's root directory in Mopidy's virtual file system.
*MUST be set by any class that implements :meth:`browse`.*
"""
def __init__(self, backend):
self.backend = backend
def browse(self, path):
"""
See :meth:`mopidy.core.LibraryController.browse`.
If you implement this method, make sure to also set
:attr:`root_directory_name`.
*MAY be implemented by subclass.*
"""
return []
# TODO: replace with search(query, exact=True, ...)
def find_exact(self, query=None, uris=None):
"""
See :meth:`mopidy.core.LibraryController.find_exact`.
*MAY be implemented by subclass.*
"""
pass
def lookup(self, uri):
"""
See :meth:`mopidy.core.LibraryController.lookup`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def refresh(self, uri=None):
"""
See :meth:`mopidy.core.LibraryController.refresh`.
*MAY be implemented by subclass.*
"""
pass
def search(self, query=None, uris=None):
"""
See :meth:`mopidy.core.LibraryController.search`.
*MAY be implemented by subclass.*
"""
pass
class BasePlaybackProvider(object):
"""
:param audio: the audio actor
:type audio: actor proxy to an instance of :class:`mopidy.audio.Audio`
:param backend: the backend
:type backend: :class:`mopidy.backends.base.Backend`
"""
pykka_traversable = True
def __init__(self, audio, backend):
self.audio = audio
self.backend = backend
def pause(self):
"""
Pause playback.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.pause_playback().get()
def play(self, track):
"""
Play given track.
*MAY be reimplemented by subclass.*
:param track: the track to play
:type track: :class:`mopidy.models.Track`
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.prepare_change()
self.change_track(track)
return self.audio.start_playback().get()
def change_track(self, track):
"""
Swith to provided track.
*MAY be reimplemented by subclass.*
:param track: the track to play
:type track: :class:`mopidy.models.Track`
:rtype: :class:`True` if successful, else :class:`False`
"""
self.audio.set_uri(track.uri).get()
return True
def resume(self):
"""
Resume playback at the same time position playback was paused.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.start_playback().get()
def seek(self, time_position):
"""
Seek to a given time position.
*MAY be reimplemented by subclass.*
:param time_position: time position in milliseconds
:type time_position: int
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.set_position(time_position).get()
def stop(self):
"""
Stop playback.
*MAY be reimplemented by subclass.*
:rtype: :class:`True` if successful, else :class:`False`
"""
return self.audio.stop_playback().get()
def get_time_position(self):
"""
Get the current time position in milliseconds.
*MAY be reimplemented by subclass.*
:rtype: int
"""
return self.audio.get_position().get()
class BasePlaylistsProvider(object):
"""
:param backend: backend the controller is a part of
:type backend: :class:`mopidy.backends.base.Backend`
"""
pykka_traversable = True
def __init__(self, backend):
self.backend = backend
self._playlists = []
@property
def playlists(self):
"""
Currently available playlists.
Read/write. List of :class:`mopidy.models.Playlist`.
"""
return copy.copy(self._playlists)
@playlists.setter # noqa
def playlists(self, playlists):
self._playlists = playlists
def create(self, name):
"""
See :meth:`mopidy.core.PlaylistsController.create`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def delete(self, uri):
"""
See :meth:`mopidy.core.PlaylistsController.delete`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def lookup(self, uri):
"""
See :meth:`mopidy.core.PlaylistsController.lookup`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def refresh(self):
"""
See :meth:`mopidy.core.PlaylistsController.refresh`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def save(self, playlist):
"""
See :meth:`mopidy.core.PlaylistsController.save`.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
from mopidy.backend import (
Backend,
LibraryProvider as BaseLibraryProvider,
PlaybackProvider as BasePlaybackProvider,
PlaylistsProvider as BasePlaylistsProvider)
# Make classes previously residing here available in the old location for
# backwards compatibility with extensions targeting Mopidy < 0.18.
__all__ = [
'Backend',
'BaseLibraryProvider',
'BasePlaybackProvider',
'BasePlaylistsProvider',
]

View File

@ -1,30 +1,8 @@
from __future__ import unicode_literals
from mopidy import listener
from mopidy.backend import BackendListener
class BackendListener(listener.Listener):
"""
Marker interface for recipients of events sent by the backend actors.
Any Pykka actor that mixes in this class will receive calls to the methods
defined here when the corresponding events happen in the core actor. This
interface is used both for looking up what actors to notify of the events,
and for providing default implementations for those listeners that are not
interested in all events.
Normally, only the Core actor should mix in this class.
"""
@staticmethod
def send(event, **kwargs):
"""Helper to allow calling of backend listener events"""
listener.send_async(BackendListener, event, **kwargs)
def playlists_loaded(self):
"""
Called when playlists are loaded or refreshed.
*MAY* be implemented by actor.
"""
pass
# Make classes previously residing here available in the old location for
# backwards compatibility with extensions targeting Mopidy < 0.18.
__all__ = ['BackendListener']

View File

@ -5,8 +5,8 @@ import itertools
import pykka
from mopidy.audio import AudioListener, PlaybackState
from mopidy.backends.listener import BackendListener
from mopidy import audio, backend
from mopidy.audio import PlaybackState
from mopidy.utils import versioning
from .library import LibraryController
@ -16,7 +16,7 @@ from .playlists import PlaylistsController
from .tracklist import TracklistController
class Core(pykka.ThreadingActor, AudioListener, BackendListener):
class Core(pykka.ThreadingActor, audio.AudioListener, backend.BackendListener):
library = None
"""The library controller. An instance of
:class:`mopidy.core.LibraryController`."""

View File

@ -123,7 +123,7 @@ class Extension(object):
.. deprecated:: 0.18
Use :meth:`setup` instead.
:returns: list of :class:`~mopidy.backends.base.Backend` subclasses
:returns: list of :class:`~mopidy.backend.Backend` subclasses
"""
return []

View File

@ -5,7 +5,7 @@ import os
import pykka
from mopidy.backends import base
from mopidy import backend
from mopidy.utils import encoding, path
from .library import LocalLibraryProvider
@ -15,7 +15,7 @@ from .playlists import LocalPlaylistsProvider
logger = logging.getLogger(__name__)
class LocalBackend(pykka.ThreadingActor, base.Backend):
class LocalBackend(pykka.ThreadingActor, backend.Backend):
uri_schemes = ['local']
libraries = []

View File

@ -2,12 +2,12 @@ from __future__ import unicode_literals
import logging
from mopidy.backends import base
from mopidy import backend
logger = logging.getLogger(__name__)
class LocalLibraryProvider(base.BaseLibraryProvider):
class LocalLibraryProvider(backend.LibraryProvider):
"""Proxy library that delegates work to our active local library."""
root_directory_name = 'local'

View File

@ -2,14 +2,14 @@ from __future__ import unicode_literals
import logging
from mopidy.backends import base
from mopidy import backend
from . import translator
logger = logging.getLogger(__name__)
class LocalPlaybackProvider(base.BasePlaybackProvider):
class LocalPlaybackProvider(backend.PlaybackProvider):
def change_track(self, track):
track = track.copy(uri=translator.local_track_uri_to_file_uri(
track.uri, self.backend.config['local']['media_dir']))

View File

@ -5,7 +5,7 @@ import logging
import os
import shutil
from mopidy.backends import base, listener
from mopidy import backend
from mopidy.models import Playlist
from mopidy.utils import formatting, path
@ -15,7 +15,7 @@ from .translator import parse_m3u
logger = logging.getLogger(__name__)
class LocalPlaylistsProvider(base.BasePlaylistsProvider):
class LocalPlaylistsProvider(backend.PlaylistsProvider):
def __init__(self, *args, **kwargs):
super(LocalPlaylistsProvider, self).__init__(*args, **kwargs)
self._media_dir = self.backend.config['local']['media_dir']
@ -58,7 +58,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
self.playlists = playlists
# TODO: send what scheme we loaded them for?
listener.BackendListener.send('playlists_loaded')
backend.BackendListener.send('playlists_loaded')
logger.info(
'Loaded %d local playlists from %s',

View File

@ -5,28 +5,27 @@ import urlparse
import pykka
from mopidy import audio as audio_lib, exceptions
from mopidy import audio as audio_lib, backend, exceptions
from mopidy.audio import scan
from mopidy.backends import base
from mopidy.models import Track
logger = logging.getLogger(__name__)
class StreamBackend(pykka.ThreadingActor, base.Backend):
class StreamBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, config, audio):
super(StreamBackend, self).__init__()
self.library = StreamLibraryProvider(
backend=self, timeout=config['stream']['timeout'])
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playback = backend.PlaybackProvider(audio=audio, backend=self)
self.playlists = None
self.uri_schemes = audio_lib.supported_uri_schemes(
config['stream']['protocols'])
class StreamLibraryProvider(base.BaseLibraryProvider):
class StreamLibraryProvider(backend.LibraryProvider):
def __init__(self, backend, timeout):
super(StreamLibraryProvider, self).__init__(backend)
self._scanner = scan.Scanner(min_duration=None, timeout=timeout)

View File

@ -3,12 +3,12 @@ from __future__ import unicode_literals
import mock
import unittest
from mopidy.backends.listener import BackendListener
from mopidy import backend
class BackendListenerTest(unittest.TestCase):
def setUp(self):
self.listener = BackendListener()
self.listener = backend.BackendListener()
def test_on_event_forwards_to_specific_handler(self):
self.listener.playlists_loaded = mock.Mock()

View File

@ -3,8 +3,7 @@ from __future__ import unicode_literals
import mock
import unittest
from mopidy.backends import base
from mopidy.core import Core
from mopidy import backend, core
from mopidy.models import Ref, SearchResult, Track
@ -12,13 +11,13 @@ class CoreLibraryTest(unittest.TestCase):
def setUp(self):
self.backend1 = mock.Mock()
self.backend1.uri_schemes.get.return_value = ['dummy1']
self.library1 = mock.Mock(spec=base.BaseLibraryProvider)
self.library1 = mock.Mock(spec=backend.LibraryProvider)
self.library1.root_directory_name.get.return_value = 'dummy1'
self.backend1.library = self.library1
self.backend2 = mock.Mock()
self.backend2.uri_schemes.get.return_value = ['dummy2']
self.library2 = mock.Mock(spec=base.BaseLibraryProvider)
self.library2 = mock.Mock(spec=backend.LibraryProvider)
self.library2.root_directory_name.get.return_value = 'dummy2'
self.backend2.library = self.library2
@ -27,7 +26,7 @@ class CoreLibraryTest(unittest.TestCase):
self.backend3.uri_schemes.get.return_value = ['dummy3']
self.backend3.has_library().get.return_value = False
self.core = Core(audio=None, backends=[
self.core = core.Core(audio=None, backends=[
self.backend1, self.backend2, self.backend3])
def test_browse_root_returns_dir_ref_for_each_lib_with_root_dir_name(self):

View File

@ -3,8 +3,7 @@ from __future__ import unicode_literals
import mock
import unittest
from mopidy.backends import base
from mopidy.core import Core, PlaybackState
from mopidy import backend, core
from mopidy.models import Track
@ -12,12 +11,12 @@ class CorePlaybackTest(unittest.TestCase):
def setUp(self):
self.backend1 = mock.Mock()
self.backend1.uri_schemes.get.return_value = ['dummy1']
self.playback1 = mock.Mock(spec=base.BasePlaybackProvider)
self.playback1 = mock.Mock(spec=backend.PlaybackProvider)
self.backend1.playback = self.playback1
self.backend2 = mock.Mock()
self.backend2.uri_schemes.get.return_value = ['dummy2']
self.playback2 = mock.Mock(spec=base.BasePlaybackProvider)
self.playback2 = mock.Mock(spec=backend.PlaybackProvider)
self.backend2.playback = self.playback2
# A backend without the optional playback provider
@ -32,7 +31,7 @@ class CorePlaybackTest(unittest.TestCase):
Track(uri='dummy1:b', length=40000),
]
self.core = Core(audio=None, backends=[
self.core = core.Core(audio=None, backends=[
self.backend1, self.backend2, self.backend3])
self.core.tracklist.add(self.tracks)
@ -78,7 +77,7 @@ class CorePlaybackTest(unittest.TestCase):
self.core.playback.current_tl_track = self.unplayable_tl_track
self.core.playback.pause()
self.assertEqual(self.core.playback.state, PlaybackState.PAUSED)
self.assertEqual(self.core.playback.state, core.PlaybackState.PAUSED)
self.assertFalse(self.playback1.pause.called)
self.assertFalse(self.playback2.pause.called)
@ -100,10 +99,10 @@ class CorePlaybackTest(unittest.TestCase):
def test_resume_does_nothing_if_track_is_unplayable(self):
self.core.playback.current_tl_track = self.unplayable_tl_track
self.core.playback.state = PlaybackState.PAUSED
self.core.playback.state = core.PlaybackState.PAUSED
self.core.playback.resume()
self.assertEqual(self.core.playback.state, PlaybackState.PAUSED)
self.assertEqual(self.core.playback.state, core.PlaybackState.PAUSED)
self.assertFalse(self.playback1.resume.called)
self.assertFalse(self.playback2.resume.called)
@ -123,10 +122,10 @@ class CorePlaybackTest(unittest.TestCase):
def test_stop_changes_state_even_if_track_is_unplayable(self):
self.core.playback.current_tl_track = self.unplayable_tl_track
self.core.playback.state = PlaybackState.PAUSED
self.core.playback.state = core.PlaybackState.PAUSED
self.core.playback.stop()
self.assertEqual(self.core.playback.state, PlaybackState.STOPPED)
self.assertEqual(self.core.playback.state, core.PlaybackState.STOPPED)
self.assertFalse(self.playback1.stop.called)
self.assertFalse(self.playback2.stop.called)
@ -146,7 +145,7 @@ class CorePlaybackTest(unittest.TestCase):
def test_seek_fails_for_unplayable_track(self):
self.core.playback.current_tl_track = self.unplayable_tl_track
self.core.playback.state = PlaybackState.PLAYING
self.core.playback.state = core.PlaybackState.PLAYING
success = self.core.playback.seek(1000)
self.assertFalse(success)

View File

@ -3,8 +3,7 @@ from __future__ import unicode_literals
import mock
import unittest
from mopidy.backends import base
from mopidy.core import Core
from mopidy import backend, core
from mopidy.models import Playlist, Track
@ -12,12 +11,12 @@ class PlaylistsTest(unittest.TestCase):
def setUp(self):
self.backend1 = mock.Mock()
self.backend1.uri_schemes.get.return_value = ['dummy1']
self.sp1 = mock.Mock(spec=base.BasePlaylistsProvider)
self.sp1 = mock.Mock(spec=backend.PlaylistsProvider)
self.backend1.playlists = self.sp1
self.backend2 = mock.Mock()
self.backend2.uri_schemes.get.return_value = ['dummy2']
self.sp2 = mock.Mock(spec=base.BasePlaylistsProvider)
self.sp2 = mock.Mock(spec=backend.PlaylistsProvider)
self.backend2.playlists = self.sp2
# A backend without the optional playlists provider
@ -34,7 +33,7 @@ class PlaylistsTest(unittest.TestCase):
self.pl2b = Playlist(name='B', tracks=[Track(uri='dummy2:b')])
self.sp2.playlists.get.return_value = [self.pl2a, self.pl2b]
self.core = Core(audio=None, backends=[
self.core = core.Core(audio=None, backends=[
self.backend3, self.backend1, self.backend2])
def test_get_playlists_combines_result_from_backends(self):

View File

@ -3,8 +3,7 @@ from __future__ import unicode_literals
import mock
import unittest
from mopidy.backends import base
from mopidy.core import Core
from mopidy import backend, core
from mopidy.models import Track
@ -18,10 +17,10 @@ class TracklistTest(unittest.TestCase):
self.backend = mock.Mock()
self.backend.uri_schemes.get.return_value = ['dummy1']
self.library = mock.Mock(spec=base.BaseLibraryProvider)
self.library = mock.Mock(spec=backend.LibraryProvider)
self.backend.library = self.library
self.core = Core(audio=None, backends=[self.backend])
self.core = core.Core(audio=None, backends=[self.backend])
self.tl_tracks = self.core.tracklist.add(self.tracks)
def test_add_by_uri_looks_up_uri_in_library(self):
@ -72,4 +71,4 @@ class TracklistTest(unittest.TestCase):
def test_filter_fails_if_values_is_a_string(self):
self.assertRaises(ValueError, self.core.tracklist.filter, uri='a')
# TODO Extract tracklist tests from the base backend tests
# TODO Extract tracklist tests from the local backend tests

View File

@ -8,7 +8,7 @@ from __future__ import unicode_literals
import pykka
from mopidy.backends import base
from mopidy import backend
from mopidy.models import Playlist, SearchResult
@ -16,7 +16,7 @@ def create_dummy_backend_proxy(config=None, audio=None):
return DummyBackend.start(config=config, audio=audio).proxy()
class DummyBackend(pykka.ThreadingActor, base.Backend):
class DummyBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, config, audio):
super(DummyBackend, self).__init__()
@ -27,7 +27,7 @@ class DummyBackend(pykka.ThreadingActor, base.Backend):
self.uri_schemes = ['dummy']
class DummyLibraryProvider(base.BaseLibraryProvider):
class DummyLibraryProvider(backend.LibraryProvider):
root_directory_name = 'dummy'
def __init__(self, *args, **kwargs):
@ -53,7 +53,7 @@ class DummyLibraryProvider(base.BaseLibraryProvider):
return self.dummy_search_result
class DummyPlaybackProvider(base.BasePlaybackProvider):
class DummyPlaybackProvider(backend.PlaybackProvider):
def __init__(self, *args, **kwargs):
super(DummyPlaybackProvider, self).__init__(*args, **kwargs)
self._time_position = 0
@ -80,7 +80,7 @@ class DummyPlaybackProvider(base.BasePlaybackProvider):
return self._time_position
class DummyPlaylistsProvider(base.BasePlaylistsProvider):
class DummyPlaylistsProvider(backend.PlaylistsProvider):
def create(self, name):
playlist = Playlist(name=name, uri='dummy:%s' % name)
self._playlists.append(playlist)

View File

@ -5,14 +5,13 @@ import unittest
import mock
import pykka
from mopidy import audio, core
from mopidy.backends import listener
from mopidy import audio, backend, core
from mopidy.local import actor
from tests import path_to_data_dir
@mock.patch.object(listener.BackendListener, 'send')
@mock.patch.object(backend.BackendListener, 'send')
class LocalBackendEventsTest(unittest.TestCase):
config = {
'local': {