Some Pylons applications need cron jobs or command-line maintenance scripts. There are two ways to do this: put the script in the egg as a global 'bin' program, or add a command to paster.
Egg scripts (bin programs)
See Automatic Script Creation
in the Setuptools manual. This makes easy_install create a wrapper script in your global 'bin' directory that runs a function of your choosing. Sometimes it's undesirable to put application-specific scripts in the global 'bin' directory; in that case use one of the approaches below.
python -m
In Python 2.5, "python -m MYPACKAGE.MYMODULE" looks up the indicated module and runs it as a script. So if you organize all your utilities under "myapp.scripts", you can run them as "python -m myapp.scripts.my_script".
In Python 2.4, only top-level modules can be run this way. This makes it not very useful for application-specific scripts because they would have to be global modules inside your egg but outside the 'myapp' package, cluttering up the global module namespace for programs unrelated to this one.
Paster command
The command line will be "paster my-command arg1 arg2" if the current directory is your application egg, or "paster --plugin=MyPylonsApp my-command arg1 arg2" otherwise. In the latter case, MyPylonsApp must have been installed via easy_install or "python setup.py develop".
Make a package directory for your commands:
$ mkdir myapp/commands
$ touch myapp/commands/__init__.py
Create a module myapp/commands/my_command.py like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | from paste.script.command import Command
class MyCommand(Command):
# Parser configuration
summary = "--NO SUMMARY--"
usage = "--NO USAGE--"
group_name = "myapp"
parser = Command.standard_parser(verbose=False)
def command(self):
import pprint
print "Hello, app script world!"
print
print "My options are:"
print " ", pprint.pformat(vars(self.options))
print "My args are:"
print " ", pprint.pformat(self.args)
print
print "My parser help is:"
print
print self.parser.format_help()
|
 |
Your class must define .command, .parser, and .summary! |
Modify the entry_points argument in setup.py to contain:
[paste.paster_command]
my-command = myapp.commands.my_command:MyCommand
Run "python setup.py develop" or "easy_install ." to update the entry points in the egg in sys.path.
Now you should be able to run:
$ paster --plugin=MyApp my-command arg1 arg2
Hello, MyApp script world!
My options are:
{'interactive': False, 'overwrite': False, 'quiet': 0, 'verbose': 0}
My args are:
['arg1', 'arg2']
My parser help is:
Usage: /usr/local/bin/paster my-command [options] --NO USAGE--
--NO SUMMARY--
Options:
-h, --help show this help message and exit
$ paster --plugin=MyApp --help
Usage: paster [paster_options] COMMAND [command_options]
...
myapp:
my-command --NO SUMMARY--
pylons:
controller Create a Controller and accompanying functional test
restcontroller Create a REST Controller and accompanying functional test
shell Open an interactive shell with the Pylons app loaded
Required class attributes
In addition to the .command method, your class should define .parser and .summary.
Command-line options
Command.standard_parser() returns a Python OptionParser. You can call parser.add_option and add as many options as you like. Inside the .command method, the user's options are available under self.options, and any additional arguments are in self.args.
There are several other class attributes that affect the parser; see them defined in paste.script.command:Command. The most useful attributes are .usage, .description, .min_args, and .max_args. .usage is the part of the usage string after the command name. The .standard_parser() method has several optional arguments to add standardized options; some of these got added to my parser although I don't see how.
See the "paster shell" command, pylons.commands:ShellCommand, for an example of using command-line options and loading the .ini file and model. (XXX We need a generic base class that does all or some of this for standalone scripts.)
"paster setup-app" is defined in paste.script.appinstall.SetupCommand. We know this from the entry point in PasteScript (PasteScript-VERSION.egg/EGG_INFO/entry_points.txt). It's a complex example of reading a config file and delegating to another entry point. The code for calling myapp.websetup:setup_config is in paste.script.appinstall somewhere, though I couldn't figure it out.
The Command class also has several convenience methods to prompt the user on the console, enable logging, verify directories exist and files have expected content, insert text into a file, run a shell command, add files to Subversion, parse "var=value" arguments, add variables to an .ini file.
Future possibilities
(Note: this has been implemented in PasteScript trunk)
Ian Bicking, author of Paste, wrote on 2007-11-10:
This has kind of been hanging for a long time. I think I'm going to add
a command:
paster request config.ini /
Which will run a request for / against the application described in
config.ini. And then, maybe allow the ini file to have something like:
[command_urls]
/foobar = description of what / does
/foobar alias = foobar
Then you could do:
paster request config.ini -h # get a list of urls in [urls]
paster request config.ini foobar # from the alias
Though... that's not ideal. Cloning that [urls] section everywhere is a
pain. Maybe instead it should have a default URL of /.command-line/,
and "paster request config.ini -h" calls /.command-line/?-h, and "paster
request config.ini foobar" calls /.command-line/foobar.
There would be a special key set to mark command-line access vs. web
access, so you could restrict access appropriately.
This has been a hanging issue for a long time, so it's really something
I should just implement. To be "nice" in Pylons it would also require a
Controller subclass that was customized to this kind of usage.
I added the commands "paster request" and "paster post" to PasteScript
trunk. If you do "easy_install PasteScript==dev" you should have them
available.
Since it does requests, your commands will be run in the full
configuration context of a normal application. Useful for cron jobs,
the error handler will also be in place and you can get email reports of
failed requests.
Because arguments all just go in QUERY_STRING, request.GET and
request.PARAMS won't look like you expect. But you can parse them with
something like:
1
2
3
4
5
6 | parser = optparse.OptionParser()
parser.add_option(etc)
args = [item[0] for item in
cgi.parse_qsl(request.environ['QUERY_STRING'])]
options, args = parser.parse_args(args)
|
Also environ['paste.command_request'] will be true, so you can block
public access.
A Pylons controller that handled some of this would probably be quite
useful. Probably even nicer with additions to the current template, so
that /.command/ all gets routed to a single controller that uses actions
for the various sub-commands, and can provide a useful response to
/.command/?-h, etc.