File: extension_points.rst

package info (click to toggle)
python-envisagecore 3.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 1,096 kB
  • ctags: 1,063
  • sloc: python: 4,115; makefile: 7; sh: 5
file content (275 lines) | stat: -rw-r--r-- 9,990 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
Extension Points
================

Whether or not we as software developers like to admit it, most (if not all) of
the applications we write need to change over time. We fix bugs, and we add, 
modify, and remove features. In other words, we spend most of our time either
fixing or extending applications.

Sometimes we extend our applications by changing the actual code, sometimes
we have other ad hoc extension mechanisms in place -- a text file here,
a directory of scripts there. As applications grow, they often end up with
numerous places where they can be extended, but with a different extension
mechanism at each one. This makes it hard for developers who want to extend
the application to know a) *where* they can add extensions, and b) *how*
to add them.

Envisage attempts to address this problem by admitting up front that 
applications need to be extensible, and by providing a standard way for
developers to advertise the places where extension can occur (known as
*extension points*), and for other developers to contribute *extensions* to
them.

In Envisage, extension points and the extensions contributed to them are stored
in the *extension registry*. To see how extension points actually work, let's
take a look at the `Message of the Day`_ example included in the Envisage
distribution. This example shows how to build a very simple application that
prints a (hopefully witty, educational, or inspiring) "Message of the Day"
chosen at random from a list of contributed messages.

1) Declaring an Extension Point
-------------------------------

Plugins declare their extension points in one of two ways:

1) Declaratively - using the 'ExtensionPoint' trait type
2) Programmatically - by overriding the 'get_extension_points' method.

In the MOTD example, the acme.motd_ plugin needs to advertise an extension
point that allows other plugins to contribute new messages. Using the
'ExtensionPoint' trait type, the plugin would look like this::

    class MOTDPlugin(Plugin):
        """ The MOTD Plugin. """"

	...

	# The messages extension point.
	messages = ExtensionPoint(
            List(IMessage), id='acme.motd.messages', desc = """

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

            """
        )

	...

Overriding the 'get_extension_points' method might look somthing like::

    class MOTDPlugin(Plugin):
        """ The MOTD Plugin. """"

	...

	def get_extension_points(self):
            """ Return the plugin's extension points. """

     	    messages = ExtensionPoint(
                List(IMessage), id='acme.motd.messages', desc = """

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

                """
            )

            return [messages]

	...


Either way, this tells us three things about the extension point:

1) That the extension point is called "acme.motd.messages"
2) That every item in a list of contributions to the extension point must
   implement the IMessage_ interface.
3) That the extension point allows you to contribute messages!

2) Making contributions to an Extension Point
---------------------------------------------

The `Message of the Day`_ example has a second plugin,
acme.motd.software_quotes_ that contributes some pithy quotes about software
development to the application.

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. Again there are are
two ways that a plugin can do this:

1) Declaratively - using the 'contributes_to' trait metadata
2) Programmatically - by overriding the 'get_extensions' method.

The declarative version looks like this::

    class SoftwareQuotesPlugin(Plugin):
        """ The software quotes plugin. """

        ...

        # The 'contributes_to' trait metadata tells Envisage the ID of the
        # extension point that this trait contributes to.
	messages = List(contributes_to='acme.motd.messages')

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

	    # It is good practise to only import your extensions when they
	    # are actually required.
	    from messages import messages

	    return messages

	...

The messages are contributed simply by creating a list trait and setting its
"contributes_to" metadata to the ID of the extension point that we want to
contribute to. All we have to do then is to intialize the trait with our
messages and "Job done"!

Note that if a plugin changes a list of contributions then the extension
registry will be updated automatically, and anybody that is consuming the
extensions will be notified accordingly.

The programmatic version looks like this::

    class SoftwareQuotesPlugin(Plugin):
        """ The software quotes plugin. """

        ...

	def get_extensions(self, extension_point_id):
            """ Get the plugin's contributions to an extension point. """

	    if extension_point_id == 'acme.motd.messages':
	        from messages import messages

                extensions = messages

	    else:
                extensions = []

            return extensions

	...

The difference between this and the declarative version is that the application
is not automatically notified if the plugin wants to change its contributions
to an extension point. To do this manually fire an 'extension_point_changed'
event.

3) Retrieving the contributions to an Extension Point
-----------------------------------------------------

OK, here's where we are so far: One plugin (acme.motd_) has advertised the fact
that it has an extension point called "acme.motd.messages", and that the
contributions to the extension point must implement the IMessage_ interface.
Another plugin (acme.motd.software_quotes_) has kindly offered to contribute
some messages about software development. Now we need to know how to retrieve
the contributed messages at runtime.

In the MOTD example, the messages are retrieved by the acme.motd_ plugin::

    class MOTDPlugin(Plugin):
        """ The MOTD Plugin. """"

	...

	# The messages extension point.
	messages = ExtensionPoint(
            List(IMessage), id='acme.motd.messages', desc = """

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

            """
        )

	...

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

            # Only do imports when you need to!
            from motd import MOTD

            return MOTD(messages=self.messages)

            ...

As you can see, all we have to do is to access the **messages** extension point
trait when we create our instance of the MOTD_ class.

This example demonstrates a common pattern in Envisage application development,
in that contributions to extension points are most often used by plugin
implementations to create and initialize services (in this case, an instance of
the MOTD_ class).

The extension registry can also be accessed through the following method on the
IApplication_ interface::

    def get_extensions(self, extension_point):
        """ Return a list containing all contributions to an extension point.

        Return an empty list if the extension point does not exist.

        """

For example, to get the messages contributed to the "acme.motd.messages"
extension point you would use::

    messages = application.get_extensions('acme.motd.messages')

Note however, that using the ExtensionPoint_ trait type, adds the ability to
validate the contributions -- in this case, to make sure that they are all
objects that implement (or can be adapted to) the IMessage_ interface. It also
automatically connects the trait so that the plugin will receive trait change
events if extensions are added/removed to/from the extension point at runtime.


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

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

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

.. _ExtensionPoint: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/enthought/envisage/extension_point.py

.. _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

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

.. _`Message of the Day`: https://svn.enthought.com/enthought/browser/EnvisageCore/trunk/examples/MOTD

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

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