Python, with its multi-paradigm nature, makes it easy to build applications as well as provide scripting support to things.
This recipe will help us explore building a project-level script that allows us to run different test suites. We will also show some extra command-line options to create hooks for packaging, publishing, registering, and writing automated documentation.
recipe15.py
that parses a set of options using Python's getopt
library.import getopt import glob import logging import nose import os import os.path import pydoc import re import sys def usage(): print print "Usage: python recipe15.py [command]" print print " --help" print " --test" print " --suite [suite]" print " --debug-level [info|debug]" print " --package" print " --publish" print " --register" print " --pydoc" print try: optlist, args = getopt.getopt(sys.argv[1:], "ht", ["help", "test", "suite=", "debug-level=", "package", "publish", "register", "pydoc"]) except getopt.GetoptError: # print help information and exit: print "Invalid command found in %s" % sys.argv usage() sys.exit(2)
–test
.def test(test_suite, debug_level): logger = logging.getLogger("recipe15") loggingLevel = debug_level logger.setLevel(loggingLevel) ch = logging.StreamHandler() ch.setLevel(loggingLevel) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) nose.run(argv=["", test_suite, "--verbosity=2"])
package
, publish
, and register
.def package(): print "This is where we can plug in code to run " + "setup.py to generate a bundle." def publish(): print "This is where we can plug in code to upload " + "our tarball to S3 or some other download site." def register(): print "setup.py has a built in function to " + "'register' a release to PyPI. It's " + "convenient to put a hook in here." # os.system("%s setup.py register" % sys.executable)
pydoc
module.def create_pydocs(): print "It's useful to use pydoc to generate docs." pydoc_dir = "pydoc" module = "recipe15_all" __import__(module) if not os.path.exists(pydoc_dir): os.mkdir(pydoc_dir) cur = os.getcwd() os.chdir(pydoc_dir) pydoc.writedoc("recipe15_all") os.chdir(cur)
debug_levels = {"info":logging.INFO, "debug":logging.DEBUG} # Default debug level is INFO debug_level = debug_levels["info"] for option in optlist: if option[0] in ("--debug-level"): # Override with a user-supplied debug level debug_level = debug_levels[option[1]]
–help
, and if found, exits the script.# Check for help requests, which cause all other # options to be ignored. for option in optlist: if option[0] in ("--help", "-h"): usage() sys.exit(1)
# Parse the arguments, in order for option in optlist: if option[0] in ("--test"): print "Running recipe15_checkin tests..." test("recipe15_checkin", debug_level) if option[0] in ("--suite"): print "Running test suite %s..." % option[1] test(option[1], debug_level) if option[0] in ("--package"): package() if option[0] in ("--publish"): publish() if option[0] in ("--register"): register() if option[0] in ("--pydoc"): create_pydocs()
recipe15.py
script with –help
.recipe15_checkin.py
to create a new test suite.check
in
test suite.import recipe11 class Recipe11Test(recipe11.ShoppingCartTest): pass
recipe15.py
script, using –test –package –publish –register –pydoc
. In the following screenshot, do you notice how it exercises each option in the same sequence as it was supplied on the command line?pydoc
directory.recipe15_all.py
to define another new test suite.all
test suite.import recipe11 import recipe12 import recipe13 import recipe14 class Recipe11Test(recipe11.ShoppingCartTest): pass class Recipe12Test(recipe12.ShoppingCartTest): pass class Recipe13Test(recipe13.ShoppingCartTest): pass class Recipe14Test(recipe14.ShoppingCartTest): pass
recipe15.py
script with –suite=recipe15_all
.This script uses Python's getopt
library, which is modeled after the C programming language's getopt()
function. This means we use the API to define a set of commands, and then we iterate over the options, calling corresponding functions.
Visit http://docs.python.org/library/getopt.html for more details on the getopt
library.
usage
: This is a function to provide help to the user.key
: The option definitions are included in the following block:optlist, args = getopt.getopt(sys.argv[1:], "ht", ["help", "test", "suite=", "debug-level=", "package", "publish", "register", "pydoc"])
"ht"
defined the short options: -h
and –t
."="
accept an argument. Those without are flags.usage()
, and then exit.test
: This activates loggers, which can be very useful if our app uses Python's logging
library.package
: This generates tarballs. We created a stub, but it can be handy to provide a shortcut by running setup.py sdist|bdist
.publish
: Its function is to push tarballs to the deployment site. We created a stub, but deploying it to an S3 site or somewhere else is useful.register
: This registers the module with PyPI. We created a stub, but it would be handy to provide a shortcut to running setup.py register
.create_pydocs
: They are the auto-generated docs. Generating HTML files based on code is very convenient.With each of these functions defined, we can iterate over the options that were parsed. For this script, there is a sequence as follows:
logging.INFO
, but provide the ability to switch to logging.DEBUG
.-h
or –help
was called. If so, print out the usage()
information and then exit with no more parsing.To exercise things, we first called this script with the –help
option. That printed out the command choices we had.
Then we called it with all the options to demonstrate the features. The script is coded to exercise a check
in
suite when we use –test
. This is a short test suite, which simulates running a quicker test meant to tell if things look alright.
Finally, we called the script with –suite=recipe15_all
. This test suite simulates running a more complete test suite that typically takes longer.
The features which this script provides could easily be handled by commands that are already built. We looked at nosetests
earlier in this chapter and saw how it can flexibly take arguments to pick tests.
Using setup.py
to generate tarballs and register releases is also a commonly used feature in the Python community.
So why write this script? Because we can tap into all of these features with a single command script, as setup.py
contains a prebuilt set of commands that involve bundling and uploading to the Python Project Index. Doing other tasks like generating pydocs, deploying to another location like an Amazon S3 bucket, or any other system level task is not included. This script demonstrates how easy it is to wire in other command-line options and link them with the project management functions.
We can also conveniently embed the usage of pydoc
. Basically, any Python library that serves project management needs can be embedded as well.
On an existing project, I developed a script to provide a unified way to embed version info into a templated setup.py
as well as documentation generated by pydoc
, sphinx
, and DocBook
. The script saved me from having to remember all the commands needed to manage the project.
Why didn't I extend distutils
to create my own commands? It was personally a matter of taste. I preferred using getopt
and working outside the framework of distutils
instead of creating and registering new subcommands.
Python has several options to handle command-line option parsing. getopt
is possibly the simplest. It is meant to quickly allow defining short and long options, but it has limits. It requires custom coding help output, as we did with the usage function.
It also requires custom handling of the arguments. optparse
provides more sophisticated options, such as better handling of arguments and auto-built help. But it also requires more code to get functional. optparse
is also targeted to be replaced by argparse
in the future.
It is left as an exercise for you to write an alternative version of this script using optparse
to assess which one is a better solution.
3.145.166.149