.. _plugins-tutorial:

Getting Started with :mod:`dip.plugins`
=======================================

A well designed dip application will be implemented as a set of components.
The more those components are decoupled from each other, the greater chance
there is of being able to reuse them in a different context.  Defining
components in terms of :term:`interfaces<interface>` is a major step in being
able to decouple them.  The use of
:ref:`default handlers<ref-computed-default-values>` makes it easy to provide a
default implementation of an interface while still allowing it to be overridden
with an alternative implementation.  However, that still requires that one
component is explictly aware of another component to use as a default
implementation.

The :mod:`dip.plugins` module provides mechanisms for making connections
between components while ensuring that the components themselves are completely
decoupled from each other.

Assuming that an application has been structured sensibly (i.e. putting
logically seperate code in separate modules - *not* how we have been
structuring our examples so far), then converting an application to be plugin
based is normally a case of adding plugin definitions.

Later on in this section we will implement some plugin definitions for our
:download:`python_ide.py</examples/shell/python_ide.py>` example.


Concepts
--------

A :term:`plugin` is an implementation of the :class:`~dip.plugins.IPlugin`
interface that makes connections between components by publishing objects,
either as contributions to :term:`extension points<extension point>` or as
:term:`services<service>`.  Plugins are managed by a :term:`plugin manager`.  A
plugin will play no part in an application until it is enabled.

An extension point is a list of published objects, usually of a particular type
or implementing a particular interface.  Each extension point has a unique
string identifier.  All extension points defined by dip have identifiers
beginning with ``dip.``.  Plugins make contributions to extension points when
they are enabled.  A plugin can bind an extension point to an attribute of an
object.  A bound attribute will normally be a :class:`~dip.model.List` but can
be anything that has an ``append()`` method.  A contribution is a list of
objects.  When a contribution is made each object is appended to each attribute
that is bound to the extension point.  When an attribute is bound then any
previous contributions are appended to the attribute.

A service is an object that implements a particular interface.  Several plugins
may provide services that implement the same interface.  When a plugin requests
a service the plugin manager will choose which service is actually used.  The
plugin does not care about the particular service, its only concern is that it
has an object that implements the interface.  A plugin can then bind a service
to an attribute of an object.

When a plugin is enabled it will create any services it provides, possibly
requesting other services to configure them with.  It will also make any 
contributions to extension points and possibly bind extension points to objects
it creates.

A plugin may specify that it requires that another plugin, identified by its
string identifier, is already present and enabled.

Plugins are lightweight objects, i.e. they are quick to import and have small
memory footprints.  It is only when a plugin is enabled that more significant
imports are done and potentially resource hungry objects created.


Writing Plugins
---------------

Going back to our :download:`python_ide.py</examples/shell/python_ide.py>`
example lets assume that we have restructured the code along the following
lines.

``python_code.py``
    This contains the ``PythonCode`` class, its ``PythonCodeFactory`` factory
    class and the adapter between ``PythonCode`` and the
    :class:`~dip.shell.IManagedModel` interface.

``python_code_codec.py``
    This contains the adapter between ``PythonCode`` and the
    :class:`~dip.io.IFilterHints`,
    :class:`~dip.io.codecs.unicode.IUnicodeDecoder` and
    :class:`~dip.io.codecs.unicode.IUnicodeEncoder` interfaces.

``python_code_editor.py``
    This contains the ``PythonCodeEditorTool`` class.

``project.py``
    This contains the ``Project`` class, its ``ProjectFactory`` factory class
    and the adapter between ``Project`` and the
    :class:`~dip.shell.IManagedModel` interface.

``project_codec.py``
    This contains the adapter between ``Project`` and the
    :class:`~dip.io.IFilterHints`, :class:`~dip.io.codecs.xml.IXmlDecoder` and
    :class:`~dip.io.codecs.xml.IXmlEncoder` interfaces.

``project_editor.py``
    This contains the ``ProjectEditorTool`` class.

This structure represents the different functional pieces.  There are strong
arguments for breaking it down further, particularly to keep adapters separate
from the types that they are adapting.

We can now write a plugin for each of the above.  The following is the plugin
that publishes an instance of ``PythonCodeFactory`` as a managed model
factory::

    from dip.model import implements, Model
    from dip.plugins import IPlugin
    from dip.ui import IDisplay


    @implements(IPlugin, IDisplay)
    class PythonCodePlugin(Model):
        """ The PythonCodePlugin is the plugin definition for the
        PythonCodeFactory managed model factory.
        """

        # The identifier of the plugin.
        id = 'myorganization.plugins.python_code'

        # The name of the plugin.
        name = "Python code plugin"

        def configure(self, plugin_manager):
            """ Configure the plugin. """

            # Create the model factory instance.
            from .python_code import PythonCodeFactory
            model_factory = PythonCodeFactory()

            # Contribute the model factory.
            plugin_manager.contribute(
                    'dip.shell.model_factories', model_factory)

A plugin must implement the :class:`~dip.plugins.IPlugin` interface.  Our
plugin also implements the :class:`~dip.ui.IDisplay` interface.

The :attr:`~dip.plugins.IPlugin.id` attribute specifies the plugin's
identifier.  Plugins with the same identifier are assumed to perform the same
function.

The :attr:`~dip.ui.IDisplay.name` attribute specifies the plugin's user
friendly name.  In the current version of dip this is unused but a future
version will allow the user to explicitly enable and disable plugins,
particularly those that have been discovered dynamically.

The :meth:`~dip.plugins.IPlugin.configure` method is called by the plugin
manager when the plugin is enabled.  Here we create our model factory and
contribute it to the ``dip.shell.model_factories`` extension point.  We assume
that the extension point will be bound to an attribute of an object elsewhere
in the application - but we don't really care.

All of the plugins for our example will look very similar to this.  We will
show just one more - the plugin that contributes the codec for the decoding and
encoding a ``Project`` instance::

    from dip.io.codecs.xml import XmlCodec
    from dip.model import implements, Model
    from dip.plugins import IPlugin
    from dip.ui import IDisplay


    @implements(IPlugin, IDisplay)
    class ProjectCodecPlugin(Model):
        """ The ProjectCodecPlugin is the plugin definition for the codec that
        decodes and encodes a Project instance.
        """

        # The identifier of the plugin.
        id = 'myorganization.plugins.project_codec'

        # The name of the plugin.
        name = "Project codec plugin"

        def configure(self, plugin_manager):
            """ Configure the plugin. """

            # Make sure the adapter gets registered.
            from . import python_codec

            # Create the codec instance.
            codec = XmlCodec(format='myorganization.formats.project')

            # Contribute the codec.
            plugin_manager.contribute('dip.io.codecs', codec)

Of course our plugin based version of our original
:download:`python_ide.py</examples/shell/python_ide.py>` file looks different
as it now mostly consists of adding a series of plugins::

    import sys

    from dip.io.plugins import FilesystemStoragePlugin
    from dip.plugins import PluginManager
    from dip.shell import IShell
    from dip.shell.plugins import (DirtyToolPlugin, MainWindowShellPlugin, 
            ModelManagerToolPlugin, QuitToolPlugin)
    from dip.ui import Application, IView

    from .python_code_plugin import PythonCodePlugin
    from .python_code_codec_plugin import PythonCodeCodecPlugin
    from .python_code_editor_plugin import PythonCodeEditorPlugin

    from .project_plugin import ProjectPlugin
    from .project_codec_plugin import ProjectCodecPlugin
    from .project_editor_plugin import ProjectEditorPlugin


    # Every application needs an Application.
    app = Application()

    # Add dip provided plugins for a shell, tools and storage.
    PluginManager.add_plugin(MainWindowShellPlugin())

    PluginManager.add_plugin(DirtyToolPlugin())
    PluginManager.add_plugin(ModelManagerToolPlugin())
    PluginManager.add_plugin(QuitToolPlugin())

    PluginManager.add_plugin(FilesystemStoragePlugin())

    # Add the application specific plugins.
    PluginManager.add_plugin(PythonCodePlugin())
    PluginManager.add_plugin(PythonCodeCodecPlugin())
    PluginManager.add_plugin(PythonCodeEditorPlugin())

    PluginManager.add_plugin(ProjectPlugin())
    PluginManager.add_plugin(ProjectCodecPlugin())
    PluginManager.add_plugin(ProjectEditorPlugin())

    # Ask for a shell for the user interface.
    shell = PluginManager.service(IShell)

    # If a command line argument was given try and open it as a project.
    if len(sys.argv) > 1:
        shell.open('myorganization.shell.tools.project_editor',
                sys.argv[1], 'myorganization.formats.project')

    # Set the shell view's window title and make it visible.
    view = IView(shell)
    view.window_title = "Python IDE[*]"
    view.visible = True

    # Enter the event loop.
    app.execute()

There are two things to note in this code:

- The first is the use of the :meth:`~dip.plugins.PluginManager.add_plugin`
  class method as a convenient way of adding a plugin to the default plugin
  manager.  Note that plugins are enabled by default.

- The second is the use of the :meth:`~dip.plugins.PluginManager.service` class
  method as a convenient way of obtaining a service from the default plugin
  manager.  Note that this returns an object that implements the
  :class:`~dip.shell.IShell` interface and can be adapted to the
  :class:`~dip.ui.IView` interface.  Compare this with previous examples where
  we create an object that implemented the :class:`~dip.ui.IView` interface and
  could be adapted to the :class:`~dip.shell.IShell` interface.

By adopting a plugin based approach we now have an application comprising a set
of independent components.  Adding a new component requires two lines to be
added to the above code, one line to import the new plugin and another to add
it to the plugin manager.  Although this is a simple change it is a change
nevertheless.  A future version of the :mod:`dip.plugins` module will support
the automatic discovery of plugins in order to deal with this.
