The Ape Plugin
==============



.. image:: ../figures/robot_monster_babe.jpg
   :align: center

.. _ape-plugin:
This is the plugin that creates the Hortator to run :ref:`Composites <composite-class>`. It will be used when the `run` subcommand is called by the user.





.. _apeplugin-introduction:    
    
Introduction
------------

Rather than implementing separate classes for the different levels of Composites used in executing the `run` sub-command of the APE, a single composite is defined which accepts error and message specifications (and composes :ref:`Components <component-class>`) to define the particular instance of the Composite.

.. uml::

   Component --|> BaseClass
   Composite --|> Component
   Leaf --|> Component
   Hortator --|> Composite
   Operator --|> Composite
   Operation --|> Composite
   Hortator "1" o- "1..*" Operator
   Operator "1" o- "1..*" Operation
   Operation "1" o- "1..*" Leaf

The `Hortator`, `Operator` and `Operation` are instances of the Composite and so aren't functionally different but they catch different levels of exceptions so that there can be a certain level of self-recovery and error handling. Each execution of the `ape` will have one `Hortator` composed of `Operators` (one `Operator` per configuration file with an `APE` section). Each line in the APE will correspond to an `Operation` composed of `Leafs` (one `Leaf` per item on the comma-separated line). Each Leaf is a plugin's product. Thus when a plugin is created, the product should inherit from the `Component` class in order to make sure it has the same interface.

The reason for differentiating the three Composites is to allow different levels of error handling.  For instance, differentiating `Operation` vs  `Operator`  allows the user to decide on a set of plugins that will be called even if the first set crashes. e.g.::

    [APE]
    op_1 = WatchRSSI,IperfSession
    op_2 = CleanUp

When translated to objects, this configuration would create one `Operation` composite for each line and both lines would be composed in an `Operator` (and if there are multiple config-files with ``[APE]`` sections, an `Operator` will be created for each and all `Operators` will be composed in the `Hortator`). If one of the `Leafs` in `op_1` (`WatchRSSI` or `IperfSession`) crashes in a predictable way (raising an `ApeError` or the equivalent) then the `op_2` Leaf (CleanUp) should still be executed. The reason for only catching ape-defined exceptions is so that if something is really wrong with the code or system and another exception is raised (either a python-built-in exception or from an external third-party package), it will be assumed that the configuration is un-runnable and the Hortator will move on to the next `Operator`.

.. _apeplugin-errors:

The Errors
----------

.. uml::

   ApeError --|> Exception
   DontCatchError --|> Exception

Since the two errors are siblings, catching the ``ApeError`` won't catch the `DontCatchError`.

.. superfluous '   

 * The `Operation` composite runs its `Leafs` and lets the errors propagate (so any errors will stop it)

 * The `Operator` runs its `Operations` and traps `ApeErrors` so that it can try all its `Operations`, allowing any clean-up `Leafs` to be run

 * The `Hortator` runs its `Operators` and traps all errors (except KeyboardInterrupts), preventing one `Operator` from stopping the others

Constants
---------

These are constants put into classes to make it easier for the tests to find them and to make the configuration-file settings more implicit.


.. code:: python

    class OperatorConfigurationConstants(object):
        """
        constants for the OperatorConfiguration
        """
        __slots__ = ()
        # sections
        settings_section = 'SETTINGS'
        operations_section = 'OPERATIONS'
        plugins_section = "PLUGINS"
    
        # options
        repetitions_option = 'repetitions'
        config_glob_option = 'config_glob'
        total_time_option = 'total_time'
        end_time_option = 'end_time'
        subfolder_option = 'subfolder'
        modules_option = 'external_modules'
        timestamp_option = 'timestamp'
        plugin_option = 'plugin'
    
        # defaults
        default_repetitions = 1
        default_config_glob = None
        default_total_time = None
        default_end_time = None
        default_subfolder = None
        default_modules = None
        default_timestamp = None
    
        #extra
        file_storage_name = 'infrastructure'
    



OperatorConfigspec
------------------

The Configuration Specification for the Operator Configuration. It's used by configobj to validate a configuration, convert strings to types, and set defaults.

.. '


.. code:: python

    config_spec = """
    [SETTINGS]
    config_glob = string(default=None)
    repetitions = integer(default=1)
    total_time = relative_time(default=None)
    end_time = absolute_time(default=None)
    subfolder = string(default=None)
    external_modules = string_list(default=None)
    timestamp = string(default=None)
    
    [OPERATIONS]
    __many__ = force_list
    
    [PLUGINS]
     [[__many__]]
     plugin = string
    """



It looks like the way configobj works there isn't a way to force the plugins section with the configspec, it just shows up...

.. '

.. uml::

   OperatorConfigspec o- ConfigObj

.. module:: ape.plugins.apeplugin
.. autosummary::
   :toctree: api

   OperatorConfigspec
   OperatorConfigspec.configspec
   OperatorConfigspec.subconfigspec
   OperatorConfigspec.validator

   



OperatorConfiguration
---------------------

The OperatorConfiguration builds the dependencies for the Operators.

   * **Responsibility**: Build the Operator from the configuration.

.. uml::

   OperatorConfiguration o- CountdownTimer
   OperatorConfiguration o- OperationConfiguration
   OperatorConfiguration o- QuarterMaster
   OperatorConfiguration o- Composite

.. module:: ape.plugins.apeplugin
.. autosummary::
   :toctree: api

   OperatorConfiguration
   OperatorConfiguration.configuration
   OperatorConfiguration.configspec
   OperatorConfiguration.countdown_timer
   OperatorConfiguration.initialize_file_storage
   OperatorConfiguration.operation_configurations
   OperatorConfiguration.quartermaster
   OperatorConfiguration.operation_timer
   OperatorConfiguration.operator
   OperatorConfiguration.save_configuration





OperationConfiguration
----------------------

A dependency builder for operations.

   * **Responsibility**: builds composite of plugins from PLUGINS section

.. uml::

   BaseClass <|-- OperationConfiguration
   OperationConfiguration o- QuarterMaster
   OperationConfiguration o- CountdownTimer
   OperationConfiguration o- Composite
   
.. autosummary::
   :toctree: api

   OperationConfiguration
   OperationConfiguration.plugin_sections_names
   OperationConfiguration.operation




.. _apeplugin-run-state-diagram:
 
The Run State Diagram
---------------------

The assumed flow for the ``run`` sub-command is something like this:

.. digraph:: run_state_diagram

   rankdir = LR
   pa [label="Parse Args"]
   bc [label="Build Map"]
   bo [label="Build Composites"]
   run [label="Run", shape=diamond]
   data [label="Data", shape=rect]
   start [label="Start", shape=diamond]
   configurations [label="Configurations", shape=rect]
   
   start -> pa [label="args"]
   pa -> bc [label="name"]
   configurations -> bc [label="config"]
   bc -> bo [label="map"]
   bo -> run [label="Hortator"]
   run -> data [label="datum"]


This means:
   
 * The `Parse Args` state has been reached before this plugin is created.

 * The `Build Map` uses a `ConfigurationMap <configuration-map>`

 * the `Build Composites` happens in creating the ``product`` 
   






.. code:: python

    CONFIGURATION = '''[OPERATIONS]
    # the option names are just identifiers
    # they will be executed in the order given.
    # Each plugin has to have a corresponding section below
    # e.g. if there is a `Sleep1` plugin listed as a right-hand-side value
    # Then there needs to be a [[Sleep1]] section in the [PLUGINS] section
    # to configure it
    <option_name_1> = <comma-separated-list of plugins>
    <option_name_2> = <comma-separated-list of plugins>
    ...
    <option_name_n> = <comma-separated-list of plugins>
    
    #[SETTINGS]
    # these are settings for the overall operation
    
    # if you add a configuration-file-glob (config_glob),
    # all matching files will be added to the configuration
    # (the default is None)
    #config_glob = settings*.config
    
    # if you want to repeat the operation defined in this config, give it repetitions
    # (default is 1)
    # repetitions = 1000000
    
    # If you want to put a time limit after which to quit (this overrides repetitions)
    # (default is None)
    # total_time = 1 day 3 hours
    
    # if you want to put an end time (this will override repetitions and total time):
    # (default is None)
    # end_time = November 23, 2013 8:00 am
    
    # if you want to store files in a sub-folder
    # (default is None)
    # subfolder = <name>
    
    # if one or more plugins is coming from the ape
    # tell me which module to import it from
    # comma-separated list
    # (default is None)
    # external_modules = package.module, package2.module2
    
    # if you want to override the file timestamp format
    # (default is None)
    # timestamp = <strftime-formatted timestamp>
    
    #[PLUGINS]
    # for each plugin listed in the [OPERATIONS] there has to be a matching
    # subsection below this section
    # sub-sections are denoted by double-brackets (you can indent them too)
    # the actual class name for the plugin is set with the 'plugin' option
    # the rest of each plugin sub-section has to be whatever configures the plugin
    
    #  [[plugin1]]
    #  plugin = Sleep
    #  updates_section = <section_name>
    #  <sleep configuration>
    
    #  [[plugin2]]
    #  plugin = Iperf
    #  <Iperf configuration>
    '''





.. _apeplugin-module-dependency-graph:

Module Dependency Graph
-----------------------

This is an auto-generated graph of this module.


[Errno 2] No such file or directory
Is pylint installed?
.. image:: classes_apeplugin.png



.. .. _apeplugin-class-diagram:
.. 
Class Diagram
-------------
.. 
.. This is an auto-generated class diagram for the Ape.
.. 
.. <<name='class_diagram', echo=False, results='sphinx'>>=
.. if in_pweave:
..     class_diagram_file = class_diagram(class_name="Ape",
..                                        filter='OTHER',
..                                        module=this_file)
..     print ".. image:: {0}".format(class_diagram_file)
.. @
.. 


.. uml::

   Ape --|> BasePlugin
   Ape o-- HelpPage
   Ape o-- Composite
   Ape o-- QuarterMaster
   Ape o-- ConfigurationMap
   Ape o-- FileStorage

.. _apeplugin-api:

The API
-------

.. currentmodule:: ape.plugins.apeplugin   
.. autosummary::
   :toctree: api

   Ape
   Ape.help
   Ape.product
   Ape.fetch_config
   Ape.arguments
   Ape.sections
   




.. _apeplugin-external-plugins:

Using External Plugins
----------------------

In order to allow the execution of plugins that are not a part of the ``ape``, I am allowing the declaration of 
modules in the configuration file::

    [MODULES]
    package.module

Or something similar. The ape will search modules named in the MODULES section for children of the `ape.plugins.base_plugin.BasePlugin` class and load and call them if found. The idea is that rather than having to mix up the ape and other packages, a `sandbox` package can be setup with plugins that act as the interface between the `ape` and the other package(s).

Using the `FakePlugin` created for the :ref:`Exploring External Imports <exploring-external-imports>` section, the config file for the ape could look something like this::

    [APE]
    operation_1 = FakePlugin

    [MODULES]
    fakepackage.fakeplugin

    [FakePlugin]
    plugin = FakePlugin
    option = value

The FakePlugin returns a :ref:`DummyClass <dummy-class>` as its `product` so the FAKEPLUGIN section doesn't really do anything.

.. '

.. note:: In order to allow more than one instance of a plugin to be created, the ``plugin=<plugin class name>`` line was added. Each section that configures a plugin needs it. The header for the section is arbitrary but must match the value defined in the APE section.

If we wanted to configure a second FakePlugin, for instance, we could do something like this::

    [APE]
    operation_1 = apple, banana

    [MODULES]
    fakepackage.fakeplugin

    [apple]
    plugin = FakePlugin
    option = value

    [banana]
    plugin = FakePlugin
    options = other_value
 
This is the intended way for it to work, anyway. If the plugin wasn't built to use the ``section_header`` attribute when retrieving section information it won't work.