Latest Version: 0.9.6.2
  Dashboard > Pylons Cookbook > ... > Extending Pylons > Cron jobs and command-line utilities
  Pylons Cookbook Log In | Sign Up   View a printable version of the current page.  
  Cron jobs and command-line utilities
Added by Mike Orr, last edited by Ian Bicking on Nov 10, 2007  (view change)
Labels: 
(None)

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.

Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators
Top