Source code for mopidy.ext

from __future__ import unicode_literals

import collections
import logging

import pkg_resources

from mopidy import config as config_lib, exceptions


logger = logging.getLogger(__name__)


[docs]class Extension(object): """Base class for Mopidy extensions""" dist_name = None """The extension's distribution name, as registered on PyPI Example: ``Mopidy-Soundspot`` """ ext_name = None """The extension's short name, as used in setup.py and as config section name Example: ``soundspot`` """ version = None """The extension's version Should match the :attr:`__version__` attribute on the extension's main Python module and the version registered on PyPI. """
[docs] def get_default_config(self): """The extension's default config as a bytestring :returns: bytes or unicode """ raise NotImplementedError( 'Add at least a config section with "enabled = true"')
[docs] def get_config_schema(self): """The extension's config validation schema :returns: :class:`~mopidy.config.schema.ExtensionConfigSchema` """ schema = config_lib.ConfigSchema(self.ext_name) schema['enabled'] = config_lib.Boolean() return schema
[docs] def get_command(self): """Command to expose to command line users running ``mopidy``. :returns: Instance of a :class:`~mopidy.commands.Command` class. """ pass
[docs] def validate_environment(self): """Checks if the extension can run in the current environment. Dependencies described by :file:`setup.py` are checked by Mopidy, so you should not check their presence here. If a problem is found, raise :exc:`~mopidy.exceptions.ExtensionError` with a message explaining the issue. :raises: :exc:`~mopidy.exceptions.ExtensionError` :returns: :class:`None` """ pass
[docs] def setup(self, registry): """ Register the extension's components in the extension :class:`Registry`. For example, to register a backend:: def setup(self, registry): from .backend import SoundspotBackend registry.add('backend', SoundspotBackend) See :class:`Registry` for a list of registry keys with a special meaning. Mopidy will instantiate and start any classes registered under the ``frontend`` and ``backend`` registry keys. This method can also be used for other setup tasks not involving the extension registry. For example, to register custom GStreamer elements:: def setup(self, registry): from .mixer import SoundspotMixer gobject.type_register(SoundspotMixer) gst.element_register( SoundspotMixer, 'soundspotmixer', gst.RANK_MARGINAL) :param registry: the extension registry :type registry: :class:`Registry` """ raise NotImplementedError
[docs]class Registry(collections.Mapping): """Registry of components provided by Mopidy extensions. Passed to the :meth:`~Extension.setup` method of all extensions. The registry can be used like a dict of string keys and lists. Some keys have a special meaning, including, but not limited to: - ``backend`` is used for Mopidy backend classes. - ``frontend`` is used for Mopidy frontend classes. - ``local:library`` is used for Mopidy-Local libraries. Extensions can use the registry for allow other to extend the extension itself. For example the ``Mopidy-Local`` use the ``local:library`` key to allow other extensions to register library providers for ``Mopidy-Local`` to use. Extensions should namespace custom keys with the extension's :attr:`~Extension.ext_name`, e.g. ``local:foo`` or ``http:bar``. """ def __init__(self): self._registry = {}
[docs] def add(self, name, cls): """Add a component to the registry. Multiple classes can be registered to the same name. """ self._registry.setdefault(name, []).append(cls)
def __getitem__(self, name): return self._registry.setdefault(name, []) def __iter__(self): return iter(self._registry) def __len__(self): return len(self._registry)
[docs]def load_extensions(): """Find all installed extensions. :returns: list of installed extensions """ installed_extensions = [] for entry_point in pkg_resources.iter_entry_points('mopidy.ext'): logger.debug('Loading entry point: %s', entry_point) extension_class = entry_point.load(require=False) extension = extension_class() extension.entry_point = entry_point installed_extensions.append(extension) logger.debug( 'Loaded extension: %s %s', extension.dist_name, extension.version) names = (e.ext_name for e in installed_extensions) logger.debug('Discovered extensions: %s', ', '.join(names)) return installed_extensions
[docs]def validate_extension(extension): """Verify extension's dependencies and environment. :param extensions: an extension to check :returns: if extension should be run """ logger.debug('Validating extension: %s', extension.ext_name) if extension.ext_name != extension.entry_point.name: logger.warning( 'Disabled extension %(ep)s: entry point name (%(ep)s) ' 'does not match extension name (%(ext)s)', {'ep': extension.entry_point.name, 'ext': extension.ext_name}) return False try: extension.entry_point.require() except pkg_resources.DistributionNotFound as ex: logger.info( 'Disabled extension %s: Dependency %s not found', extension.ext_name, ex) return False except pkg_resources.VersionConflict as ex: if len(ex.args) == 2: found, required = ex.args logger.info( 'Disabled extension %s: %s required, but found %s at %s', extension.ext_name, required, found, found.location) else: logger.info('Disabled extension %s: %s', extension.ext_name, ex) return False try: extension.validate_environment() except exceptions.ExtensionError as ex: logger.info( 'Disabled extension %s: %s', extension.ext_name, ex.message) return False return True