Skip to content

Commit 7e79618

Browse files
author
Dariusz Suchojad
committed
SESPRINGPYTHONPY-149: Sphinx docs - Plugins.
1 parent d8131a0 commit 7e79618

1 file changed

Lines changed: 361 additions & 1 deletion

File tree

docs/sphinx/source/plugins.rst

Lines changed: 361 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,362 @@
11
Spring Python's plugin system
2-
=============================
2+
=============================
3+
4+
Spring Python's plugin system is designed to help you rapidly develop applications.
5+
Plugin-based solutions have been proven to enhance developer efficiency, with
6+
examples such as `Grails <http://grails.org/>`_ and `Eclipse <http://eclipse.org/>`_
7+
being market leaders in usage and productivity.
8+
9+
This plugin solution was mainly inspired by the Grails demo presented by
10+
Graeme Rocher at the SpringOne Americas 2008 conference, in which he created
11+
a Twitter application in 40 minutes. Who wouldn't want to have something similar
12+
to support Spring Python development?
13+
14+
Introduction
15+
------------
16+
17+
Spring Python will manage an approved set of plugins. These are plugins written
18+
by the committers of Spring Python and are verified to work with an associated
19+
version of the library. These plugins are also hosted by the same services used
20+
to host Spring Python downloads, meaning they have the same level of support
21+
as Spring Python.
22+
23+
However, being an open source framework, developers have every right to code
24+
their own plugins. We fully support the concept of 3rd party plugins. We want
25+
to provide as much support in the way of documentation and extension points
26+
for you to develop your own plugins as well.
27+
28+
.. note::
29+
30+
Have you considered submitting your plugin as a Spring Extension?
31+
32+
`Spring Extensions <http://www.springsource.org/extensions>`_ is the official
33+
incubator process for SpringSource. You can
34+
always maintain your own plugin separately, using whatever means you wish. But
35+
if want to get a larger adoption of your plugin, name association with
36+
SpringSource, and perhaps one day becoming an official part of the software
37+
suite of SpringSource, you may want to consider looking into the Spring
38+
Extensions process.
39+
40+
41+
Coily - Spring Python's command-line tool
42+
-----------------------------------------
43+
44+
Coily is the command-line tool that utilizes the plugin system. It is similar
45+
to grails command-line tool, in that through a series of installed plugins,
46+
you are able to do many tasks, including build skeleton apps that you can later
47+
flesh out. If you look at the details of this app, you will find a sophisticated,
48+
command driven tool to built to manage plugins. The real power is in the
49+
plugins themselves.
50+
51+
Commands
52+
++++++++
53+
54+
.. highlight:: bash
55+
56+
To get started, all you need is a copy of coily installed in some directory located
57+
on your path::
58+
59+
% coily --help
60+
61+
The results should list available commands::
62+
63+
Coily - the command-line management tool for Spring Python
64+
==========================================================
65+
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
66+
Licensed under the Apache License, Version 2.0
67+
68+
69+
Usage: coily [command]
70+
71+
--help print this help message
72+
--list-installed-plugins list currently installed plugins
73+
--list-available-plugins list plugins available for download
74+
--install-plugin [name] install coily plugin
75+
--uninstall-plugin [name] uninstall coily plugin
76+
--reinstall-plugin [name] reinstall coily plugin
77+
78+
79+
* --help - Print out the help menu being displayed
80+
81+
* --list-installed-plugins - list the plugins currently installed in this
82+
account. It is important to know that each plugin creates a directly
83+
underneath the user's home directory in a hidden directory *~/.springpython*.
84+
If you delete this entire directory, you have effectively uninstalled all plugins.
85+
86+
* --list-available-plugins - list the plugins available for installation.
87+
Coily will check certain network locations, such as the S3 site used to host
88+
Spring Python downloads. It will also look on the local file system. This is
89+
in case you have a checked out copy of the plugins source code, and want to
90+
test things out without uploading to the network.
91+
92+
* --install-plugin - install the named plugin. In this case, you don't have to
93+
specify a version number. Coily will figure out which version of the plugin
94+
you need, download it if necessary, and finally copy it into *~/.springpython*.
95+
96+
* --uninstall-plugin - uninstall the named plugin by deleting its entry from *~/.springpython*
97+
98+
* --reinstall-plugin - uninstall then install the plugin. This is particulary
99+
useful if you are working on a plugin, and need a shortcut step to deploy.
100+
101+
In this case, no plugins have been installed yet. Every installed plugin will
102+
list itself as another available command to run. If you have already installed
103+
the *gen-cherrypy-app* plugin, you will see it listed::
104+
105+
Coily - the command-line management tool for Spring Python
106+
==========================================================
107+
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
108+
Licensed under the Apache License, Version 2.0
109+
110+
111+
Usage: coily [command]
112+
113+
--help print this help message
114+
--list-installed-plugins list currently installed plugins
115+
--list-available-plugins list plugins available for download
116+
--install-plugin [name] install coily plugin
117+
--uninstall-plugin [name] uninstall coily plugin
118+
--reinstall-plugin [name] reinstall coily plugin
119+
--gen-cherrypy-app [name] plugin to create skeleton CherryPy applications
120+
121+
You should notice an extra option listed at the bottom: *gen-cherrypy-app*
122+
is listed as another command with one argument. Later on, you can read
123+
official documentation on the existing plugins, and also how to write your own.
124+
125+
126+
Officially Supported Plugins
127+
----------------------------
128+
129+
This section documents plugins that are developed by the Spring Python team.
130+
131+
External dependencies
132+
+++++++++++++++++++++
133+
134+
*gen-cherrypy-app* plugin requires the installation of `CherryPy 3 <http://cherrypy.org/>`_.
135+
136+
gen-cherrypy-app
137+
++++++++++++++++
138+
139+
This plugin is used to generate a skeleton `CherryPy <http://cherrypy.org/>`_
140+
application based on feeding it a command-line argument::
141+
142+
% coily --gen-cherrypy-app twitterclone
143+
144+
This will generate a subdirectory *twitterclone* in the user's current directory.
145+
Inside twitterclone are several files, including *twitterclone.py*. If you run
146+
the app, you will see a working CherryPy application, with Spring Python
147+
security in place::
148+
149+
% cd twitterclone
150+
% python twitterclone.py
151+
152+
You can immediately start modifying it to put in your features.
153+
154+
Writing your own plugin
155+
-----------------------
156+
157+
Architecture of a plugin
158+
++++++++++++++++++++++++
159+
160+
.. highlight:: python
161+
162+
A plugin is pretty simple in structure. It is basically a Python package with
163+
some special things added on. *gen-cherrypy-app* plugin demonstrates this.
164+
165+
.. image:: gfx/gen-cherrypy-app-folder-struct.png
166+
:align: center
167+
168+
The special things needed to define a plugin are as follows:
169+
170+
* A root folder with the same name as your plugin and a *__init__.py*, making
171+
the plugin a Python package.
172+
173+
* A package-level variable named *__description__*
174+
This attribute should be assigned the string value description you want
175+
shown for your plugin when coily --help is run.
176+
177+
* A package-level function named either *create* or *apply*
178+
179+
* If your plugin needs one command line argument, define a *create* method with the following signature::
180+
181+
def create(plugin_path, name)
182+
183+
* If your plugin doesn't need any arguments, define an *apply* method with the following signature::
184+
185+
def apply(plugin_path)
186+
187+
In either case, your plugin gets passed an extra argument, plugin_path,
188+
which contains the directory the plugin is actually installed in. This is
189+
typically so you can reference other files your plugin needs access to.
190+
191+
.. note::
192+
193+
What does "package-level" mean?
194+
195+
The code needs to be in the __init__.py file. This file makes the enclosing
196+
directory a Python package.
197+
198+
Case Study - gen-cherrypy-app plugin
199+
++++++++++++++++++++++++++++++++++++
200+
201+
*gen-cherrypy-app* is a plugin used to build a `CherryPy <http://cherrypy.org/>`_ web application using
202+
Spring Python's feature set. It saves the developer from having to re-configure
203+
Spring Python's security module, coding CherryPy's engine, and so forth. This
204+
allows the developer to immediately start writing business code against a
205+
working application.
206+
207+
Using this plugin, we will de-construct this simple, template-based plugin.
208+
This will involve looking line-by-line at *gen-cherrypy-app/__init__.py*.
209+
210+
Source Code
211+
>>>>>>>>>>>
212+
213+
::
214+
215+
"""
216+
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
217+
218+
Licensed under the Apache License, Version 2.0 (the "License");
219+
you may not use this file except in compliance with the License.
220+
You may obtain a copy of the License at
221+
222+
http://www.apache.org/licenses/LICENSE-2.0
223+
224+
Unless required by applicable law or agreed to in writing, software
225+
distributed under the License is distributed on an "AS IS" BASIS,
226+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
227+
See the License for the specific language governing permissions and
228+
limitations under the License.
229+
"""
230+
import re
231+
import os
232+
import shutil
233+
234+
__description__ = "plugin to create skeleton CherryPy applications"
235+
236+
def create(plugin_path, name):
237+
if not os.path.exists(name):
238+
print "Creating CherryPy skeleton app %s" % name
239+
os.makedirs(name)
240+
241+
# Copy/transform the template files
242+
for file_name in ["cherrypy-app.py", "controller.py", "view.py", "app_context.py"]:
243+
input_file = open(plugin_path + "/" + file_name).read()
244+
245+
# Iterate over a list of patterns, performing string substitution on the input file
246+
patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])]
247+
for pattern, replacement in patterns_to_replace:
248+
input_file = re.compile(r"\$\{%s}" % pattern).sub(replacement, input_file)
249+
250+
output_filename = name + "/" + file_name
251+
if file_name == "cherrypy-app.py":
252+
output_filename = name + "/" + name + ".py"
253+
254+
app = open(output_filename, "w")
255+
app.write(input_file)
256+
app.close()
257+
258+
# Recursively copy other parts
259+
shutil.copytree(plugin_path + "/images", name + "/" + "images")
260+
else:
261+
print "There is already something called %s. ABORT!" % name
262+
263+
264+
Deconstructing the factory
265+
>>>>>>>>>>>>>>>>>>>>>>>>>>
266+
267+
* The opening section shows the copyright statement, which should tip you off
268+
that this is an official plugin.
269+
270+
* __description__ is a required variable::
271+
272+
__description__ = "plugin to create skeleton CherryPy applications"
273+
274+
It contains the description displayed when a user runs::
275+
276+
% coily --help
277+
278+
::
279+
280+
Usage: coily [command]
281+
...
282+
--gen-cherrypy-app [name] plugin to create skeleton CherryPy applications
283+
284+
* Opening line defines create with two arguments::
285+
286+
def create(plugin_path, name):
287+
288+
The arguments allow both the plugin path to be fed along with the command-line
289+
argument that is filled in when the user runs the command::
290+
291+
% coily --gen-cherrypy-app [name]
292+
293+
It is important to realize that *plugin_path* is needed in case the plugin
294+
needs to refer to any files inside its installed directory. This is because
295+
plugins are not installed anywhere on the *PYTHONPATH*, but instead, in the
296+
user's home directory underneath *~/.springpython*.
297+
298+
This mechanism was chosen because it gives users an easy ability to pick
299+
which plugins they wish to use, without requiring system admin power. It also
300+
eliminates the need to deal with multiple versions of plugins being installed
301+
on your *PYTHONPATH*. This provides maximum flexibility which is needed in a
302+
development environment.
303+
304+
* This plugin works by creating a directory in the user's current working directory,
305+
and putting all relevant files into it. The argument passed into the command-line
306+
is used as the name of an application, and the directory created has the same name::
307+
308+
if not os.path.exists(name):
309+
print "Creating CherryPy skeleton app %s" % name
310+
os.makedirs(name)
311+
312+
However, if the directory already exists, it won't proceed::
313+
314+
else:
315+
print "There is already something called %s. ABORT!" % name
316+
317+
* This plugin then iterates over a list of filenames, which happen to match the
318+
names of files found in the plugin's directory. These are essentially template
319+
files, intended to be copied into the target directory. However, the files
320+
are not copied directly. Instead they are opened and read into memory::
321+
322+
# Copy/transform the template files
323+
for file_name in ["cherrypy-app.py", "controller.py", "view.py", "app_context.py"]:
324+
input_file = open(plugin_path + "/" + file_name).read()
325+
326+
Then, the contents are scanned for key phrases, and substituted. In this case,
327+
the substitution is a variant of the name of the application being generated::
328+
329+
# Iterate over a list of patterns, performing string substitution on the input file
330+
patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])]
331+
for pattern, replacement in patterns_to_replace:
332+
input_file = re.compile(r"\$\{%s}" % pattern).sub(replacement, input_file)
333+
334+
The substituted content is written to a new output file. In most cases,
335+
the original filename is also the target filename. However, the key file,
336+
*cherrypy-app.py* is renamed to the application's name::
337+
338+
output_filename = name + "/" + file_name
339+
if file_name == "cherrypy-app.py":
340+
output_filename = name + "/" + name + ".py"
341+
342+
app = open(output_filename, "w")
343+
app.write(input_file)
344+
app.close()
345+
346+
* Finally, the images directory is recursively copied into the target directory::
347+
348+
# Recursively copy other parts
349+
shutil.copytree(plugin_path + "/images", name + "/" + "images")
350+
351+
Summary
352+
>>>>>>>
353+
354+
All these steps effectively copy a set of files used to template an application.
355+
With this template approach, the major effort of developing this plugin is spent
356+
working on the templates themselves, not on this template factory. While this is
357+
mostly working with python code for a python solution, the fact that this is a
358+
template requires reinstalling the plugin everytime a change is made in order
359+
to test them.
360+
361+
Users are welcome to use *gen-cherypy-app*'s *__init__.py* file to generate their
362+
own template solutions, and work on other skeleton tools or solutions.

0 commit comments

Comments
 (0)