Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > 4a6f76725dc8922dc15f7eb0d84d77ef > files > 261

python-enthought-envisagecore-3.1.1-2mdv2010.0.noarch.rpm

The "Message of the Day" (MOTD) Example
=======================================

Only marginally more complicated than the traditional "Hello World" example,
this is a simple application that prints a witty, educational, or
inspiring "Message of the Day". Obviously, if we actually had to write this
application, we might not choose to use a framework like Envisage (it is one of
the rare applications that is so simple that why would you bother), but it does
serve to illustrate all of the fundamental aspects of building an Envisage
application.

All of the code for this example can be found in the `examples/MOTD`_ directory
of the Envisage distribution. This directory contains two subdirectories:

dist
  This directory contains the actual runnable application as it *might*
  actually be distributed/deployed (of course, the *actual* deployment is up to
  you).

  To run the application::

    cd dist
    python run.py
  
  (or equivalent, depending on your operating system and shell)
  
src
  This directory contains the source code for the eggs that make up the
  application. This is there to allow easy access to the example code, but
  would obviously not normally need to be deployed.

Before we dive right in to building our extensible application, let's take a
small, but important, step back. Envisage is designed to be an integration
platform -- a place where you can bring existing code and with a (hopefully)
minimal amount of effort, make it work with the rest of the application.
Because of this, we will start the MOTD example by designing the application
without any knowledge of Envisage whatsoever. This is obviously a good idea in
general, as it allows our code to be reused outside of Envisage applications.

Plain Ol' MOTD
--------------

So lets take a look at our non-Envisage aware MOTD application, the code for
which is in the acme.motd_ package. A good place to start as a developer
*using* any package in Envisage (and, for that matter, the entire Enthought
tool-suite) is to look at any interfaces and classes exposed via its 'api.py'
module.

In this case, there are 2 interfaces

1) IMOTD_

  The interface for simple "Message of the Day" functionality.

2) IMessage_

  The interface supported by each message returned by the motd() method on
  the IMOTD_ interface.

We also (*not* coincidentally!) have 2 corresponding implementation classes:

1) MOTD_
2) Message_

As you can see, the MOTD_ class simply contains a list of messages and
when its motd() method is called, it returns a random choice from that list.

An example of using our MOTD_ class at the Python prompt might be::

    >>> from acme.motd.api import Message, MOTD
    >>> motd = MOTD(messages=[Message(author='Anon', text='Hello World!')])
    >>> message = motd.motd()
    >>> print '"%s" - %s' % (message.text, message.author)
    "Hello World!" - Anon
    >>> 

Well, we had to get "Hello World" in there somewhere!

The important point here is that this code is written without any knowledge of
Envisage or extensibility whatsoever. It is just a small, reusable piece of
code (albeit a very simple one).

Not So Plain Ol' MOTD
---------------------

Now lets look at the steps that we have to go through to use this code and
turn it into an extensible, pluggable Envisage application.

1) Create the main Application class
------------------------------------

First of all, we need to create an object that represents the application
itself. In Envisage, this can be any object that implements the IApplication_
interface, but is usually either an instance of the default Application_ class,
or one derived from it.

In the MOTD_ example, we create the class in the run.py_ module as follows::

    def run():
        """ The function that starts your application. """

        # Create and run the application.
        from enthought.envisage.api import Application

        return Application(id='acme.motd').run()

Note that the run.py_ file also contains some boilerplate code to add the
application's `Python Eggs`_ to the ``sys.path``, but this is not specific
to Envisage - that code would be required for any egg-based application.

2) Create the 'acme.motd' plugin
--------------------------------

This is the plugin that will deliver the "Message of the Day" functionality
into the application. It will do this by declaring an extension point to
allow other plugins to contribute messages, and by using contributions to
create an instance of the MOTD_ class and to publish it as a service.

By default, Envisage finds plugins via Python eggs, so all we have to do is
to declare the existence of our plugin using the "enthought.envisage.plugins"
entry point in our 'setup.py' module::

    setup(
        ...

        entry_points = """

        ...

	[enthought.envisage.plugins]
	acme.motd = acme.motd.motd_plugin:MOTDPlugin

        ...
    
        """
    )

The left-hand-side of the 'acme.motd = acme.motd.motd_plugin:MOTDPlugin' line
*must* be the same as the 'id' trait as specified in the 'MOTDPlugin' class -
in this case 'acme.motd'. While this smacks of duplication, it allows plugin
managers such as the 'EggPluginManager' to filter unwanted plugins by id
without the need to import and instantiate them.

Notice that we don't import the plugin from an 'api.py' module. This is to
delay importing implementation code until it is actually needed.

As showm above, the corresponding plugin implementation is in the
MOTDPlugin_ class::

  class MOTDPlugin(Plugin):
      """ The 'Message of the Day' plugin.

      This plugin simply prints the 'Message of the Day' to stdout.
    
      """

      # The IDs of the extension points that this plugin offers.
      MESSAGES = 'acme.motd.messages'

      # The IDs of the extension points that this plugin contributes to.
      SERVICE_OFFERS = 'enthought.envisage.service_offers'

      #### 'IPlugin' interface ##################################################

      # The plugin's unique identifier.
      id = 'acme.motd'

      # The plugin's name (suitable for displaying to the user).
      name = 'MOTD'

      #### Extension points offered by this plugin ##############################

      # The messages extension point.
      #
      # Notice that we use the string name of the 'IMessage' interface rather
      # than actually importing it. This makes sure that the import only happens
      # when somebody actually gets the contributions to the extension point.
      messages = ExtensionPoint(
          List(Instance('acme.motd.api.IMessage')), id=MESSAGES, desc="""

          This extension point allows you to contribute messages to the 'Message
          Of The Day'.

          """
      )

      #### Contributions to extension points made by this plugin ################

      service_offers = List(contributes_to=SERVICE_OFFERS)

      def _service_offers_default(self):
          """ Trait initializer. """

          # Register the protocol as a string containing the actual module path
          # (do not use a module path that goes via an 'api.py' file as this does
          # not match what Python thinks the module is!). This allows the service
          # to be looked up by passing either the exact same string, or the
          # actual protocol object itself.
          motd_service_offer = ServiceOffer(
              protocol = 'acme.motd.i_motd.IMOTD',
              factory  = self._create_motd_service
          )

          return [motd_service_offer]

      ###########################################################################
      # Private interface.
      ###########################################################################

      def _create_motd_service(self):
          """ Factory method for the 'MOTD' service. """

          # Only do imports when you need to! This makes sure that the import
          # only happens when somebody needs an 'IMOTD' service.
          from motd import MOTD

          return MOTD(messages=self.messages)

      # This plugin does all of its work in this method which gets called when
      # the application has started all of its plugins.
      @on_trait_change('application:started')
      def _print_motd(self):
          """ Print the 'Message of the Day' to stdout! """

          # Note that we always offer the service via its name, but look it up
          # via the actual protocol.
          from acme.motd.api import IMOTD
        
          # Lookup the MOTD service.
          motd = self.application.get_service(IMOTD)

          # Get the message of the day...
          message = motd.motd()

          # ... and print it.
          print '\n"%s"\n\n- %s' % (message.text, message.author)

          return

Although it is obviously a bit of overkill, the example shows how we would
take a MOTD_ object and register it a service for other parts of the
application to use. Sadly, in this example, there are no other parts of the
application, so we just lookup and use the service ourselves!

3) Build the 'acme.motd' egg.
-----------------------------

To deploy the plugin into an application, we have to build it as an egg (this
is only because we are using eggs as our deployment mechanism, if you do not
want to use eggs then obviously you don't have to do any of this!)::

    cd .../examples/MOTD/src/acme.motd
    python setup.py bdist_egg --dist-dir ../../dist/eggs

If we run the application now , we will be told to work hard and be good to our
Mothers. Good advice indeed, but what it really shows is that we haven't yet
contributed any messages to the application. Lets do this next.

4) Create the 'acme.motd.software_quotes' plugin
------------------------------------------------

First of all, we have to create the messages that we want to add. Remember that
when the acme.motd_ plugin advertised the extension point, it told us that
every contribution had to implement the IMessage_ interface. Happily, there is
a class that does just that already defined for us (Message_) and so we create
a simple module ('messages.py'_) and add our Message_ instances to it::

    messages = [
        ...
    
        Message(
            author = "Martin Fowler",
            text   = "Any fool can write code that a computer can understand. Good"
            " programmers write code that humans can understand."
        )

        Message(
            author = "Chet Hendrickson",
            text   = "The rule is, 'Do the simplest thing that could possibly"
            " work', not the most stupid."
        )

        ...
    ]

Now we create a plugin for the acme.motd.software_quotes_ package and tell
Envisage about the messages that we have just created::

  class SoftwareQuotesPlugin(Plugin):
      """ The 'Software Quotes' plugin. """

      #### 'IPlugin' interface ##################################################

      # The plugin's unique identifier.
      id = 'acme.motd.software_quotes'

      # The plugin's name (suitable for displaying to the user).
      name = 'Software Quotes'

      #### Extension point contributions ########################################

      # Messages for the 'Message Of The Day'.
      messages = List(contributes_to='acme.motd.messages')
    
      ###########################################################################
      # 'SoftwareQuotesPlugin' interface.
      ###########################################################################

      def _messages_default(self):
          """ Trait initializer. """

          # Only do imports when you need to!
          from messages import messages

          return messages

And finally we go to the 'setup.py' file for the acme.motd.software_quotes_ egg
and tell Envisage about the plugin::

    setup(
        entry_points = """

        [enthought.envisage.plugins]
	acme.motd.software_quotes = acme.motd.software_quotes.software_quotes_plugin:SoftwareQuotesPlugin

	...

        """
    )

5) Build the 'acme.motd.software_quotes' egg.
---------------------------------------------

To deploy the plugin into an application, we have to build it as an egg::

    cd .../examples/MOTD/src/acme.motd.software_quotes
    python setup.py bdist_egg --dist-dir ../../dist/eggs

If we run the application now , we will (if all is well!) get a random, pithy
quote about software development!

To add more messages to the application in future, all we have to do is to
create other plugins similar to the 'acme.motd.software_quotes' egg and drop 
them into the '.../examples/MOTD/dist/eggs' directory.

We have successfully built our first extensible, pluggable application!

.. _`Python Eggs`: http://peak.telecommunity.com/DevCenter/PythonEggs

.. _acme.motd: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/api.py

.. _acme.motd.software_quotes: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/software_quotes/setup.py

.. _Application: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/enthought/envisage/application.py

.. _`examples/MOTD`: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD

.. _IApplication: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/enthought/envisage/i_application.py

.. _IMessage: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/i_message.py

.. _Message: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/message.py

.. _MOTD: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/motd.py

.. _IMOTD: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/i_motd.py

.. _MOTDPlugin: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/acme/motd/motd_plugin.py

.. _run.py: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD/run.py