Mod_python's PSP: Python Server Pages
by Gregory Trubetskoy02/26/2004
The new 3.1 version of mod_python introduces several major additions and enhancements over the previous 3.0 version. They are PSP, Cookie, and Session support. This article will introduce the first addition on the list, PSP.
Python Server Pages (PSP), as you probably guessed already, is a way to inline Python in HTML or XML documents. The server interprets this inlined code to produce the final HTML sent to the client. This approach has become popular with tools such as JSP, PHP, and ColdFusion.
Several other projects share the name "PSP", including Webware PSP and Perl Server Pages. The canonical name of what this article describes is therefore "Mod_python PSP", but for simplicity I will often refer to it as simply PSP.
PSP Story
Inlining Python in HTML is a subject of some controversy. Some people consider code inside HTML a bad programming practice, since it utterly violates the Model-View-Controller paradigm by placing the application logic inside the presentation layer. (I am inclined to agree with this, but as you will see later on, there are ways to use PSP that actually comply with the MVC model.) Bad as it may be, millions of PHP users show a clear demand for this style of programming — developers are having success implementing sites this way.
The inclusion of PSP in mod_python was rather unexpected. I have always maintained that something like this is outside the scope of mod_python, whose main objective is Apache-Python integration, not high-level web application tools and frameworks. Meanwhile, expecting "batteries to be included", many new mod_python users had expressed disappointment on the mailing lists that mod_python lacked PSP-like functionality. Quite a few frameworks worked with mod_python, but it seems like to choose a framework, one would have to try them all out first and make a decision personally.
Then one day an announcement came across the Python mailing list from Sterling Hughes (a PHP core developer, but obviously a Python fan as well!) regarding mod_psp. Mod_psp was an Apache module written in C that provided bare-bones Python-in-HTML functionality in a clean and simple way. Mod_psp was not thread-safe and had some syntactical short-comings, but it appeared as a great starting point for something that could be included in mod_python, mainly because it was fast and written specifically for Apache. Even better, Sterling had agreed to donate his code to ASF and had spent time on the initial work of integrating mod_psp into mod_python.
PSP Objectives
Early in the PSP-related discussions on the mod_python development list, I compiled a list of criteria that I felt were key to a successful implementation:
There should be no new language to learn. Many other frameworks, mainly as way to overcome the white space conflict between Python and HTML, introduced alternative syntax, often complex enough that it appeared as a separate language somewhat resembling Python. In my opinion this contradicts the spirit of simplicity and clarity of Python. New syntax can also be a significant deterrent for developers who rely on language syntax support features of their editor.
It should be as fast as possible. Mod_python solves many complex and low-level operating system and networking issues for the sake of performance, something that most Python developers do not have the skill or time to deal with. A PSP implementation should comply with this notion by being fast and clean. There is no value in hacking together yet another slow parser implemented in Python. The fact that PSP is a flex-generated C scanner adds real value in that it is not something that most people "can try at home".
It should require no Python semantics in HTML. Python uses indentation to denote blocks of code. Indentation has no significance in HTML. PSP should not require HTML to be indented.
It should require no HTML semantics in Python. Since HTML pays no attention to indentation, it seems there should be another way to denote blocks within PSP. The first temptation is to introduce a code block delimiter character into Python. PSP should avoid this.
PSP Syntax
PSP is similar to early JSP, delimiting its code using the
greater/less-than/percent tokens (<% and
%>).
Similarly to JSP, PSP has four types of entities:
Code represents the Python source code that drives the logic of how the final output is produced. It's enclosed in
<%and%>markers.An expression is Python code whose resulting string representation becomes part of the final output. They are enclosed in
<%=and%>markers.Directives are special instructions to the PSP processor. Directives are enclosed in
<%@and%>markers.-
Unlike HTML comments, PSP comments are removed by the PSP parser and never make it into the final output. They are enclosed in
<%--and--%>.
Admittedly, this syntax is rather primitive and not XML compliant, but it's a start. Most likely, later versions will feature an alternative XML-compliant syntax, opening the door for XSLT-generated PSP pages and other XML goodness.
The thorny issue of indentation is addressed by making the last indentation in effect "stick" throughout the HTML that follows it, with a recommendation to use meaningful comments to make code more readable, for example:
<%
if x == y:
# begin
%>
... some html ...
<%
# end
%>
The above code snippet also demonstrates a subtle syntactic difference introduced by PSP — the indentation of last line of code matters even if it is a comment.
The PSP syntax also introduces a small gotcha:
<%
if user in administrators:
level = 'admin'
else:
level = 'user'
# end
%>
... some html ...
In the above code, it's easy to forget the block-terminating comment
(# end in this case). Without it, some html becomes
part of the else block and will only be included in the final
output when the user in administrators condition is false.
In general, this syntax works quite well. Its only minor limitation is that it takes more space (e.g., three lines to terminate a block), though some will consider it a feature.
Hello World Example
For the impatient, here is a quick Hello World without much explanation. To
use PSP you have to configure Apache and mod_python to use the
mod_python.psp handler. Here is the relevant part of the Apache
config:
<Directory /some/path>
AddHandler mod_python .psp
PythonHandler mod_python.psp
PythonDebug On
</Directory>
Here is the PSP code. It must be in a file ending with .psp,
at least, according to the above configuration:
<html>
<%
if form.has_key('name'):
greet = 'Hello, %s!' % form['name'].capitalize()
else:
greet = 'Hello there!'
# end
%>
<h1><%= greet %></h1>
</html>
The above example will produce "Hello there!" if you simply invoke the page.
If you append the URL with ?name=john query argument, then the
result will be "Hello John!".
Under the Hood
The mechanics of PSP are quite simple. The PSP parser converts the PSP page into pure Python code suitable for execution in mod_python. This is best demonstrated by an example:
<html>
<%
import time
%>
<h1>Current time is
<%= time.ctime() %> </h1>
</html>
will become something like:
req.write("""<html>
""")
import time
req.write("""
<h1>Current time is
""");req.write(str(time.ctime()));req.write("""</h1>
</html>""")
The above Python code is then compiled into a code object using Python's
built-in compile() function. PSP caches that object and reused it
fo subsequent requests, unless the source file changes on disk.
The strange use of semicolons in the resulting Python code is there to try to preserve line numbering. The PSP parser is not capable of producing any errors. Bad PSP will simply result in bad Python, which will cause compilation errors from the Python interpreter. Having the line numbers reported by the interpreter correspond to the line numbers in the original PSP page helps tremendously in debugging.
Global Variables
Several variables exist in the global namespace at the PSP page execution time. These variables, therefore, can be used without assigning a value to them first. They are:
1. req
req, the mod_python Request object. This means that all of the
advanced functionality of mod_python is still available within the PSP
pages.
2. psp
psp, an instance of PSPInstance, which provides
access to a small PSP-specific API. This object has the following methods:
set_error_page(filename)This allows you to specify a PSP page to be invoked when a Python error occurs. This is useful for customizing error output, similar to the
errorPagedirective in JSP.apply_data(object)This method will call the callable object
object, passing form data as arguments and return the result. If you are familiar with JSP, this works much likesetProperty. If, for example you have an object defined as follows:class Car: def __init__(self, color): self.color = color # etc.Then a PSP page called as result of a form submission (the form contains a field named
color) can do this:<% car = psp.apply_data(Car) %>This will call the callable object
Car(classes are callable), passing it the value of form field namedcolor, resulting in an instance ofCarwhich is assigned to car.redirect(location)This can be used for redirection from within PSP pages. It's important to call this function absolutely first in the PSP page, because redirection cannot happen after there is any output sent to the browser.
3. form
form is the form data in a dictionary-like object (mod_python
FieldStorage). Merely mentioning this in the code causes a
FieldStorage instantiation, thereby consuming all
POST input. PSP uses Python's introspective qualities to determine
whether a piece of code uses the form variable. If the form variable is not
mentioned in the code, then no FieldStorage is instantiated.
4. session
session Similarly, PSP's the behavior changes if you mention
this variable inside the PSP page. When PSP detects that a page refers to the
session variable, it automatically creates a
mod_python.Session object, which will generate session cookies and
turn on session locking ensuring that each unique session can only have one
active request to this page at a time.
Directives
At this point PSP supports only one directive.
<%@ include file='filename'>
The PSP parser will replace this directive by the contents of the file
filename. This can be a very useful feature, although it presently
carries the limitation of complicating debugging because it throws off any
correspondence of original line numbers to the line numbers in the resulting
Python code.
Debugging
As I already mentioned, the PSP parser produces no errors. This may change in the future, but for now this is the case. Only the Python interpreter produces errors, but those errors will refer to the PSP-generated Python code, which is not visible to the developer. It can be difficult at times to associate the error condition reported by Python with the original PSP source.
To aid in this, the PSP handler provides the ability to peek at the
intermediate Python code produced by the parser. This only works when
PythonDebug Apache configuration directive is On. If
you append an underscore to the PSP URL, you will receive a nice listing
showing original PSP on the left and the resulting Python code on the
right.
If the original link were http://localhost/test.psp, then
http://localhost/test.psp_ will show the PSP-generated Python
source code. For this to work, you must register the .psp_ extension (with the underscore) with AddHandler:
AddHandler mod_python .psp .psp_
Using PSP as a Templating System
Most *SP's only support the mode of operation where the code is inlined in a web page referred to in the URL. As I mentioned above, this is not the best programming practice because it places the application logic inside the presentation component. Mod_python PSP can also work as a flexible templating system from within Python code. I advocate this method of using PSP because it allows for a clean programming style, separating presentation from logic.
Let's look at an example of such usage. Since we're not using PSP as a
handler, we will use the Publisher handler instead. Since the
Publisher handler is outside the scope of this article, I will
instead list the code that I think is self-explanatory. If you need more
information on the Publisher, I recommend looking at the
mod_python tutorial section in the standard documentation.
Here is a snippet of the relevant Apache configuration for using the
Publisher:
<Directory /your/document/root>
SetHandler mod_python
PythonHandler mod_python.publisher
PythonDebug On
</Directory>
Since we're using PSP as a templating mechanism, we need a template. Let's
assume it is in a file named /your/document/root/hello.tmpl:
<html>
<h1><%= greet %></h2>
</html>
Here is the Python script. Let's assume it is in a file called /your/document/root/pubpsp.py.
from mod_python import psp
def hello(req, name=''):
s = 'Hello, there!'
if name:
s = 'Hello, %s!' % name.capitalize()
tmpl = psp.PSP(req, filename='hello.tmpl')
tmpl.run(vars = { 'greet': s })
return
By the magic of the Publisher handler, you can invoke the
hello() function in the script via
http://localhost/pubpsp/hello. Pass a name to the
hello() function with
http://localhost/pubpsp?name=joe.
In the above example, we load the PSP template by creating an instance of
the psp.PSP class. At the time of instantiation, PSP either
translates it into Python source code and then compiles it or loads it from the
PSP cache. (Yes, caching is on even when PSP is used this way).
The next line calls the template's run() method, passing it a
vars dictionary. This is when the template is actually executed
and its output is sent to the client. The vars dictionary is a
list of variable names that will be added to the global namespace of the
template just before it is executed. We pass it a variable called
greet which is referred to in an expression inside the
template.
Nested PSP Templates
A nice feature of the PSP templates is that one template can contain
references to another in an expression. Let's expand the above example. The
Apache configuration stays the same, but we'll add another template file called
time.tmpl:
<h2>
And the time is <%= now %>
</h2>
We'll modify the hello.tmpl template:
<html>
<h1><%= greet %></h2>
<%= time_tmpl %>
</html>
Finally, our new script code is:
from mod_python import psp
import time
def hello(req, name=''):
s = 'Hello, there!'
if name:
s = 'Hello, %s!' % name.capitalize()
time_tmpl = psp.PSP(req, filename='time.tmpl',
vars = {'now': time.ctime()})
hello_tmpl = psp.PSP(req, filename='hello.tmpl')
hello_tmpl.run(vars = { 'greet': s, 'time_tmpl':time_tmpl })
return
In the above example we instantiated an additional template,
time_tmpl. Note that this time we passed a variable called
now as part of the constructor rather than an argument to the
run() method. PSP allows you to pass variables either or both
ways. Then we pass the time_tmpl PSP object to the
hello_tmpl.
In this example, the time_tmpl is parsed and compiled at
instantiation time, but is executed only when the container template's
run() method is called.
This example is, of course, completely impractical, but it demonstrates a very useful feature. Imagine that you have a complex site that contains a dynamically generated menu. You can place such a menu into a template of its own and include it in the other templates of the site.
Conclusion
The PSP functionality included in mod_python 3.1 is powerful and versatile, even though it is only the first version. Its ability to be used as a class from within the Python code makes it suitable for clean web development in accordance with the MVC paradigm.
Gregory Trubetskoy is the lead developer of mod_python and a member of the Apache Software Foundation.
Return to the Python DevCenter.
Showing messages 1 through 18 of 18.
-
req.write new page
2007-08-16 07:34:57 SijmenSP [View]
-
req.write new page
2007-08-16 07:31:37 SijmenSP [View]
If is use
req.write in a loop
for writing out textual information the new information is appended to the information just written
for example the following script:
def index(req):
for i in range(10)import time
-
psp
2005-07-28 02:51:11 paulhide [View]
Apache 2.0.53
mod_python 3.1.3
psp ?
All software installed locally on NT box.
Experimenting with session in psp code. Session.py crashes on line 165. I embedded the following code in a template file to try to find out why. Comments show the values that were returned.
docroot = req.document_root()
#docroot --> C:/Apache2/htdocs
dirpath = req.hlist.directory
#dirpath --> J:\/
c_path = dirpath[len(docroot):]
Clearly c_path (my version of c.path) is of zero length and when indexed [-1] on line 165 of the actual code, it crashes. Perhaps my apache config file contains unexpected (wrong) values.
Paul Hide
-
psp_ debugging secure?
2005-04-19 13:13:51 russelio [View]
I don't like that people can see my psp code by just
typing in psp_ for the code. Is there a way within apache(I guess I could try figuring this out), to make this unviewable unless you have a username/password?
-
mod_python PSP is redundant
2005-01-04 17:45:39 jon_perez [View]
Spyce (http://spyce.sf.net) covers everything that
PSP does and much more. Moreover, Spyce can
work via CGI, fastCGI and as its own proxy server
in addition to running over mod_python.
This means that PSP is just a case of reinventing
the wheel (and not a very good reinvention at that).
Forcing people to incur the extra bloat of PSP along
with mod_python when they want to use a better alternative is not a very good decision. -
mod_python PSP is redundant
2005-07-20 13:59:35 MPHellwig [View]
I took a look a both and my conclusion is that both are capable of producing the exact same output with equal performance, the differents is more personal preference:
- If you want PHP _like_ functionality with Python and no templating, go for Spyce
- If you want Python programming with PHP _like_ functionality, go for mod_python and templating PSP
- If your choice is between PSP with Spyce or mod_python, go for Spyce or Spyce on mod_python if you need the performance -
mod_python PSP is redundant
2006-02-22 11:59:59 jon_perez [View]
make that custom tags... ala JSP... a superior-to-templating feature that PHP doesn't have at all. -
mod_python PSP is redundant
2006-02-22 11:55:47 jon_perez [View]
While Spyce may not have templates per se, it supports tags and active handlers which can be so much more powerful than templates, yet easy to understand and learn at the same time. -
mod_python PSP is redundant
2005-02-27 09:17:45 nsalgado [View]
Using PSP as templates are a very good way of doing html pages. I don't see any advantage on using another tool.
I'm converting my pages from Zope to mod_python using PSP and I'm very happy with the simplicity off PSP versus ZPT.
I just want to thank you to Gregory Trubetskoy and to Sterling Hughes for their work.
-
problem executing template example
2004-12-21 08:40:05 ChristianPinedo [View]
Hi,
I can not execute the template example at a GNU/Linux workstation with apache2 and mod_python 3.1.3. Whenever I tried it, the browser (Firefox 1.0 and Epiphany 1.4.5) downloaded a file that was the generated html page but not browsed it.
To solve this I had to add a line to pubpsp.py:
def hello(req, name=''):
s = 'Hello. there!'
if name:
s = 'Hello, %s!' %
name.capitalize()
req.content_type = 'text/html' # this !!
tmpl = psp.PSP(req, filename='hello.tmpl')
tmpl.run(vars = {'greets': s})
return
This is the only solution i have found.
-
Cheetah
2004-12-04 02:53:14 chernia [View]
HI,
Coming from turbine/velocity to evaluate python for a large web project, I ran on the cheetah template engine, but someone wrote there is a performance issue unresolved:
http://www.modpython.org/pipermail/mod_python/2004-August/016046.html
-
print "Hello"
2004-04-17 14:37:35 M-a-S [View]
I think it would be great if this worked:
<%
...
print expression
...
%>
Otherwise one have to write
<%
...
%><%= expression %><%
...
%>
instead. Am I right? -
print "Hello"
2004-08-25 16:31:32 Ikeuchi [View]
There's a way, just forget the print, use instead req.write().
<%
req.write(expression)
%>
-
psp does not work
2004-03-06 02:00:29 cowboy2 [View]
my configfle file:
<Directory /var/www/html/test>
AddHandler mod_python .psp
PythonHandler mod_python.psp
PythonDebug On
</Directory>
but,when i go to http://192.168.0.1/test/helloword.psp,i got the source file .(it remain contain the <%%> flag)
this article is my 1st psp guide.but i failed.
-
How does PSP compare to Spyce?
2004-02-27 02:29:58 g-rayman [View]
Is Spyce [PSP] an implementation of PSP or a different framework with the same name? -
How does PSP compare to Spyce?
2004-02-27 06:37:28 batripler [View]
Spyce PSP has a number of advantages over mod_python PSP. A big plus is that it is portable, running over many webserver configurations, including mod_python. Spyce is also very fast, but it's written in pure Python. The Spyce PSP language supports first-class Spyce functions, which mod_python does not. This is useful for templating, among other uses. Spyce also supports active tags, ala JSP. Spyce supports threaded execution, comes with a rich set of pre-written standard modules. etc... -
How does PSP compare to Spyce?
2004-02-27 05:52:22 grisha [View]
Different framewrok with a different name. I touch on the naming issue in the third paragraph of the article.







req.write in a loop
for writing out textual information the new information is appended to the information just written
for example the following script:
def index(req):
import time
req.content_type = "text/html"
for i in range(10)
time.sleep(1)
req.write(str(i))
will result in this:
0123456789
Is it possible to empty the page between every req.write so each number appears on it's own?