Spring Python’s plugin system is designed to help you rapidly develop applications. Plugin-based solutions have been proven to enhance developer efficiency, with examples such as Grails and Eclipse being market leaders in usage and productivity.
This plugin solution was mainly inspired by the Grails demo presented by Graeme Rocher at the SpringOne Americas 2008 conference, in which he created a Twitter application in 40 minutes. Who wouldn’t want to have something similar to support Spring Python development?
Spring Python will manage an approved set of plugins. These are plugins written by the committers of Spring Python and are verified to work with an associated version of the library. These plugins are also hosted by the same services used to host Spring Python downloads, meaning they have the same level of support as Spring Python.
However, being an open source framework, developers have every right to code their own plugins. We fully support the concept of 3rd party plugins. We want to provide as much support in the way of documentation and extension points for you to develop your own plugins as well.
Note
Have you considered submitting your plugin as a Spring Extension?
Spring Extensions is the official incubator process for SpringSource. You can always maintain your own plugin separately, using whatever means you wish. But if want to get a larger adoption of your plugin, name association with SpringSource, and perhaps one day becoming an official part of the software suite of SpringSource, you may want to consider looking into the Spring Extensions process.
Coily is the command-line tool that utilizes the plugin system. It is similar to grails command-line tool, in that through a series of installed plugins, you are able to do many tasks, including build skeleton apps that you can later flesh out. If you look at the details of this app, you will find a sophisticated, command driven tool to built to manage plugins. The real power is in the plugins themselves.
To get started, all you need is a copy of coily installed in some directory located on your path:
% coily --help
The results should list available commands:
Coily - the command-line management tool for Spring Python
==========================================================
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
Licensed under the Apache License, Version 2.0
Usage: coily [command]
--help print this help message
--list-installed-plugins list currently installed plugins
--list-available-plugins list plugins available for download
--install-plugin [name] install coily plugin
--uninstall-plugin [name] uninstall coily plugin
--reinstall-plugin [name] reinstall coily plugin
In this case, no plugins have been installed yet. Every installed plugin will list itself as another available command to run. If you have already installed the gen-cherrypy-app plugin, you will see it listed:
Coily - the command-line management tool for Spring Python
==========================================================
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
Licensed under the Apache License, Version 2.0
Usage: coily [command]
--help print this help message
--list-installed-plugins list currently installed plugins
--list-available-plugins list plugins available for download
--install-plugin [name] install coily plugin
--uninstall-plugin [name] uninstall coily plugin
--reinstall-plugin [name] reinstall coily plugin
--gen-cherrypy-app [name] plugin to create skeleton CherryPy applications
You should notice an extra option listed at the bottom: gen-cherrypy-app is listed as another command with one argument. Later on, you can read official documentation on the existing plugins, and also how to write your own.
This section documents plugins that are developed by the Spring Python team.
gen-cherrypy-app plugin requires the installation of CherryPy 3.
This plugin is used to generate a skeleton CherryPy application based on feeding it a command-line argument:
% coily --gen-cherrypy-app twitterclone
This will generate a subdirectory twitterclone in the user’s current directory. Inside twitterclone are several files, including twitterclone.py. If you run the app, you will see a working CherryPy application, with Spring Python security in place:
% cd twitterclone
% python twitterclone.py
You can immediately start modifying it to put in your features.
A plugin is pretty simple in structure. It is basically a Python package with some special things added on. gen-cherrypy-app plugin demonstrates this.
The special things needed to define a plugin are as follows:
A root folder with the same name as your plugin and a __init__.py, making the plugin a Python package.
A package-level variable named __description__ This attribute should be assigned the string value description you want shown for your plugin when coily –help is run.
A package-level function named either create or apply
If your plugin needs one command line argument, define a create method with the following signature:
def create(plugin_path, name)
If your plugin doesn’t need any arguments, define an apply method with the following signature:
def apply(plugin_path)
In either case, your plugin gets passed an extra argument, plugin_path, which contains the directory the plugin is actually installed in. This is typically so you can reference other files your plugin needs access to.
Note
What does “package-level” mean?
The code needs to be in the __init__.py file. This file makes the enclosing directory a Python package.
gen-cherrypy-app is a plugin used to build a CherryPy web application using Spring Python’s feature set. It saves the developer from having to re-configure Spring Python’s security module, coding CherryPy’s engine, and so forth. This allows the developer to immediately start writing business code against a working application.
Using this plugin, we will de-construct this simple, template-based plugin. This will involve looking line-by-line at gen-cherrypy-app/__init__.py.
"""
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
import os
import shutil
__description__ = "plugin to create skeleton CherryPy applications"
def create(plugin_path, name):
if not os.path.exists(name):
print "Creating CherryPy skeleton app %s" % name
os.makedirs(name)
# Copy/transform the template files
for file_name in ["cherrypy-app.py", "controller.py", "view.py", "app_context.py"]:
input_file = open(plugin_path + "/" + file_name).read()
# Iterate over a list of patterns, performing string substitution on the input file
patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])]
for pattern, replacement in patterns_to_replace:
input_file = re.compile(r"\$\{%s}" % pattern).sub(replacement, input_file)
output_filename = name + "/" + file_name
if file_name == "cherrypy-app.py":
output_filename = name + "/" + name + ".py"
app = open(output_filename, "w")
app.write(input_file)
app.close()
# Recursively copy other parts
shutil.copytree(plugin_path + "/images", name + "/" + "images")
else:
print "There is already something called %s. ABORT!" % name
The opening section shows the copyright statement, which should tip you off that this is an official plugin.
__description__ is a required variable:
__description__ = "plugin to create skeleton CherryPy applications"
It contains the description displayed when a user runs:
% coily --help
Usage: coily [command]
...
--gen-cherrypy-app [name] plugin to create skeleton CherryPy applications
Opening line defines create with two arguments:
def create(plugin_path, name):
The arguments allow both the plugin path to be fed along with the command-line argument that is filled in when the user runs the command:
% coily --gen-cherrypy-app [name]
It is important to realize that plugin_path is needed in case the plugin needs to refer to any files inside its installed directory. This is because plugins are not installed anywhere on the PYTHONPATH, but instead, in the user’s home directory underneath ~/.springpython.
This mechanism was chosen because it gives users an easy ability to pick which plugins they wish to use, without requiring system admin power. It also eliminates the need to deal with multiple versions of plugins being installed on your PYTHONPATH. This provides maximum flexibility which is needed in a development environment.
This plugin works by creating a directory in the user’s current working directory, and putting all relevant files into it. The argument passed into the command-line is used as the name of an application, and the directory created has the same name:
if not os.path.exists(name):
print "Creating CherryPy skeleton app %s" % name
os.makedirs(name)
However, if the directory already exists, it won’t proceed:
else:
print "There is already something called %s. ABORT!" % name
This plugin then iterates over a list of filenames, which happen to match the names of files found in the plugin’s directory. These are essentially template files, intended to be copied into the target directory. However, the files are not copied directly. Instead they are opened and read into memory:
# Copy/transform the template files
for file_name in ["cherrypy-app.py", "controller.py", "view.py", "app_context.py"]:
input_file = open(plugin_path + "/" + file_name).read()
Then, the contents are scanned for key phrases, and substituted. In this case, the substitution is a variant of the name of the application being generated:
# Iterate over a list of patterns, performing string substitution on the input file
patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])]
for pattern, replacement in patterns_to_replace:
input_file = re.compile(r"\$\{%s}" % pattern).sub(replacement, input_file)
The substituted content is written to a new output file. In most cases, the original filename is also the target filename. However, the key file, cherrypy-app.py is renamed to the application’s name:
output_filename = name + "/" + file_name
if file_name == "cherrypy-app.py":
output_filename = name + "/" + name + ".py"
app = open(output_filename, "w")
app.write(input_file)
app.close()
Finally, the images directory is recursively copied into the target directory:
# Recursively copy other parts
shutil.copytree(plugin_path + "/images", name + "/" + "images")
All these steps effectively copy a set of files used to template an application. With this template approach, the major effort of developing this plugin is spent working on the templates themselves, not on this template factory. While this is mostly working with python code for a python solution, the fact that this is a template requires reinstalling the plugin everytime a change is made in order to test them.
Users are welcome to use gen-cherypy-app‘s __init__.py file to generate their own template solutions, and work on other skeleton tools or solutions.