Mod Python

Introduction

This tutorial will teach you how to get started using mod_python and PSP. This entire website is using both mod_python and PSP, and I am going to share the knowledge that I've gained from converting this site from PHP to PSP.

What the heck is PSP? PSP is basically Python code embedded within web pages that is run when the page is accessed. This is accomplished in our tutorial through the mod_python DSO for Apache 2.

Why PSP? I love Python! I'm very comfortable with it, and when I heard that I could use it with my site I was very excited. I didn't fully understand the mod_python publisher and I am used to the style that PHP uses, so I chose PSP instead of full out mod_python coding, which I'm sure some people will protest. Following the references at the end of this document you can learn to use PSP as a templating system instead of how I use it on this site, so feel free to do so. One problem I ran into during my quest is that there isn't much documentation on PSP, so I told myself if I got everything working I'd write up this tutorial, and well, here we are.

I hope this tutorial will help someone, and that you have an easy transition into making sites using PSP.

Update (December 2004): Our site is now hosted by cityhost.ca and we have switched to a content management system written in PHP, so we are no longer using mod_python for the official site. I still recommend mod_python to everyone, it is an excellent way to write dynamic websites.

Update (February 2007): So it's been a few years since I updated this tutorial. I am now using 1and1 to host my site, and am no longer really using a content management system for the main site, just a few simple scripts. I no longer use Gentoo Linux, and as this tutorial is so old some parts of it may be inaccurate, however as it may still be somewhat useful I've put it up again.

Requirements

In order to use this tutorial to it's fullest, it is recommended that you have:

  • Gentoo Linux (for installation and configuration)
  • A working Apache 2 web server installation
  • A basic understanding of Apache
  • A basic understanding of Python
  • A basic understanding of XHTML

Installation

First, we need to install mod_python, but let's let Portage do the work. As of this writing, the version of mod_python we need (3.1 or above) is masked, so to emerge it we'll use this command:

ACCEPT_KEYWORDS="~x86" emerge mod_python

Note: if you are not using Gentoo Linux, you should follow the installation and configuration instructions on the mod_python website.

Configuration

Now, just as the ebuild tells us, we need to add "-D PYTHON" to APACHE2_OPTS in /etc/conf.d/apache2. The line in the file should look similar to this:

APACHE2_OPTS="-D PYTHON"

Next, to make all of these changes take effect, we should restart apache. To do this, issue the following command:

/etc/init.d/apache2 restart

You should get a message that apache stops and then restarts. It should now be working with mod_python, and you can test it by trying to access a non-existent page on your server (to get an error page, which will have your apache version and plugins listed unless you have turned this option off).

Now, to get this site working I had to do two more things, which I will now explain. First, I had to tell apache to use mod_python's psp module to parse .psp files when it encounters them, so that the code within them gets run. This is done in /etc/apache2/conf/commonapache2.conf. I used my server's document root as the place to implement this, so you will have to replace /public/servers/http with whatever your document root is:

<Directory /public/servers/http>
    ...
    AddHandler mod_python .psp
    PythonHandler mod_python.psp
    PythonDebug On
    ...
</Directory>

Notice that the PythonDebug option is optional, but can help when your code isn't working properly.

The last thing that was needed for this site was to tell apache to search for index.psp as the index file (to make it load http://programmer-art.org/index.psp when http://programmer-art.org is accessed). This is done in the same file as above. You should have a section similar to the one below; just add index.psp into the list:

<IfModule mod_dir.c>
    DirectoryIndex index.psp index.html index.htm
</IfModule>

Once again, you'll have to restart apache for these new changes to take effect:

/etc/init.d/apache2 restart

Using PSP

Now that everything is set up, let's try out some PSP code. Let's try a quick 'Hello World!' example. Create a file called example1.psp and put this in it:

<%
test_string = "Hello World!"
%>
<html>
    <%= test_string %>
</html>

The above page, when accessed, should print out 'Hello World!' in an HTML page.

The above example illustrates a very important part of PSP. You have probably noticed that Python code is seperated from the HTML by <% and %> symbols. There are four such combinations for code, expressions, directives, and comments.

Code is delimited by <% and %> symbols, and can be any valid Python code.

Expressions are delimited by <%= and %> symbols, and the output of the expression will be printed to the page. This is one of two ways to write output to the page, the other being the req.write() function discussed later.

Directives are delimited by <%@ and %> symbols. Currently, there is only one such directive, and that is the include directive, which looks like this: <%@ include file="filename" %>.

Comments are delimited by <%-- and --%> symbols. These comments will be removed by mod_python when the page is accessed, unlike normal HTML comments.

Special Variables

PSP has a few special, preset variables that you can use in your scripts that give you all the power of mod_python! I will cover the basics, but for in-depth explanations of each you should check the References and Further Reading section near the bottom of the page.

The first such variable is the req object, which is an instance of a mod_python Request object. This object corresponds to the current request. For all it's functions, check out the mod_python documentation.

The second such variable is the psp object, which is an instance of a mod_python PSPInstance, and has only three functions. Gregory's tutorial explains this one quite well.

The third such variable is the form object, which is a dictionary like object from which you can get data such as what variables were passed to the page.

The final such variable is the session object, which is an instance of a mod_python Session object. This object corresponds to the current session. Its functions are all in the mod_python documentation.

Examples

How can I print out data?

<%
# we are going to print out data in two ways
test_string = "Hello World!"
%>
<html>
    <%-- Output using the expression delimiters --%>
    <%= test_string %>
    <br/>
    This is some static content in the page...
    <br/>
    <%-- Output using the request object --%>
    <% req.write("Hello again!") %>
</html>

How can I get a variable that is passed on the URL?

<html>
<%
# get the variable 'page' passed on the URL
if form.has_key("page"):
    page = form["page"]
else:
    page = "home"
req.write("You are on the " + page + " page.")
%>
</html>

How can I tell what browser someone is using?

<html>
<%
# get the client's user agent string
req.add_common_vars() # init the CGI variables
if req.subprocess_env.has_key("HTTP_USER_AGENT"):
    agent = req.subprocess_env["HTTP_USER_AGENT"]
    req.write("You are using " + agent + ".")
else:
    req.write("Couldn't determine user agent")
# get out of the else
%>
</html>

How can I save variables in a cookie for later use?

<html>
<%
from mod_python.Cookie import add_cookie, get_cookies, Cookie
# check if we are explicitly passed a style
style = "default"
if form.has_key("style"):
    # set the style and try to save it
    style = form["style"]
    style_cookie = Cookie("style", style)
    # uncomment the next line to make the cookie "permanent"
    #style_cookie.expires = 2114398800
    # save the cookie
    add_cookie(req, style_cookie)
else:
    # try to load the style from a saved cookie
    page_cookies = get_cookies(req, Cookie)
    if page_cookies.has_key("style"):
    style_cookie = page_cookies["style"]
    style = style_cookie.value
req.write("You are using the " + style + " style.")
%>
<br/>
Change style: <a href="?style=default">Default</a> <a href="?style=ice">Ice</a> <a href="?style=gentoo">Gentoo</a>
<br/>
Remove the style option in the URL and the selected value should be loaded from a cookie when you reload the page.
</html>

Common Problems

Code indentation can become a problem because it is so important in Python. If you are using if/else/for statements and such you may need to leave a blank line or comment below the last statement for the page to display correctly. The good news about this is that you can loop pieces of HTML code without constantly calling req.write or using the expression delimiters. An example showing correct use is below:

<html>
<%
# try if/then statements
if 1 == 1:
%>
    This will be printed
    <br/>
<%
else:
%>
    This will be skipped
    <br/>
This will also be skipped...
<%
# make sure to stop the indentation!
%>
This will be printed again
</html>

I ran into the problem of not being able to include files using a variable. I got no errors, no anything, so I wrote this quick script which uses mod_python's PSP class to load and run other .psp files from within my index.psp file. It looks like this: (note: page is a variable loaded much like in the example above)

<%
# run the included pages through the psp compiler...
# I can't get the damn include directive to work :(
from mod_python.psp import PSP
import os
# make sure the file we want exists
if os.access("/public/servers/http/" + page + ".psp", os.F_OK):
    content_file = PSP(req, page + ".psp")
    # run the psp page
    content_file.run(form)
else:
    req.write("<h2>Error!</h2>")
    req.write("<p>The page you requested cannot be found!</p>")
# come out of the else!
%>

References and Further Reading

I couldn't have written this without the help of these sites. Make sure to check them out for more information!