<filename>spotipo_plugins/__init__.py<gh_stars>1-10
# -*- coding: utf-8 -*-
"""
    spotipo_plugins
    ~~~~~~~~~~~~~

    The Plugin class that every plugin should implement

    Modified version of flask-plugins from https://github.com/sh4nks/flask-plugins

    :copyright: (c) 2017 by Nibblecomm.
    :copyright: (c) 2014 by the FlaskBB Team.
    :license: BSD, see LICENSE for more details.
"""
import os
import sys
import importlib
from collections import deque
from werkzeug.utils import cached_property, import_string
from jinja2 import Markup
from flask import json
# Find the stack on which we want to store the database connection.
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
# before that we need to use the _request_ctx_stack.
try:
    from flask import _app_ctx_stack as stack
except ImportError:
    from flask import _request_ctx_stack as stack

from ._compat import itervalues, iteritems, intern_method

__version__ = "0.3"
__author__ = "<NAME>"


class PluginError(Exception):
    pass


def get_plugin(identifier):
    """Returns a plugin instance from the enabled plugins for the given
    name.
    """
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').plugins[identifier]


def get_plugin_from_all(identifier):
    """Returns a plugin instance from all plugins (includes also the disabled
    ones) for the given name.
    """
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').all_plugins[identifier]


def get_enabled_plugins():
    """Returns all enabled plugins as a list"""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').plugins.values()


def get_all_plugins():
    """Returns all plugins as a list including the disabled ones."""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').all_plugins.values()


class Plugin(object):
    """Every plugin should implement this class. It handles the registration
    for the plugin hooks, creates or modifies additional relations or
    registers plugin specific thinks
    """

    #: If setup is called, this will be set to ``True``.
    enabled = False

    def __init__(self, path):
        #: The plugin's root path. All the files in the plugin are under this
        #: path.
        self.path = os.path.abspath(path)

        with open(os.path.join(path, 'info.json')) as fd:
            self.info = i = json.load(fd)

        #: The plugin's name, as given in info.json. This is the human
        #: readable name.
        self.name = i['name']

        #: The plugin's identifier. This is an actual Python identifier,
        #: and in most situations should match the name of the directory the
        #: plugin is in.
        self.identifier = i['identifier']

        #: The human readable description. This is the default (English)
        #: version.
        self.description = i.get('description')

        #: This is a dictionary of localized versions of the description.
        #: The language codes are all lowercase, and the ``en`` key is
        #: preloaded with the base description.
        self.description_lc = dict(
            (k.split('_', 1)[1].lower(), v) for k, v in i.items()
            if k.startswith('description_')
        )
        self.description_lc.setdefault('en', self.description)

        #: The author's name, as given in info.json. This may or may not
        #: include their email, so it's best just to display it as-is.
        self.author = i['author']

        #: A short phrase describing the license, like "GPL", "BSD", "Public
        #: Domain", or "Creative Commons BY-SA 3.0".
        self.license = i.get('license')

        #: A URL pointing to the license text online.
        self.license_url = i.get('license_url')

        #: The URL to the plugin's or author's Web site.
        self.website = i.get('website')

        #: The plugin's version string.
        self.version = i.get('version')

        #: The plugin's type, could be general,login,prelogin,postlogin and 
        #: export
        self.type = i.get('type')


        #: Any additional options. These are entirely application-specific,
        #: and may determine other aspects of the application's behavior.
        self.options = i.get('options', {})

    @cached_property
    def license_text(self):
        """
        The contents of the theme's license.txt file, if it exists. This is
        used to display the full license text if necessary. (It is `None` if
        there was not a license.txt.)
        """
        lt_path = os.path.join(self.path, 'license.txt')
        if os.path.exists(lt_path):
            with open(lt_path) as fd:
                return fd.read()
        else:
            return None

    def setup(self):  # pragma: no cover
        """This method is used to register all things that the plugin wants to
        register.
        """
        pass

    def enable(self):
        """Enables the plugin by removing the 'DISABLED' file in the plugins
        root directory, calls the ``setup()`` method and sets the plugin state
        to true.
        """
        disabled_file = os.path.join(self.path, "DISABLED")
        try:
            if os.path.exists(disabled_file):
                os.remove(disabled_file)

            if not self.enabled:
                self.enabled = True
        except:
            raise
        return self.enabled

    def disable(self):
        """Disablesthe plugin.

        The app usually has to be restarted after this action because
        plugins _can_ register blueprints and in order to "unregister" them,
        the application object has to be destroyed.
        This is a limitation of Flask and if you want to know more about this
        visit this link: http://flask.pocoo.org/docs/0.10/blueprints/
        """
        disabled_file = os.path.join(self.path, "DISABLED")
        try:
            open(disabled_file, "a").close()
            self.enabled = False
        except:
            raise
        return self.enabled

    def install(self):  # pragma: no cover
        """Installs the things that must be installed in order to
        have a fully and correctly working plugin. For example, something that
        needs to be installed can be a relation and/or modify a existing
        relation.
        """
        pass

    def uninstall(self):  # pragma: no cover
        """Uninstalls all the things which were previously
        installed by `install()`. A Plugin must override this method.
        """
        pass


class PluginManager(object):
    """Collects all Plugins and maps the metadata to the plugin"""

    def __init__(self, app=None, **kwargs):
        """Initializes the PluginManager. It is also possible to initialize the
        PluginManager via a factory. For example::

            plugin_manager = PluginManager()
            plugin_manager.init_app(app)

        :param app: The flask application.

        :param plugin_folder: The plugin folder where the plugins resides.

        :param base_app_folder: The base folder for the application. It is used
                                to build the plugins package name.
        """
        # All enabled plugins
        self._plugins = None

        # All plugins - including the disabled ones
        self._all_plugins = None

        # All available plugins including the disabled ones
        self._available_plugins = dict()

        # All found plugins
        self._found_plugins = dict()

        if app is not None:
            self.init_app(app, **kwargs)

    def init_app(self, app, base_app_folder=None, plugin_folder="plugins"):
        self._event_manager = EventManager(app)
        app.jinja_env.globals["emit_event"] = self._event_manager.template_emit

        app.plugin_manager = self
        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['plugin_manager'] = self
        self.app = app

        if base_app_folder is None:
            base_app_folder = self.app.root_path.split(os.sep)[-1]

        self.plugin_folder = os.path.join(self.app.root_path, plugin_folder)
        self.base_plugin_package = ".".join(
            [base_app_folder, plugin_folder]
        )

        self.setup_plugins()

    @property
    def all_plugins(self):
        """Returns all plugins including disabled ones."""
        if self._all_plugins is None:
            self.load_plugins()
        return self._all_plugins

    @property
    def plugins(self):
        """Returns all enabled plugins as a dictionary. You still need to
        call the setup method to fully enable them."""
        if self._plugins is None:
            self.load_plugins()
        return self._plugins

    def load_plugins(self):
        """Loads all plugins. They are still disabled.
        Returns a list with all loaded plugins. They should now be accessible
        via self.plugins.
        """
        self._plugins = {}
        self._all_plugins = {}
        for plugin_name, plugin_package in iteritems(self.find_plugins()):

            try:
                plugin_class = import_string(
                    "{}.{}".format(plugin_package, plugin_name)
                )
            except ImportError:
                raise PluginError(
                    "Couldn't import {} Plugin. Please check if the "
                    "__plugin__ variable is set correctly.".format(plugin_name)
                )

            plugin_path = os.path.join(
                self.plugin_folder,
                os.path.basename(plugin_package.replace(".", "/"))
            )

            plugin_instance = plugin_class(plugin_path)

            try:
                if self._available_plugins[plugin_name]:
                    self._plugins[plugin_instance.identifier] = plugin_instance
            except KeyError:
                pass

            self._all_plugins[plugin_instance.identifier] = plugin_instance

    def find_plugins(self):
        """Find all possible plugins in the plugin folder."""
        for item in os.listdir(self.plugin_folder):
            if os.path.isdir(os.path.join(self.plugin_folder, item)) \
                    and os.path.exists(
                        os.path.join(self.plugin_folder, item, "__init__.py")):

                plugin = ".".join([self.base_plugin_package, item])

                # Same like from exammple.plugins.pluginname import __plugin__
                tmp = importlib.import_module(plugin)

                try:
                    # Add the plugin to the available plugins if the plugin
                    # isn't disabled
                    if not os.path.exists(
                            os.path.join(self.plugin_folder, item, "DISABLED")
                    ):

                        self._available_plugins[tmp.__plugin__] = \
                            "{}".format(plugin)

                    self._found_plugins[tmp.__plugin__] = \
                        "{}".format(plugin)

                except AttributeError:
                    pass

        return self._found_plugins

    def setup_plugins(self):  # pragma: no cover
        """Runs the setup for all enabled plugins. Should be run after the
        PluginManager has been initialized. Sets the state of the plugin to
        enabled.
        """
        for plugin in itervalues(self.plugins):
            with self.app.app_context():
                plugin.enabled = True
                plugin.setup()

    def install_plugins(self, plugins=None):
        """Installs one or more plugins.

        :param plugins: An iterable with plugins. If no plugins are passed
                        it will try to install all plugins.
        """
        for plugin in plugins or itervalues(self.plugins):
            with self.app.app_context():
                plugin.install()

    def uninstall_plugins(self, plugins=None):
        """Uninstalls one or more plugins.

        :param plugins: An iterable with plugins. If no plugins are passed
                        it will try to uninstall all plugins.
        """
        for plugin in plugins or itervalues(self.plugins):
            with self.app.app_context():
                plugin.uninstall()

    def enable_plugins(self, plugins=None):
        """Enables one or more plugins.

        It either returns the amount of enabled plugins or
        raises an exception caused by ``os.remove`` which says most likely
        that you can't write on the filesystem.

        :param plugins: An iterable with plugins.
        """
        _enabled_count = 0
        for plugin in plugins:
            plugin.enable()
            _enabled_count += 1
        return _enabled_count

    def disable_plugins(self, plugins=None):
        """Disables one or more plugins.
        It either returns the amount of disabled plugins or
        raises an exception caused by ``open`` which says most likely
        that you can't write on the filesystem.

        The app usually has to be restarted after this action because
        plugins **can** register blueprints and in order to "unregister" them,
        the application object has to be destroyed.
        This is a limitation of Flask and if you want to know more about this
        visit this link: http://flask.pocoo.org/docs/0.10/blueprints/

        :param plugins: An iterable with plugins
        """
        _disabled_count = 0
        for plugin in plugins:
            plugin.disable()
            _disabled_count += 1
        return _disabled_count


def connect_event(event, callback, position='after'):
    """Connect a callback to an event.  Per default the callback is
    appended to the end of the handlers but handlers can ask for a higher
    privilege by setting `position` to ``'before'``.

    Example usage::

        def on_before_metadata_assembled(metadata):
            metadata.append('<!-- IM IN UR METADATA -->')

        # And in your setup() method do this:
            connect_event('before-metadata-assembled',
                           on_before_metadata_assembled)
    """
    ctx = stack.top
    ctx.app.extensions.get('plugin_manager')._event_manager.connect(
        event, callback, position
    )


def emit_event(event, *args, **kwargs):
    """Emit a event and return a list of event results.  Each called
    function contributes one item to the returned list.

    This is equivalent to the following call to :func:`iter_listeners`::

        result = []
        for listener in iter_listeners(event):
            result.append(listener(*args, **kwargs))
    """
    ctx = stack.top
    return [
        x(*args, **kwargs) for x in
        ctx.app.extensions.get('plugin_manager')._event_manager.iter(event)
    ]


def iter_listeners(event):
    """Return an iterator for all the listeners for the event provided."""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager')._event_manager.iter(event)


class EventManager(object):
    """Helper class that handles event listeners and event emitting.

    This is *not* a public interface. Always use the `emit_event` or
    `connect_event` or the `iter_listeners` functions to access it.
    """

    def __init__(self, app):
        self.app = app
        self._listeners = {}
        self._last_listener = 0

    def connect(self, event, callback, position='after'):
        """Connect a callback to an event."""
        assert position in ('before', 'after'), 'invalid position'
        listener_id = self._last_listener
        event = intern_method(event)
        if event not in self._listeners:
            self._listeners[event] = deque([callback])
        elif position == 'after':
            self._listeners[event].append(callback)
        elif position == 'before':
            self._listeners[event].appendleft(callback)
        self._last_listener += 1
        return listener_id

    def remove(self, event, callback):
        """Remove a callback again."""
        try:
            self._listeners[event].remove(callback)
        except (KeyError, ValueError):
            pass

    def iter(self, event):
        """Return an iterator for all listeners of a given name."""
        if event not in self._listeners:
            return iter(())
        return iter(self._listeners[event])

    def template_emit(self, event, *args, **kwargs):
        """Emits events for the template context."""
        results = []
        for f in self.iter(event):
            rv = f(*args, **kwargs)
            if rv is not None:
                results.append(rv)
        return Markup(TemplateEventResult(results))


class TemplateEventResult(list):
    """A list subclass for results returned by the event listener that
    concatenates the results if converted to string, otherwise it works
    exactly like any other list.
    """

    def __init__(self, items):
        list.__init__(self, items)

    def __unicode__(self):
        if sys.version_info[0] >= 3:
            return u''.join(map(str, self))
        else:
            return u''.join(map(unicode, self))
        

    def __str__(self):
        if sys.version_info[0] >= 3:
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

<filename>spotipo_plugins/__init__.py<gh_stars>1-10
# -*- coding: utf-8 -*-
"""
    spotipo_plugins
    ~~~~~~~~~~~~~

    The Plugin class that every plugin should implement

    Modified version of flask-plugins from https://github.com/sh4nks/flask-plugins

    :copyright: (c) 2017 by Nibblecomm.
    :copyright: (c) 2014 by the FlaskBB Team.
    :license: BSD, see LICENSE for more details.
"""
import os
import sys
import importlib
from collections import deque
from werkzeug.utils import cached_property, import_string
from jinja2 import Markup
from flask import json
# Find the stack on which we want to store the database connection.
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
# before that we need to use the _request_ctx_stack.
try:
    from flask import _app_ctx_stack as stack
except ImportError:
    from flask import _request_ctx_stack as stack

from ._compat import itervalues, iteritems, intern_method

__version__ = "0.3"
__author__ = "<NAME>"


class PluginError(Exception):
    pass


def get_plugin(identifier):
    """Returns a plugin instance from the enabled plugins for the given
    name.
    """
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').plugins[identifier]


def get_plugin_from_all(identifier):
    """Returns a plugin instance from all plugins (includes also the disabled
    ones) for the given name.
    """
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').all_plugins[identifier]


def get_enabled_plugins():
    """Returns all enabled plugins as a list"""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').plugins.values()


def get_all_plugins():
    """Returns all plugins as a list including the disabled ones."""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager').all_plugins.values()


class Plugin(object):
    """Every plugin should implement this class. It handles the registration
    for the plugin hooks, creates or modifies additional relations or
    registers plugin specific thinks
    """

    #: If setup is called, this will be set to ``True``.
    enabled = False

    def __init__(self, path):
        #: The plugin's root path. All the files in the plugin are under this
        #: path.
        self.path = os.path.abspath(path)

        with open(os.path.join(path, 'info.json')) as fd:
            self.info = i = json.load(fd)

        #: The plugin's name, as given in info.json. This is the human
        #: readable name.
        self.name = i['name']

        #: The plugin's identifier. This is an actual Python identifier,
        #: and in most situations should match the name of the directory the
        #: plugin is in.
        self.identifier = i['identifier']

        #: The human readable description. This is the default (English)
        #: version.
        self.description = i.get('description')

        #: This is a dictionary of localized versions of the description.
        #: The language codes are all lowercase, and the ``en`` key is
        #: preloaded with the base description.
        self.description_lc = dict(
            (k.split('_', 1)[1].lower(), v) for k, v in i.items()
            if k.startswith('description_')
        )
        self.description_lc.setdefault('en', self.description)

        #: The author's name, as given in info.json. This may or may not
        #: include their email, so it's best just to display it as-is.
        self.author = i['author']

        #: A short phrase describing the license, like "GPL", "BSD", "Public
        #: Domain", or "Creative Commons BY-SA 3.0".
        self.license = i.get('license')

        #: A URL pointing to the license text online.
        self.license_url = i.get('license_url')

        #: The URL to the plugin's or author's Web site.
        self.website = i.get('website')

        #: The plugin's version string.
        self.version = i.get('version')

        #: The plugin's type, could be general,login,prelogin,postlogin and 
        #: export
        self.type = i.get('type')


        #: Any additional options. These are entirely application-specific,
        #: and may determine other aspects of the application's behavior.
        self.options = i.get('options', {})

    @cached_property
    def license_text(self):
        """
        The contents of the theme's license.txt file, if it exists. This is
        used to display the full license text if necessary. (It is `None` if
        there was not a license.txt.)
        """
        lt_path = os.path.join(self.path, 'license.txt')
        if os.path.exists(lt_path):
            with open(lt_path) as fd:
                return fd.read()
        else:
            return None

    def setup(self):  # pragma: no cover
        """This method is used to register all things that the plugin wants to
        register.
        """
        pass

    def enable(self):
        """Enables the plugin by removing the 'DISABLED' file in the plugins
        root directory, calls the ``setup()`` method and sets the plugin state
        to true.
        """
        disabled_file = os.path.join(self.path, "DISABLED")
        try:
            if os.path.exists(disabled_file):
                os.remove(disabled_file)

            if not self.enabled:
                self.enabled = True
        except:
            raise
        return self.enabled

    def disable(self):
        """Disablesthe plugin.

        The app usually has to be restarted after this action because
        plugins _can_ register blueprints and in order to "unregister" them,
        the application object has to be destroyed.
        This is a limitation of Flask and if you want to know more about this
        visit this link: http://flask.pocoo.org/docs/0.10/blueprints/
        """
        disabled_file = os.path.join(self.path, "DISABLED")
        try:
            open(disabled_file, "a").close()
            self.enabled = False
        except:
            raise
        return self.enabled

    def install(self):  # pragma: no cover
        """Installs the things that must be installed in order to
        have a fully and correctly working plugin. For example, something that
        needs to be installed can be a relation and/or modify a existing
        relation.
        """
        pass

    def uninstall(self):  # pragma: no cover
        """Uninstalls all the things which were previously
        installed by `install()`. A Plugin must override this method.
        """
        pass


class PluginManager(object):
    """Collects all Plugins and maps the metadata to the plugin"""

    def __init__(self, app=None, **kwargs):
        """Initializes the PluginManager. It is also possible to initialize the
        PluginManager via a factory. For example::

            plugin_manager = PluginManager()
            plugin_manager.init_app(app)

        :param app: The flask application.

        :param plugin_folder: The plugin folder where the plugins resides.

        :param base_app_folder: The base folder for the application. It is used
                                to build the plugins package name.
        """
        # All enabled plugins
        self._plugins = None

        # All plugins - including the disabled ones
        self._all_plugins = None

        # All available plugins including the disabled ones
        self._available_plugins = dict()

        # All found plugins
        self._found_plugins = dict()

        if app is not None:
            self.init_app(app, **kwargs)

    def init_app(self, app, base_app_folder=None, plugin_folder="plugins"):
        self._event_manager = EventManager(app)
        app.jinja_env.globals["emit_event"] = self._event_manager.template_emit

        app.plugin_manager = self
        if not hasattr(app, 'extensions'):
            app.extensions = {}
        app.extensions['plugin_manager'] = self
        self.app = app

        if base_app_folder is None:
            base_app_folder = self.app.root_path.split(os.sep)[-1]

        self.plugin_folder = os.path.join(self.app.root_path, plugin_folder)
        self.base_plugin_package = ".".join(
            [base_app_folder, plugin_folder]
        )

        self.setup_plugins()

    @property
    def all_plugins(self):
        """Returns all plugins including disabled ones."""
        if self._all_plugins is None:
            self.load_plugins()
        return self._all_plugins

    @property
    def plugins(self):
        """Returns all enabled plugins as a dictionary. You still need to
        call the setup method to fully enable them."""
        if self._plugins is None:
            self.load_plugins()
        return self._plugins

    def load_plugins(self):
        """Loads all plugins. They are still disabled.
        Returns a list with all loaded plugins. They should now be accessible
        via self.plugins.
        """
        self._plugins = {}
        self._all_plugins = {}
        for plugin_name, plugin_package in iteritems(self.find_plugins()):

            try:
                plugin_class = import_string(
                    "{}.{}".format(plugin_package, plugin_name)
                )
            except ImportError:
                raise PluginError(
                    "Couldn't import {} Plugin. Please check if the "
                    "__plugin__ variable is set correctly.".format(plugin_name)
                )

            plugin_path = os.path.join(
                self.plugin_folder,
                os.path.basename(plugin_package.replace(".", "/"))
            )

            plugin_instance = plugin_class(plugin_path)

            try:
                if self._available_plugins[plugin_name]:
                    self._plugins[plugin_instance.identifier] = plugin_instance
            except KeyError:
                pass

            self._all_plugins[plugin_instance.identifier] = plugin_instance

    def find_plugins(self):
        """Find all possible plugins in the plugin folder."""
        for item in os.listdir(self.plugin_folder):
            if os.path.isdir(os.path.join(self.plugin_folder, item)) \
                    and os.path.exists(
                        os.path.join(self.plugin_folder, item, "__init__.py")):

                plugin = ".".join([self.base_plugin_package, item])

                # Same like from exammple.plugins.pluginname import __plugin__
                tmp = importlib.import_module(plugin)

                try:
                    # Add the plugin to the available plugins if the plugin
                    # isn't disabled
                    if not os.path.exists(
                            os.path.join(self.plugin_folder, item, "DISABLED")
                    ):

                        self._available_plugins[tmp.__plugin__] = \
                            "{}".format(plugin)

                    self._found_plugins[tmp.__plugin__] = \
                        "{}".format(plugin)

                except AttributeError:
                    pass

        return self._found_plugins

    def setup_plugins(self):  # pragma: no cover
        """Runs the setup for all enabled plugins. Should be run after the
        PluginManager has been initialized. Sets the state of the plugin to
        enabled.
        """
        for plugin in itervalues(self.plugins):
            with self.app.app_context():
                plugin.enabled = True
                plugin.setup()

    def install_plugins(self, plugins=None):
        """Installs one or more plugins.

        :param plugins: An iterable with plugins. If no plugins are passed
                        it will try to install all plugins.
        """
        for plugin in plugins or itervalues(self.plugins):
            with self.app.app_context():
                plugin.install()

    def uninstall_plugins(self, plugins=None):
        """Uninstalls one or more plugins.

        :param plugins: An iterable with plugins. If no plugins are passed
                        it will try to uninstall all plugins.
        """
        for plugin in plugins or itervalues(self.plugins):
            with self.app.app_context():
                plugin.uninstall()

    def enable_plugins(self, plugins=None):
        """Enables one or more plugins.

        It either returns the amount of enabled plugins or
        raises an exception caused by ``os.remove`` which says most likely
        that you can't write on the filesystem.

        :param plugins: An iterable with plugins.
        """
        _enabled_count = 0
        for plugin in plugins:
            plugin.enable()
            _enabled_count += 1
        return _enabled_count

    def disable_plugins(self, plugins=None):
        """Disables one or more plugins.
        It either returns the amount of disabled plugins or
        raises an exception caused by ``open`` which says most likely
        that you can't write on the filesystem.

        The app usually has to be restarted after this action because
        plugins **can** register blueprints and in order to "unregister" them,
        the application object has to be destroyed.
        This is a limitation of Flask and if you want to know more about this
        visit this link: http://flask.pocoo.org/docs/0.10/blueprints/

        :param plugins: An iterable with plugins
        """
        _disabled_count = 0
        for plugin in plugins:
            plugin.disable()
            _disabled_count += 1
        return _disabled_count


def connect_event(event, callback, position='after'):
    """Connect a callback to an event.  Per default the callback is
    appended to the end of the handlers but handlers can ask for a higher
    privilege by setting `position` to ``'before'``.

    Example usage::

        def on_before_metadata_assembled(metadata):
            metadata.append('<!-- IM IN UR METADATA -->')

        # And in your setup() method do this:
            connect_event('before-metadata-assembled',
                           on_before_metadata_assembled)
    """
    ctx = stack.top
    ctx.app.extensions.get('plugin_manager')._event_manager.connect(
        event, callback, position
    )


def emit_event(event, *args, **kwargs):
    """Emit a event and return a list of event results.  Each called
    function contributes one item to the returned list.

    This is equivalent to the following call to :func:`iter_listeners`::

        result = []
        for listener in iter_listeners(event):
            result.append(listener(*args, **kwargs))
    """
    ctx = stack.top
    return [
        x(*args, **kwargs) for x in
        ctx.app.extensions.get('plugin_manager')._event_manager.iter(event)
    ]


def iter_listeners(event):
    """Return an iterator for all the listeners for the event provided."""
    ctx = stack.top
    return ctx.app.extensions.get('plugin_manager')._event_manager.iter(event)


class EventManager(object):
    """Helper class that handles event listeners and event emitting.

    This is *not* a public interface. Always use the `emit_event` or
    `connect_event` or the `iter_listeners` functions to access it.
    """

    def __init__(self, app):
        self.app = app
        self._listeners = {}
        self._last_listener = 0

    def connect(self, event, callback, position='after'):
        """Connect a callback to an event."""
        assert position in ('before', 'after'), 'invalid position'
        listener_id = self._last_listener
        event = intern_method(event)
        if event not in self._listeners:
            self._listeners[event] = deque([callback])
        elif position == 'after':
            self._listeners[event].append(callback)
        elif position == 'before':
            self._listeners[event].appendleft(callback)
        self._last_listener += 1
        return listener_id

    def remove(self, event, callback):
        """Remove a callback again."""
        try:
            self._listeners[event].remove(callback)
        except (KeyError, ValueError):
            pass

    def iter(self, event):
        """Return an iterator for all listeners of a given name."""
        if event not in self._listeners:
            return iter(())
        return iter(self._listeners[event])

    def template_emit(self, event, *args, **kwargs):
        """Emits events for the template context."""
        results = []
        for f in self.iter(event):
            rv = f(*args, **kwargs)
            if rv is not None:
                results.append(rv)
        return Markup(TemplateEventResult(results))


class TemplateEventResult(list):
    """A list subclass for results returned by the event listener that
    concatenates the results if converted to string, otherwise it works
    exactly like any other list.
    """

    def __init__(self, items):
        list.__init__(self, items)

    def __unicode__(self):
        if sys.version_info[0] >= 3:
            return u''.join(map(str, self))
        else:
            return u''.join(map(unicode, self))
        

    def __str__(self):
        if sys.version_info[0] >= 3:
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

# -*- coding: utf-8 -*- spotipo_plugins     ~~~~~~~~~~~~~      The Plugin class that every plugin should implement      Modified version of flask-plugins from https://github.com/sh4nks/flask-plugins      :copyright: (c) 2017 by Nibblecomm.     :copyright: (c) 2014 by the FlaskBB Team.     :license: BSD, see LICENSE for more details. # Find the stack on which we want to store the database connection. # Starting with Flask 0.9, the _app_ctx_stack is the correct one, # before that we need to use the _request_ctx_stack. Returns a plugin instance from the enabled plugins for the given     name. Returns a plugin instance from all plugins (includes also the disabled     ones) for the given name. Returns all enabled plugins as a list Returns all plugins as a list including the disabled ones. Every plugin should implement this class. It handles the registration     for the plugin hooks, creates or modifies additional relations or     registers plugin specific thinks #: If setup is called, this will be set to ``True``. #: The plugin's root path. All the files in the plugin are under this #: path. #: The plugin's name, as given in info.json. This is the human #: readable name. #: The plugin's identifier. This is an actual Python identifier, #: and in most situations should match the name of the directory the #: plugin is in. #: The human readable description. This is the default (English) #: version. #: This is a dictionary of localized versions of the description. #: The language codes are all lowercase, and the ``en`` key is #: preloaded with the base description. #: The author's name, as given in info.json. This may or may not #: include their email, so it's best just to display it as-is. #: A short phrase describing the license, like "GPL", "BSD", "Public #: Domain", or "Creative Commons BY-SA 3.0". #: A URL pointing to the license text online. #: The URL to the plugin's or author's Web site. #: The plugin's version string. #: The plugin's type, could be general,login,prelogin,postlogin and #: export #: Any additional options. These are entirely application-specific, #: and may determine other aspects of the application's behavior. The contents of the theme's license.txt file, if it exists. This is         used to display the full license text if necessary. (It is `None` if         there was not a license.txt.) # pragma: no cover This method is used to register all things that the plugin wants to         register. Enables the plugin by removing the 'DISABLED' file in the plugins         root directory, calls the ``setup()`` method and sets the plugin state         to true. Disablesthe plugin.          The app usually has to be restarted after this action because         plugins _can_ register blueprints and in order to "unregister" them,         the application object has to be destroyed.         This is a limitation of Flask and if you want to know more about this         visit this link: http://flask.pocoo.org/docs/0.10/blueprints/ # pragma: no cover Installs the things that must be installed in order to         have a fully and correctly working plugin. For example, something that         needs to be installed can be a relation and/or modify a existing         relation. # pragma: no cover Uninstalls all the things which were previously         installed by `install()`. A Plugin must override this method. Collects all Plugins and maps the metadata to the plugin Initializes the PluginManager. It is also possible to initialize the         PluginManager via a factory. For example::              plugin_manager = PluginManager()             plugin_manager.init_app(app)          :param app: The flask application.          :param plugin_folder: The plugin folder where the plugins resides.          :param base_app_folder: The base folder for the application. It is used                                 to build the plugins package name. # All enabled plugins # All plugins - including the disabled ones # All available plugins including the disabled ones # All found plugins Returns all plugins including disabled ones. Returns all enabled plugins as a dictionary. You still need to         call the setup method to fully enable them. Loads all plugins. They are still disabled.         Returns a list with all loaded plugins. They should now be accessible         via self.plugins. Find all possible plugins in the plugin folder. # Same like from exammple.plugins.pluginname import __plugin__ # Add the plugin to the available plugins if the plugin # isn't disabled # pragma: no cover Runs the setup for all enabled plugins. Should be run after the         PluginManager has been initialized. Sets the state of the plugin to         enabled. Installs one or more plugins.          :param plugins: An iterable with plugins. If no plugins are passed                         it will try to install all plugins. Uninstalls one or more plugins.          :param plugins: An iterable with plugins. If no plugins are passed                         it will try to uninstall all plugins. Enables one or more plugins.          It either returns the amount of enabled plugins or         raises an exception caused by ``os.remove`` which says most likely         that you can't write on the filesystem.          :param plugins: An iterable with plugins. Disables one or more plugins.         It either returns the amount of disabled plugins or         raises an exception caused by ``open`` which says most likely         that you can't write on the filesystem.          The app usually has to be restarted after this action because         plugins **can** register blueprints and in order to "unregister" them,         the application object has to be destroyed.         This is a limitation of Flask and if you want to know more about this         visit this link: http://flask.pocoo.org/docs/0.10/blueprints/          :param plugins: An iterable with plugins Connect a callback to an event.  Per default the callback is     appended to the end of the handlers but handlers can ask for a higher     privilege by setting `position` to ``'before'``.      Example usage::          def on_before_metadata_assembled(metadata):             metadata.append('<!-- IM IN UR METADATA -->')          # And in your setup() method do this:             connect_event('before-metadata-assembled',                            on_before_metadata_assembled) Emit a event and return a list of event results.  Each called     function contributes one item to the returned list.      This is equivalent to the following call to :func:`iter_listeners`::          result = []         for listener in iter_listeners(event):             result.append(listener(*args, **kwargs)) Return an iterator for all the listeners for the event provided. Helper class that handles event listeners and event emitting.      This is *not* a public interface. Always use the `emit_event` or     `connect_event` or the `iter_listeners` functions to access it. Connect a callback to an event. Remove a callback again. Return an iterator for all listeners of a given name. Emits events for the template context. A list subclass for results returned by the event listener that     concatenates the results if converted to string, otherwise it works     exactly like any other list.