Paste Script: Development¶
- author:
Ian Bicking <ianb@colorstudy.com>
- revision:
$Rev$
- date:
$LastChangedDate$
Introduction¶
This document is an introduction to how you can extend paster and
Paste Script for your system – be it a framework, server setup, or
whatever else you want to do.
What Paste Script Can Do¶
paster is a two-level command, where the second level (e.g.,
paster help, paster create, etc) is pluggable.
Commands are attached to Python Eggs, i.e., to the package you distribute and someone installs. The commands are identified using entry points.
To make your command available do something like this in your
setup.py file:
from setuptools import setup
setup(...
entry_points="""
[paste.paster_command]
mycommand = mypackage.mycommand:MyCommand
[paste.global_paster_command]
myglobal = mypackage.myglobal:MyGlobalCommand
""")
This means that paster mycommand will run the MyCommand
command located in the mypackage.mycommand module. Similarly with
paster myglobal. The distinction between these two entry points
is that the first will only be usable when paster is run inside a
project that is identified as using your project, while the second
will be globally available as a command as soon as your package is
installed.
How’s the Local Thing Work?¶
So if you have a local command, how does it get enabled? If the
person is running paster inside their project directory,
paster will look in Project_Name.egg-info/paster_plugins.txt
which is a list of project names (the name of your package) whose
commands should be made available.
This is for frameworks, so frameworks can add commands to paster
that only apply to projects that use that framework.
What Do Commands Look Like?¶
The command objects (like MyCommand) are subclasses of
paste.script.command.Command. You can look at that class to get
an idea, but a basic outline looks like this:
from paste.script import command
class MyCommand(command.Command):
max_args = 1
min_args = 1
usage = "NAME"
summary = "Say hello!"
group_name = "My Package Name"
parser = command.Command.standard_parser(verbose=True)
parser.add_option('--goodbye',
action='store_true',
dest='goodbye',
help="Say 'Goodbye' instead")
def command(self):
name = self.args[0]
if self.verbose:
print "Got name: %r" % name
if self.options.goodbye:
print "Goodbye", name
else:
print "Hello", name
max_args and min_args are used to give error messages. You
can also raise command.BadCommand(msg) if the arguments are
incorrect in some way. (Use None here to give no restriction)
The usage variable means paster mycommand -h will give a usage
of paster mycommand [options] NAME. summary is used with
paster help (describing your command in a short form).
group_name is used to group commands together for paste help
under that title.
The parser object is an optparse
<http://python.org/doc/current/lib/module-optparse.html>
OptionParser object. Command.standard_parser is a class
method that creates normal options, and enables options based on these
keyword (boolean) arguments: verbose, interactive,
no_interactive (if interactive is the default), simulate,
quiet (undoes verbose), and overwrite. You can create the
parser however you want, but using standard_parser() encourages a
consistent set of shared options across commands.
When your command is run, .command() is called. As you can see,
the options are in self.options and the positional arguments are
in self.args. Some options are turned into instance variables –
especially self.verbose and self.simulate (even if you haven’t
chosen to use those options, many methods expect to find some value
there, which is why they are turned into instance variables).
There are quite a few useful methods you can use in your command. See the Command class for a complete list. Some particulars:
run_command(cmd, arg1, arg2, ..., cwd=os.getcwd(), capture_stderr=False):
Runs the command, respecting verbosity and simulation. Will raise an error if the command doesn’t exit with a 0 code.
insert_into_file(filename, marker_name, text, indent=False):
Inserts a line of text into the file, looking for a marker like
-*- marker_name -*-(and inserting just after it). Ifindent=True, then the line will be indented at the same level as the marker line.
ensure_dir(dir, svn_add=True):
Ensures that the directory exists. If
svn_addis true and the parent directory has an.svndirectory, add the new directory to Subversion.
ensure_file(filename, content, svn_add=True):
Ensure the file exists with the given content. Will ask the user before overwriting a file if
--interactivehas been given.
Templates¶
The other pluggable part is “templates”. These are used to create new
projects. Paste Script includes one template itself:
basic_package which creates a new setuptools package.
To enable, add to setup.py:
setup(...
entry_points="""
[paste.paster_create_template]
framework = framework.templates:FrameworkTemplate
""")
FrameworkTemplate should be a subclass of
paste.script.templates.Template. An easy way to do this is simply
with:
from paste.script import templates
class FrameworkTemplate(templates.Template):
egg_plugins = ['Framework']
summary = 'Template for creating a basic Framework package'
required_templates = ['basic_package']
_template_dir = 'template'
use_cheetah = True
egg_plugins will add Framework to paste_plugins.txt in the
package. required_template means those template will be run
before this one (so in this case you’ll have a complete package ready,
and you can just write your framework files to it). _template_dir
is a module-relative directory to find your source files.
The source files are just a directory of files that will be copied
into place, potentially with variable substitutions. Three variables
are expected: project is the project name (e.g., Project-Name),
package is the Python package in that project (e.g.,
projectname) and egg is the project’s egg name as generated by
setuptools (e.g., Project_Name). Users can add other variables by
adding foo=bar arguments to paster create.
Filenames are substituted with +var_name+, e.g., +package+ is
the package directory.
If a file in the template directory ends in _tmpl then it will be
substituted. If use_cheetah is true, then it’s treated as a
Cheetah template. Otherwise
string.Template is
used, though full expressions are allowed in ${expr} instead of
just variables.
See the templates module for more.