UnixReview.com
May 2006
Regular Expressions: CherryPy proves its worth
by Miki Tebeka and Cameron LairdSo they've asked you for a "small" internal office application. Just a simple addressbook — how complicated can it be?
You're experienced enough to know better. Sure, programming it will be easy but:
- You'll need to package it.
- You'll need to provide an installer.
- You'll need to support multiple platforms.
- There will be more features and you'll need to upgrade.
As previous installments of Regular Expressions have mentioned, the magnitude of "simple" applications multiplies when everything after the programming is included. One way to regain a lot of simplicity, though, as we've earlier advocated, is to choose Web applications for your solutions because:
- No need to package
- No need to provide installer
- Multiple platforms supported
- Updates show up instantly
Maybe you're familiar with all these arguments, but other work has prevented you from learning Web development yourself. Is there a gentle way to break yourself in?
Web FrameworksSure! There are many, in fact. Web development has come a long way in the past 15 years. "Web development frameworks" provide just the right abstraction level for the job before us. This month, we'll take an easy tour of the Python-based CherryPy Web development framework and Cheetah "template engine". Please note that there are many, many other frameworks, even if you restrict yourself to a single development language like Python, PHP, or Perl. Deciding between them is a challenge. The good news, though, is that they largely rely on the same concepts, so much of what you learn with CherryPy, for example, would help you use Ruby on Rails or AOLserver. In fact, Cheetah's templating is sufficiently general to have applications even outside Web programming: you can use it as a code generator for clumsy C++ programming, for example.
Installation of CherryPy and Cheetah follows Python standards: retrieve the source distributions, unpack, and python setup.py install
. Should you ever need Web service outside the Unix world, both packages also provide Windows installers.
Start with the user interface; this will give you immediate practice with the underlying engine's capabilities and conventions.
Cheetah interprets source files which embed both HTML and Python. Begin by creating a "master page" that sets the layout of all other pages:
#from time import ctime ## Header <html> <head> <title> LoonyTunes AddressBook </title> <head> <body> <center> <h1> LoonyTunes AddressBook </h1> </center> ## Body #block body ## Show error if this page is shown <h1><center><font color="RED"> OOOPS! </font></center></h1> #end block body ## Footer <hr /> <font size="-1"> Generated $ctime() by <a href="mailto:[email protected]">duffy</a> </font> </body>Notice that we import the
ctime
Python library for use in the footer. We also define a block
for the the body of displayed pages; inheriting pages do their work by overriding this block
. A block
, delimited, conveniently enough, with #block
, is a section of the template that can be selectively re-implemented in a subclass. Blocks are useful for managing parts of a template more rationally than by a copy-paste-edit of the template as a whole. Think of blocks as object methods whose inheriting classes may override with their own implementations.
Next, we write an index page, derived, of course, from the master:
#extends master ## Default attributes #attr $results = None #attr $query = "" #block body ## Search form <form> Query: <br /> <input type="text" name="query" value="$query" size="50"/> <br /> ## Search within results #if $results <input type="checkbox" name="within" />Search in results <br /> #end if <input type="submit" value="Search" /> </form> ## Query without hits #if (not $results) and $query No matches found for $query #end if ## We have results show them #if $results Found $len($results) result(s): <table width="80%" border="1"> ## Header <tr> <td>Name</td> <td>Phone</td> </tr> #for $user in $results <tr> <td><a href="/users/$user.login">$user.name</a></td> <td>$user.phone</td> </tr> #end for </table> #end if #end block bodyTo understand Cheetah, keep in mind that:
- Cheetah files end with the
.tmpl
extension. - We "extend" the master page, in close analogy with subclassing in Python itself, or a similar object-oriented language. The page attributes here are like class members visible "from outside", both for reading and writing.
- This page's
body
block replaces thebody
block of the master page. - Python code starts with
#
. Unlike conventional Python source there is an explicit end tag:end
closes code blocks. - Comments start with
##
.#*
and*#
begin and end, respectively, multi-line comments.
page.query = query page.results = results return str(page)Similarly, the user page is
#extends master #block body ## Display user <h1>$user.name</h1><table> <tr> <td>Login:</td> <td>$user.login</td> </tr> <tr> <td>Phone:</td> <td>$user.phone</td> </tr> </table> #end block bodyUsing the Templates Cheetah compiles the
.tmpl
pages to .py
pages. If you have a base page (like our master), you must compile it. The cheetah-compile
script does this.
We use a different approach: Cheetah Template
classes, for which we load the template:
def get_page(name): '''Get a page template''' return Template(file = '%s.tmpl' % name)The Web Server The CherryPy framework has the following conventions:
- Set
cherrypy.root
to your Web application class. expose
all methods you want to be reflected as Web pages.- Use
cherrypy.config
to set server configuration. - The
index
method is the access point to your server. - Give a default value to each parameter you'll get from a Web form.
- The
default
method catches all requests that don't have an explicitexposed
method. - Enable "sessionFilter.on" to get session support.
@expose def index(self, query = None, within = 0): '''Index''' page = get_page("index") # Initial page if query is None: return str(page) # Search and display results page.query = query if within and session.get(RESULTS, None): results = search(query, session[RESULTS]) else: results = search(query) session[RESULTS] = results page.results = results return str(page)Note the decorator syntax. If you work with a version of Python before 2.4, you can write
index.exposed = 1Running the server is easy:
cherrypy.root = AddrBook() config.update(CONFIG) server.start()Configuration CherryPy configuration comes from either a file formatted in a style intended to be obvious, or with a dictionary. Typical content for the former — for the configuration file — might be:
[global] sessionFilter.on = 1 autoreload.on = 0Invoke this with:
config.update(file = "addressbook.conf")The corresponding dictionary approach is to define:
CONFIG = { 'sessionFilter.on' : 1, 'autoreload.on' : 0and invoke:
config.update(CONFIG)
CherryPy is quite flexible. Consult the manual for the all of the options it exposes to configuration control.
Among these, the two that we use are sessionFilter.on
, which enables session support, and autoreload.on
, which instructs the server to restart itself whenever one of the modules it is using changes. autoreload.on
shortens the development cycle, because it eliminates the programmer's need to stop and start the server during development. It's typically turned off for production, though, on security and performance grounds.
Advanced Issues
CSS
Use CSS for better layout. All CherryPy requires is a reference to the CSS in your master template and a definition of and a definition of the CSS file as static in the server configuration.
Running Under a Web ServerCherryPy comes with its own Web server. To use it with a different one — perhaps one on which you already rely — you have the following options:
- Run your application on a different port and connect to myserver:8283.
- Proxy requests from your Web server to your Web application.
- Run your server as an
fcgi
orscgi
application. - Use
mode_python.
Performance
Remember that "Premature optimization is the root of all evil". However, if you find out things are a bit slow for your needs, consider the following options:
- Serve static content directly from your Web server.
- Pre-compile all Cheetah templates.
- Use the Cheetah caching mechanism.
Summary
We've built a Web-based addressbook in fewer than 150 lines of code!
This introduction demonstrates how easy Web development with CherryPy and Cheetah can be. Next time someone asks you for a "small" internal application that you'd conventionally do as a standalone executable, consider all the advantages Web applications enjoy in deployment, networking, security standardization, and even training. They're available to you.
Miki Tebeka is a Software Process Engineer for Qualcomm Corporation. Cameron Laird, vice president of Phaseit, Inc., has co-authored "Regular Expressions" for seven years.