spyce
home
license
community
download
examples
resources
wishlist
contrib (@sf)
documentation
intro
lang
runtime
modules
tags
install
exits
sourceforge
statistics
freshmeat

transparent transparent transparent
Documentation
[[ Spyce ]]
Python Server Pages
by Rimon Barr

Spyce User Documentation

Rimon Barr

Release 1.3.10

[ Multi-Page Format ]

TABLE OF CONTENTS


1. INTRODUCTION

This document aims to be the only necessary and authoritative source of information about spyce, usable as a comprehensive refence, a user guide and a tutorial all-in-one. It should be read at least once from end to end.

SPYCE is an Apache plugin and command-line utility to support simple and efficient Python-based dynamic server-side HTML scripting. Like JSP, PHP, ASP and other similar HTML scripting languages it allows the generation of dynamic HTML content via embedded programming logic, and does not attempt to provide an encompassing application framework. Just as JSP uses Java, PHP uses a Perl-like language and ASP most commonly uses Visual Basic, Spyce embeds Python among the HTML tags. Its performance is comparable to the other solutions in its class.

1.1. Rationale

A natural question to ask is why one would choose Spyce, over JSP, ASP, PHP and a handful of other popular HTML scripting languages or technologies that perform a similar function. We compare Spyce with an array of exising tools (chosen for their interesting points of comparison, not completeness):

  • Java Server Pages, JSP, is a widely popular, effective and well-supported solution based on Java Servlet technology. Spyce differs from JSP in that it embeds Python code among the HTML, thus providing a number of advantages over Java. First, Python is a high-level scripting language, where rapid prototyping is syntactically easier to perform. Second, Python is interpreted and latently typed, which can be advantageous for prototyping, especially in avoiding unnecessary binary incompatibility for minor changes. Third, Spyce code is of first-order in the Spyce language, unlike JSP. And, lastly, creating new active tags and modules is simpler in Spyce than in JSP. Like Java, Python is portable.
  • PHP is another popular webserver module for dynamic content generation. The PHP interpreter engine and the language itself were explicitly designed for the task of dynamic HTML generation, while Python is a general-purpose scripting language. Spyce borrows from the extensive development effort in Python. Since any Python library can be imported and reused, Spyce does not need to rebuild many of the core function libraries that have been implemented by the PHP project. Moreover, the use of Python often simplifies integration of Spyce with existing system environments. Spyce code is also first-order in the Spyce language and Spyce supports active tags, unlike PHP. Lastly, Spyce is modular in its design, allowing users to easily extend its base functinality with add-on modules. Spyce, like PHP, can run entirely within the process space of a webserver or via CGI (as well as other adapters), and has been benchmarked to be competitive in performance. In addition, the Spyce engine can be run from the command-line, which allows Spyce to be used as an HTML preprocessor.
  • Active Server Pages, ASP, is a Microsoft technology popular with Microsoft Internet Information Server (IIS) users. The default and most common language embedding is Visual Basic. Stated briefly, the author strongly prefers the language design and syntax of Python over VB. Secondly, ASP is not well-supported outside the IIS environment, while Spyce can currently run under mod_python (Apache), as well as under CGI and FastCGI, or as a proxy server, supported in the majority of web server environments. Lastly, Spyce is open-source, and free.
  • WebWare with Python Server Pages, PSP, is a recent Python-based open-source development. PSP is similar in design to the Spyce language, and shares many of the same benefits. However, Spyce supports both Python chunks (indented Python) as well as PSP-style statements (braced Python). Spyce code is also first-order in the Spyce language and supports active tags, unlike PSP. PSP is also an integral part of WebWare, an application-server framework similar to Tomcat Java-based application server of the Apache Jakarta project. Spyce is to WebWare as JSP is to Tomcat. Spyce is far simpler to install and run than WebWare, and does not involve notions such as application contexts. It aims to do only one thing well: provide a preprocessor and runtime engine for the dynamic generation of HTML using embedded Python.
  • Zope is an object-oriented open-source application server, specializing in "content management, portals, and custom applications." It is more mature than WebWare, but also attacks a much larger problem than Spyce. Zope provides a scripting language called DHTML and can call extensions written in Perl or Python. Spyce embeds Python directly in the HTML, and only Python. It is also easier to install.
In summary, the most popular of these solutions seem to be the solutions (JSP, PHP and ASP) that focus on the smaller problem of embedding a language within HTML. Spyce embeds Python in HTML, and no more. Many users have said that this is "exactly what they have been waiting for". Hopefully, this is the correct point in the design space for your project as well. You really just have to try it and see whether it suits your needs.

1.2. Design Goals

As a Spyce user, it helps to understand the broad design goals of this tool. Spyce is designed to be:

  • Feature poor: The philosophy behind the design of Spyce is only to include features that particularly enhance its functionality over the wealth that is already available from within Python. One can readily import and use Python modules for many functions, and there is no need to recode large bodies of functionality.
  • Small: There is an active push keep Spyce small, especially the core engine. The engine currently stands at 3600 lines of code. The standard modules comprise another 2000 lines.
  • Modular: Spyce is built to be extended with Spyce modules that provide additional functionality over the core engine capabilities and standard Python modules. New features in the core engine and language are rationalised against the option of creating a new module. Standard Spyce modules are those that are considered useful in a general setting and are included in the default Spyce distribution. Users and third-parties are encouraged to develop their own Spyce modules.
  • Intuitive: Obey user expectations.
  • Convenient: Using Spyce should be made as efficient as possible. This, for example, is the reason behind the choice of [[ as delimeters over alternatives such as <? (php) and <% (jsp). Functions and modules are also designed with as many defaults as possible.
  • Single-purpose: To be the best, most versatile, wildly-popular Python-based dynamic HTML engine. Nothing more; nothing less.
  • Fast: Performance is important. It is expected that Spyce will perform comparably with any other dynamic, scripting solutions available. Of paramount importance, however, is clean design and syntax, and high degree of modularity and usability. The philosophy is to build and tweak only when necessary and only for significant performance gains, tested empirically.

Now, let's start using Spyce...

1.3. Getting Started

After installing Spyce, you are ready to write your first Spyce page. Start by editing a file called hello.spy in some web-published directory (a directory served by your webserver, and where .spy files will be handled correctly). Enter the following Spyce code:

examples/hello.spy
<html><body>
  Hello [[print 'world!',]]
  [[ for i in range(10): { ]]
    [[=i]]
  [[ } ]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

Note: This manual assumes a knowledge of Python and focusses exclusively on Spyce. If you do not already know Python, it is easiest to learn via this short tutorial, and has extensive documentation.

Save the file, and execute: "spyce hello.spy"; the output should be:

  <html><body>
    Hello world!
      0  
      1  
      2  
      3  
      4  
      5  
      6  
      7  
      8  
      9 
  </body></html>

You might want to see this inside a browser! For this, you need a Spyce enabled web server. Spyce can integrate into a number of web environments, but the easiest for the purposes of "getting started" is the built-in Spyce web server. You can run it by typing: "spyce -l -p port root". Replace port with a port number, or omit the -p switch and assume the default which is 80. Replace root with the root document directory; the default is the current directory. All web requests are served relative to the root document directory.

Now, if you fire up your browser, and browse to: http://your.domain:port/some_path/hello.spy; the output should be:

  Hello world! 0  1  2  3  4  5  6  7  8  9

If you are curious, you can see the Python source code of the compile Spyce script. Execute "spyce -c hello.spy". It sometimes helps to understand the transformation that is taking place. That's all folks! You have just written your first Spyce script. Having performed the obligatory Hello world! ritual, we now describe the Spyce language more systematically.

2. LANGUAGE

The basic structure of a Spyce script is an HTML file with embeddings. There are six types of possible embeddings among the plain HTML text:

The majority of HTML strings are written out to the browser, verbatim. However, the Spyce language also supports "active" HTML tags via tag libraries, in the sense that they can execute custom code. Spyce comments are elided. The Spyce directives affect the interpreter behaviour. Python statements and chunks are executed. Finally, Python expressions are evaluated and the result is emitted to the browser. Each Spyce tag type has a unique beginning delimeter, namely [[, [[\, [[=, [[. or [[--. All tags end with ]], except comment tags, which end with --]].

Since [[ and ]] are special Spyce delimeters, one would escape them as \[[ and \]] for use in HTML text. They can not be escaped in Python code, but the string expressions ("["*2) and ("]"*2), or equivalent expressions, can be used instead, or the brackets can be conveniently separated with a space in the case of list or slicing expressions.

In addition, Spyce scripts are first-class members of the Spyce language: you can create a Spyce lambda out of a Spyce string using the syntax below, in any of the Spyce Python elements (statements, chunks and expressions). A Spyce lambda can be invoked like a regular Python function, and its execution context (i.e. modules) is that of its calling point. Spyce lambdas do not currently support nested variable scoping, nor default parameters.

Note that braces in "[parameters]" above means that a Spyce lambda may have zero parameters. Consider the parameter list of a Spyce lambda to be the same as the parameter list of a Python lambda definition.

2.1. Plain HTML and Active Tags

Static plain HTML strings are printed as they are encountered. Depending on the compacting mode of the Spyce interpreter, some whitespace may be eliminated. The Spyce transform module, can also pre-processes this string, if necessary, for example, for compression.

The Spyce language supports tag libraries. Once a tag library is imported under some name, mytags, then all static HTML tags of the form <mytags:foo ... > become "active". That is, code from the tag library is executed at that point in the document. Tags can control their output, conditionally skip or loop the execution of their bodies, and can interact with other active tags in the document. Tag libraries and modules (discussed later) can both considerably reduce the amount of code on a Spyce page, and increase code reuse and modularity.

2.2. Spyce Comments

Syntax: [[-- comment --]]

Spyce comments are ignored, and do not produce any output, meaning that they will not appear at the browser even in the HTML source. The first line of a Spyce file, if it begins with the characters #!, is also considered a comment, by Unix scripting convention. Spyce comments do not nest.

2.3. Spyce Directives

Syntax: [[. directive ]]

Spyce directives directly affect the operation of the Spyce interpreter. There is a limited set of directives, listed and explained below.

  • [[.include file=file]] :
    Upon encountering this tag the Spyce compiler will insert the file referenced, treating its contents as if it had been typed inline. Statically included files are not checked for updates. Only an update to the primary file will invalidate the cache. For dynamic includes, please refer to the include module. Developers should also consider creating custom Python or Spyce modules, when the included file is primarily code, since there is no need to involve the (slow) Spyce compiler.
  • [[.compact mode=mode]] :
    Spyce can output the static HTML strings in various modes of compaction, which can both save bandwidth and improve download times without visibly affecting the output. Compaction of static HTML strings is performed once when the input Spyce file is compiled, and there is no additional run-time overhead beyond that. Dynamically generated content from Python code tags and expressions is not compacted nor altered in any way. Spyce can operate in one of the compaction modes listed below. One can use the compact tag to change the compaction mode from that point in the file forwards.

    • off: No compaction is performed. Every space and newline in the static HTML strings is preserved.
    • space: Space compaction involves reducing any consecutive runs of spaces or tabs down to a single space. Any spaces or tabs at the beginning of a line are eliminated. These transformations will not affect HTML output, barring the <pre> tag, but can considerably reduce the size of the emitted text.
    • line: Line compaction eliminates any (invisible) trailing whitespace at the end of lines. More significantly it improves the indented presentation of HTML, by ignoring any lines that do not contain any static text or expression tags. Namely, it removes all the whitespace, including the line break, surrounding the code or directives on that line. This compaction method usually "does the right thing", and produces nice HTML without requiring tricky indentation tricks by the developer. It is, therefore, the initial compaction mode.
    • full: Full compaction applies both space and line compaction. If the optional mode attribute is omitted, full compaction mode is the default value assumed.
  • [[.import name=name from=file as=name args=arguments]] :
    The import directive loads and defines a Spyce module into the global context. (The [[.module ... ]]directive is synonymous.) A Spyce module is a Python file, written specifically to interact with Spyce. The name parameter is required, specifying the name of the Python class to load. The file parameter is optional, specifying the file where the named class is to be found. If omitted, file will equal name.py. The file path can be absolute or relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current script directory, in that order. Users are encouraged to name or prefix their modules uniquely so as not to be masked by system modules or tag libraries. The as parameter is optional, and specifies the name under which the module will be installed in the global context. If omitted, this parameter defaults to the name parameter. Lastly, the optional args parameter provides arguments to be passed to the module initialization function. All Spyce modules are start()ed before Spyce processing begins, init()ed at the point where the directive is placed in the code, and finish()ed after Spyce processing terminates. It is convention to place modules at, or near, the very top of the file unless the location of initialization is relevant for the functioning of the specific module.

    [[.import names="name1,name2,..."]] :
    An alternative syntax allows convenient loading of multiple Spyce modules. One can not specify non-standard module file locations, nor rename the modules using this syntax.

  • [[.taglib name=name from=file as=name]] :
    The taglib directive loads a Spyce tag library. A Spyce tag library is a Python file, written specifically to interact with Spyce. The name parameter is required, specifying the name of the Python class to load. The file parameter is optional, specifying the file where the named class is to be found. If omitted, file will equal name.py. The file path can be absolute or relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current script directory, in that order. Users are encouraged to name or prefix their tag libraries uniquely so as not to be masked by system tag libraries and modules. The as parameter is optional, and specifies the unique tag prefix that will be used to identify the tags from this library. If omitted, this parameter defaults to the name parameter. It is convention to place tag library directives at, or near, the very top of the file. The tags only become active after the point of the tag library directive.

    [[.taglib names="name1,name2,..."]] :
    An alternative syntax allows convenient loading of multiple Spyce tag libraries. One can not specify non-standard tag library locations, nor specify a prefix of the tag libraries using this syntax.

2.4. Python Statements

Syntax: [[ statement(s) ]]

The contents of a code tag is one or more Python statements. The statements are executed when the page is emitted. There will be no output unless the statements themselves generate output.

The statements are separated with semi-colons or new lines, as in regular Python scripts. However, unlike regular Python code, Python statements do not nest based on their level of indentation. This is because indenting code properly in the middle of HTML is difficult on the developer. To alleviate this problem, Spyce supports a slightly modifed Python syntax: proper nesting of Spyce statements is achieved using begin- and end-braces: { and }, respectively. These MUST be used, because the compiler regenerates the correct indentation based on these markers alone. Even single-statement blocks of code must be wrapped with begin and end braces. (If you prefer to use Python-like indentation, read about chunks).

The following Spyce code, from the Hello World! example above:

  [[ for i in range(10): { ]]
    [[=i]]
  [[ } ]]

produces the following indented Python code:

  for i in range(10):
    response.writeStatic('  ')
    response.writeExpr(i)
    response.writeStatic('\n')

Without the braces, the code produced would be unindented and, in this case, also invalid:

  for i in range(10):
  response.writeStatic('  ')
  response.writeExpr(i)
  response.writeStatic('\n')

Note how the indentation of the expression does not affect the indentation of the Python code that is produced; it merely changes the number of spaces in the writeStatic string. Also note that unbalanced open and close braces within a single tag are allowed, as in the example above, and they modify the indentation level outside the code tag. However, the braces must be balanced across an entire file. Remember: inside the [[ ... ]] delimiters, braces are always required to change the indentation level.

2.5. Python Chunks

Syntax: [[\ Python chunk ]]

There are many Python users that experience anguish, disgust or dismay upon reading the previous section: "Braces!? Give me real, indented Python!". These intendation zealots will be more comfortable using Python chunks, which is why Spyce supports them.

A Python chunk is straight Python code, and the internal indentation is preserved. The entire block is merely outdented (or indented) as a whole, such that the first non-empty line of the block matches the indentation level of the context into which the chunk was placed. Thus, a Python chunk can not affect the indentation level outside its scope, but internal indentation is fully respected, relative to the first line of code, and braces ({, }) are not required. Since the first line of code is used as an indentation reference, it is recommended that the start delimeter of the tag (i.e. the [[\) be placed on its own line, above the code chunk, as shown in the following example:

[[\
    def printHello(num):
      for i in range(num):
        response.write('hello<br>')

    printHello(5)
]]

Naturally, one should not use braces here for purposes of indentation, only for Python dictionaries. Additional braces will merely generate Python syntax errors in the context of chunks. To recap: a Python statement tag should contain braced Python; A Python chunk tag should contain regular indented Python.

2.6. Python Expressions

Syntax: [[= expression ]]

The contents of an expression tag is a Python expression. The result of that expression evaluation is printed using the its string representation. The Spyce transform module, can pre-processes this result, to assist with mundane tasks such as ensuring that the string is properly HTML-encoded.

2.7. Spyce Lambdas

Syntax: [[spy [params] : spyce lambda code ]]
or: [[spy! [params] : spyce lambda code ]]

A nice feature of Spyce is that Spyce scripts are first-class members of the language. In other words, you can create a Spyce lambda (or function) in any of the Spyce Python elements (statements, chunks and expressions). These can then be invoked like regular Python functions, stored in variables for later use, or be passed around as paramaters. This feature is often very useful for templating (example shown below), and can also be used to implement more esoteric processing functionality, such as internationalization, multi-modal component frameworks and other kinds of polymorphic renderers.

It is instructive to understand how these functions are generated. The [[spy ... : ... ]] syntax is first translated during compilation into a call to the define() function spylambda module. At runtime, this call compiles the Spyce code at the point of definition, and returns a function. While the invocation of a Spyce lambda is reasonably efficient, it is certainly not as fast as a regular Python function invocation. The spycelambda can be memoized (explained in the spylambda module section) by using the [[spy! ... : ... ]] syntax. However, even with this optimization one should take care to use Python lambdas and functions when the overhead of Spyce parsing and invocation is not needed.

Note that Spyce lambdas do not currently support nested variable scoping, nor default parameters. The global execution context (specifically, Spyce modules) of the Spyce lambda is defined at the point of its execution.

examples/spylambda.spy
[[
  # table template
  table = [[spy! title, data: 
    <table>
      <tr>
        [[for cell in title: {]]
          <td><b>[[=cell]]</b></td>
        [[}]]
      </tr>
      [[for row in data: {]]
        <tr>
          [[for cell in row: {]]
            <td>[[=cell]]</td>
          [[}]]
        </tr>
      [[}]]
    </table> 
  ]]

  # table information
  title = ['Country', 'Size', 'Population', 'GDP per capita']
  data = [
    [ 'USA', '9,158,960', '280,562,489', '$36,300' ],
    [ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
    [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
  ]
]]

[[-- emit web page --]]
<html><body>
  [[ table(title, data) ]]
</body></html>

Run this code.
(requires Spyce-enabled web server)

2.8. ASP/JSP syntax

Finally, due to popular demand, the Spyce engine will respect ASP and JSP-like delimeters. In other words, it will also recognize the following syntax:

3. RUNTIME

Having covered the Spyce language syntax, we now move to describing the runtime processing. Each time a request comes in, the cache of compiled Spyce files is checked for the compiled version of the requisite Spyce file. If one is not found, the Spyce file is quickly read, transformed, compiled and cached for future use.

The compiled Spyce is initialized, then processed, then finalized. The initialization consists of initializing all the Spyce modules. The Spyce file is executed top-down, until the end is reached or an exception is thrown, whichever comes first. The finalization step then finalizes each module in reverse order of initialization, and any buffered output is automatically flushed.

3.1. Exceptions

The Spyce file is executed top-down, until the end of the file is reached, a valued is returned, or an exception is thrown, whichever comes first. If the code terminates via an unhandled exception, then it is caught by the Spyce engine. Depending on the exception type, different actions are taken:

  • spyceDone can be raised at any time to stop the Spyce processing (without error) at that point. It is often used to stop further output, as in the example below that emits a binary image file. The spyceDone exception, however, is more useful for modules writers. In regular Spyce code one could simply issue a return statement, with the same effect.
  • spyceRedirect is used by the redirect module. It causes the Spyce engine to immediately redirect the request to another Spyce file internally.
  • All other exceptions will be processed via the Spyce error module. This module will emit a default error message, unless the user has installed some other error handler.

Note that non-runtime exceptions, such as exceptions caused by compile errors, missing files, access restrictions and the like, are handled by the server. The default server error handler can be configured via the server configuration file.

examples/gif.spy
[[.import name=include ]]
[[\
  # Spyce can also generate other content types
  # The following code displays the Spyce logo
  response.setContentType('image/gif')
  response.write(include.dump('spyce.gif', 1))
  raise spyceDone
]]
Run this code.
(requires Spyce-enabled web server)

3.2. Code Transformation

While the minutia of the code transformation that produces Python code from the Spyce sources is of no interest to the casual user, it has some slight, but important, ramifications on certain aspects of the Python language semantics when used inside a Spyce file.

The result of the Spyce compilation is some Python code, wherein the majority of the Spyce code actually resides in a single function called spyceProcess. If you are curious to see the result of a Spyce compilation, execute: "spyce -c".

It follows from the compilation transformation that:

  • Any functions defined within the Spyce file are actually nested functions within the spyceProcess function.
  • The use of global variables within Spyce code is not supported, but also not needed. If nested scoping is available (Python versions >2.1) then these variables will simply be available. If not, then you will need to pass variables into functions as default parameters, and will not be able to update them by value (standard Python limitations). It is good practice to store constants and other globals in a single class, or to to place them in a single, included file, or both.
  • The global Spyce namespace is reserved for special variables, such as Spyce and Python modules. While the use of the keyword global is not explicitly checked, it will pollute this space and may result in unexpected behaviour or runtime errors.

  • The lifetime of variables is the duration of a request. Variables with lifetimes longer than a single request can be stored in the pool module.

3.3. Dynamic Content

The most common use of Spyce is to serve dynamic HTML content, but it should be noted that Spyce can be used as a general purpose text engine. It can be used to generate XML, text and other output, as easily as HTML. In fact, the engine can also be used to generate dynamic binary data, such as images, PDF files, etc., if needed.

The Spyce engine can be installed in a number of different configurations that can produce dynamic output, including:

  • mod_python: Apache module that runs a Python interpreter in-process
  • FCGI: A CGI-like method that does not incur the large process startup overhead on each request
  • webserver: The engine can operate as a proxy webserver, serving requests redirected to it by a primary server. It can also operate as a primary server, although this webserver is extremely feature poor.
  • CGI: Spyce can interoperate with any webserver that supports CGI
3.4. Static Content

A nice feature of Spyce is that it can be invoked both from within a web server to process a web request dynamically and also from the command-line. The processing engine itself is the same in both cases. The command-line option is actually just a modified CGI client, and is often used to pre-process static content, such as this manual.

Some remarks regarding command-line execution specifics are in order. The request and response objects for a command-line request are connected to standard input and output. A minimal CGI-like environment is created among the other shell environment variables. Header and cookie lookups will return None and the engine will accept input on stdin for POST information, if requested. There is also no cache as the process memory is lost at the end of every execution.

Most commonly, Spyce is invoked from the command-line to generate static .html ouput. Spyce then becomes a rather handy and powerful .html preprocessing tool. It was used on this documentation to produce the consistent headers and footers, to include and highlight the example code snippets, etc...

The following makefile rule comes in handy:

  %.html: %.spy
    spyce -o $@ $<

3.5. Command line

The full command-line syntax is:

spyce v1.3.10, by Rimon Barr: Python Server Pages
Command-line usage:
  spyce [-c] [-o filename.html] <filename.spy>
  spyce [-w] <filename.spy>         <-- CGI
  spyce -O filename(s).spy          <-- batch processing
  spyce -l [-p port] [<root>]       <-- proxy server
  spyce -h | -v
    -h, -?, --help       display this help information
    -v, --version        display version
    -o, --output         send output to given file
    -O                   send outputs of multiple files to *.html
    -c, --compile        compile only; do not execute
    -w, --web            cgi mode: emit headers (or use run_spyceCGI.py)
    -q, --query          set QUERY_STRING environment variable
    -l, --listen         run in HTTP server mode
    -p, --port           listen on given port, default 80
    --conf [file]        Spyce configuration file
To configure Apache, please refer to: spyceApache.conf
For more details, refer to the documentation.
  http://spyce.sourceforce.net
Send comments, suggestions and bug reports to <rimon AT acm DOT org>.

3.6. Configuration

Though there are a variety of very different installation alternatives for the Spyce engine, effort has been invested in consolidating all the various runtime configuration options.

By default, the Spyce engine will search for a file called spyce.conf in its installation directory. An alternative file location may be specified via the --conf command-line option. However, the engine will operate just fine without any configuration file, using it built-in default values. In general, command-line options supercede configuration file options which, in turn, supercede built-in configuration defaults. An example configuration file is provided, called spyce.conf.eg.

The options specified in the Spyce configuration file are server-level configuration options (as opposed to file-level options). The general format of a Spyce file is that of a Windows .ini file. Sections are denoted inside braces (i.e. [section]), and options with values are specified on independent lines separated by a colon (i.e. option: value). Comment lines begin with either a semicolon ';' or a hash '#'. An example of an extensive Spyce file (i.e. with all options specified) is shown, with the individual options described below. 3.6.1. Example configuration

###############
# 
# Spyce configuration file
#    -- an example
#
###############



###############
#
# The [spyce] section defines the main spyce configuration options
#

[spyce]

#
# The spyce path determines which directories are searched for when
# loading modules and tag libraries. The Spyce installation directory
# is always searched first. Any directories in the SPYCE_PATH
# environment are also searched.
#

#path: /usr/spyce/inc:/myapplication


#
# The import option can be used to pre-load various Python modules
# during engine initialization.
#

# import: myModule, myModule2


#
# The error option sets the server-level error handler; file-level
# error handling is defined within Spyce scripts using the error
# module. The format of this option is MODULE:FUNCTION. The server
# will call the error handler as: 
#   MODULE.FUNCTION(request, response, error)
# if a server-level error should occur.
#

#error: error:serverHandler


#
# The pageerror option sets the default page-level error handler.
# The format of this option is one of:
#   string:MODULE:VARIABLE
#   file:FILE
# where the lowercase words are literals.
#

#pageerror: string:error:defaultErrorTemplate


#
# The concurrency option is used for long-lived engines (i.e. not for
# CGI or command-line processing), and sets the concurrency level for
# the engine. Legal values are 'thread' (or 'threading') and 'fork'
# (or 'forking'). Any other value will result in serial request
# processing, which also is the default.
# 

#concurrency: thread
#concurrency: fork


# 
# The cache option affects the underlying cache mechanism that the
# server uses to maintain compiled Spyce scripts. The general format
# for this option is TYPE:INFO, where TYPE defines the cache handler
# and INFO is specific to that cache handler. Currently, Spyce
# supports two cache handlers:
#   - memory: the default, takes no parameters
#   - file: store compiled Spyce scripts to files on disk in some
#        directory; the INFO is the directory to use
#

#cache: file:/tmp


# 
# The debug option affects the caching of compiled Spyce files and
# Spyce modules. When it is turned on, then caching is disabled. It
# should NOT be used in a production environment, as compilation is
# not a optimized (fast) process. The values '0', 'off' or 'false'
# disable debugging. Any other value turns it on. The default, if
# omitted is off.
# 

#debug: 1



###############
#
# The globals section defines server-wide constants. The values can be
# arbitrary Python expressions. These values are evaluated and stored
# in a hashtable under the given option name. The hashtable is
# accessible as "pool.globals" within any Spyce file (with the pool
# method loaded), or as self.wrapper.server.globals within any Spyce
# module.
# 

[globals]

#name: "My Website"
#four: 2+2



###############
#
# The www section defines options for the built-in Spyce web server.
# 

[www]

# 
# The root option defines the document root of the webserver from
# which all requests are processed. This option can be overridden from
# the command-line. The default is the current directory when the
# server is started.
#

#root: /var/www/html


#
# The port option defines which TCP port the server will listen on.
# The default is port 80.
# 

#port: 8000


#
# The mime option is a comma-separated list of files. The files should
# be definitions of mime-types for common file extensions in the
# standard Apache format. The default is to read the spyce.mime file
# in Spyce installation directory.
#

#mime: /etc/mime.types


#
# The ext_ and ext_foo options define the default handler and the
# handler used for files ending in .foo, respectively. The currently
# supported handlers are:
#   spyce - process the file at the requested path a spyce script
#   dump  - transfer the file at the requested path verbatim, 
#     providing an appropriate "Content-type" header, if it is known.
# By default, all .spy files are processed via the spyce handler, and 
# all others through the dump handler.
#

#ext_html: spyce
#ext_: spyce

3.6.2. path

The path option specifies additional module and import directories searched by the Spyce engine. Specifically, the Spyce path (the path searched to resolve [[.import]] directives) is the Spyce home modules directory, plus the directories specified here, plus the directory of the current Spyce file. The directories specified here are also added to the sys.path, so that import statements search these directories as well. The format for this option is a colon-separated (or semicolon-separated, depending on your operating system) list of directories. If path is omitted from the configuration file, the engine will check for a SPYCE_PATH variable in the process environment, and use that. Failing this, the default is an empty string.

3.6.3. import

The import option specifies Python modules (not Spyce modules) that should be loaded (i.e. imported) at server startup. This can be useful to perform initialization at server startup. Using the pool module is prefered for storing long-lived values. The string for this option is a comma-separated list of Python module names. In CGI-mode, the Spyce engine is restarted on each request, so these modules will be loaded on each request. For other configurations, the import is performed once. To use this module, you will still need to import the module in your Spyce modules or Spyce files, but it will be taken from the Python module cache.

3.6.4. error

The error option specifies an error handling function to call for server-level errors. Server-level errors include: spyce.spyceNotFound, spyce.spyceForbidden, spyce.spyceSyntaxError and spyce.pythonSyntaxError. The string should be of the format "module:function", and the module should be on the server path. The function is invoked as function(server, request, response, error) to generate the error output. The default is "error:serverHandler". Please look at the serverHandler function in the error module, if you are considering writing your own server error handler. File-level (or request-level) errors are handled using the error module.

3.6.5. pageerror

The pageerror option specifies the default page-level error handler used by the error module. Page-level errors include all runtime errors that occur during the processing of a Spyce (i.e. after the compilation phase has completed successfully). This option can take one of two forms: 1) "string:module:variable", where string is a literal, module specifies a module in the server path and variable is the name of a variable containing the default error template Spyce string; or 2) "file:filename", where file is a literal and filename is the name of the Spyce file to use as the default error template. The default value for this option is "string:error:defaultErrorTemplate". Please refer to this string in the error module to see how to define your own page-level error handlers. 3.6.6. concurrency

The concurrency option specifies the concurrency mode that the engine will operate in. The Spyce engine can process requests serially (i.e. non-concurrently or one-at-a-time), or in parallel using either process-level (forking) or thread-level (threading) concurrency. Note that this option affects the request processing parallelism of a single engine. Request processing parallelism may also be acheived by running multiple instances of the engine, and this choice is independent of the follow discussion.

The "one shot" Spyce configurations, namely CGI-based or command-line execution, are unaffected by the concurrency option, since the server only processes one request per Spyce engine invocation.

The FastCGI configuration does not currently support parallel request processing due to a limitation of the FastCGI interface code (not anything inherent to the core of the Spyce engine). This will be remedied in the future.

The mod_python configuration does not support parallel request processing within a single engine, since it is not supported by the mod_python interface. In fact, many mod_python compilations do not even allow threading in the Python interpretter. However, mod_python configurations run one Spyce engine per Apache child, and achieve request processing parallelism in this way.

Lastly, the Spyce engine can be run in webserver mode. This mode is affected by the concurrency mode. The webserver runs in serial mode by default. It can fork a new process for each request. Alternatively, it can spawn a new thread for each request, if threading is supported by the Python interpretter.

The possible values for the concurrency option are 'thread' (or 'threading') and 'fork' (or 'forking'). Any other value results in a serial execution, which is also the default.

3.6.7. cache

The cache option allows the user to select the underlying Spyce caching implementation used. The format of the string for this option is "type:info", where the type specifies the cache type, and the optional info parameter is passed on to the cache implementation for initialization. Spyce currently supports the following cache implementations:

  • memory: This is the default cache type, which uses an in-memory dictionary to store the compiled Spyces. It takes no parameters.
  • file: The file-based cache type will serialize the compiled Spyces and store them in a given directory. The info parameter should be the directory location.

3.6.8. debug

The debug option specifies whether the engine will run in debug mode. Debug mode currently only affects the caching of Spyce files and Spyce modules. Namely, Spyce files are always recompiled and Spyce modules are not cached (are reloaded on each request) in debug mode, which is useful for module developers. This switch should not be used in a production environment, because compilation will slow things down considerably! The default value for this option is off. The values '0', 'false', and 'off' represent production mode (as does leaving out the debug option entirely), while any other value for this option turns debugging on.

3.6.9. globals

The globals section is also entirely optional. Any "key: expr" pair will be stored in a hashtable, h, as h[key]=eval(expr). In other words, the expression is evaluated first, and the result is stored in the hashtable using the given key. This hashtable is accessible as self.wrapper.server.globals within any Spyce module, or as pool.globals within any Spyce file (with the pool module loaded).

3.6.10. www

The www section defines values for the built-in Spyce webserver.

The root option defines the root directory from which all requests are served. By default, this is the current directory. It may be overridden on the command-line.

The port option defines the port at which the web server will listen. By default, this is port 80, the regular HTTP port. It may be overridden using the -p command-line switch.

The mime option is a comma-separated list of files that define mime-types for common file extensions. The files should be in the standard Apache format. By default, the Spyce engine will read a file called spyce.mime in from the installation directory.

The ext_ and ext_foo (where foo can be replaced by any extension) define the handler mapping. The legitimate values are: spyce and dump. The spyce handler processes the file at the requested path as a spyce script. The dump handler transfers the file at the requested path verbatim, using the appropriate "Content-type" header, if it is known. ext_foo option defines a mapping for a file that ends with foo. The ext_ option defines the default handler. By default, all files ending with .spy are processed by the spyce handler, and all other files are processed using the dump handler.

3.7. Programmatic Interface

It is also possible to embed Spyce into another program. All you need is to run or embed a Python interpretter. The most convenient entry points are:

  • spyceFileHandler( request, response, filename ), in spyce.py, and
  • spyceStringHandler( request, response, code ), in spyce.py.

4. MODULES

The Spyce language, as described above, is simple and small. The Spyce compiler merely embeds the power of Python using special Spyce tags. Most functionality is provided at runtime through Spyce modules and Python modules. A suite of standard Spyce modules is included with the Spyce distribution. The standard Python modules are included in the Python distribution. You, of course, may also write new Spyce modules and Python modules, or use code contributed (sourceforge link) by others to extend the base functionality available in Spyce.

It is important, from the outset, to define what a Spyce module is, and is not. Specifically, it is important to differentiate a Spyce module from a Python module. A Python module is a file with Python code, usually with a common theme, and not necessarily related to Spyce. In contrast, a Spyce module is a file with Python code that is written in a specific way to interact directly with the Spyce runtime engine. A Spyce module may access the internal request and response structures, require per-request startup and tear-down callbacks from the engine, build on the existing standard modules, or alter the behaviour of the runtime engine in some way, whereas a Python module does not.

Both can be imported and used with equal ease at runtime. Spyce modules are imported using the Spyce [[.import]] directive. Python modules are imported using Python import keyword. Remember that modules need to have the same read permissions as regular files that you expect the web server to read.

Once included, a module may be accessed anywhere in the Spyce code as a global variable. Spyce modules are objects. They provide methods and fields. One uses them as regular Python objects. Module are started before pre-Spyce processing, and finished post-Spyce processing. They are initialized with optional arguments during Spyce processing at the point of the Spyce directive.

Modules may be renamed from their defaults using the as attribute, though this is discouraged in most cases. Doing this may cause unexpected behaviour. The session module, for example, may expect to find or otherwise load a module named cookie in the Spyce environment, if cookies are chosen for session management; the taglib module expects to find the standard stdout module to capture the output of tag body processing; the stdout and filter modules interact very closely with the response module; etc.

The following standard Spyce modules are loaded implictly into the spyce environment, because they are required for Spyce operation: request, response, error, stdout, spylambda and taglib. Of the standard Python modules, only the __builtins__ module is imported, along with a number of Spyce exceptions from the spyceException module.

Below, we document each individual standard Spyce module, and then describe how one would write new Spyce modules.

4.1. Request

The request module is loaded implicitly into every Spyce environment. It provides the following methods:

  • uri( [component] ):
    Returns the request URI, or some component thereof. If the optional component parameter is specified, it should be one of the following strings: 'scheme', 'location', 'path', 'parameters', 'query' or 'fragment'.
  • method():
    Returns request method type (GET, POST, ...)
  • query():
    Returns the request query string
  • get( [name], [default], [ignoreCase] ):
    Returns request GET information. If name is specified then a single list of values is returned if the parameter exists, or default, which defaults to None, if the parameter does not exist. Parameters without values are skipped, though empty string values are allowed. If name is omitted, then a dictionary of lists is returned. If ignoreCase is true, then the above behaviour is performed in a case insensitive manner (all parameters are treated as lowercase).
  • get1( [name], [default], [ignoreCase] ):
    Returns request GET information, similarly to (though slightly differently from) the function above. If name is specified then a single string is returned if the parameter exists, or default, which default to None, if the parameter does not exist. If there is more than one value for a parameter, then only one is returned. Parameters without values are skipped, though empty string values are allowed. If name is omitted, then a dictionary of strings is returned. If the optional ignoreCase flag is true, then the above behaviour is performed in a case insensitive manner (all parameters are treated as lowercase).
  • post( [name], [default], [ignoreCase] ):
    Returns request POST information. If name is specified then a single list of values is returned if the parameter exists, or default, which defaults to None, if the parameter does not exist. Parameters without values are skipped, though empty string values are allowed. If name is omitted, then a dictionary of lists is returned. If ignoreCase is true, then the above behaviour is performed in a case insensitive manner (all parameters are treated as lowercase). This function understands form information encoded either as 'application/x-www-form-urlencoded' or 'multipart/form-data'. Uploaded file parameters are not included in this dictionary; they can be accessed via the file method.
  • post1( [name], [default], [ignoreCase] ):
    Returns request POST information, similarly to (though slightly differently from) the function above. If name is specified then a single string is returned if the parameter exists, or default, which defaults to None, if the parameter does not exist. If there is more than one value for a parameter, then only one is returned. Parameters without values are skipped, though empty string values are allowed. If name is omitted, then a dictionary of strings is returned. If the optional ignoreCase flag is true, then the above behaviour is performed in a case insensitive manner (all parameters are treated as lowercase). This function understands form information encoded either as 'application/x-www-form-urlencoded' or 'multipart/form-data'. Uploaded file parameters are not included in this dictionary; they can be accessed via the file method.
  • file( [name], [ignoreCase] ):
    Returns files POSTed in the request. If name is specified then a single cgi.FieldStorage class is returned if such a file parameter exists, otherwise None. If name is omitted, then a dictionary of file entries is returned. If the optional ignoreCase flag is true, then the above behaviour is performed in a case insensitive manner (all parameters are treated as lowercase). The interesting fields of the FieldStorage class are:

    • name: the field name, if specified; otherwise None
    • filename: the filename, if specified; otherwise None; this is the client-side filename, not the filename in which the content is stored - a temporary file you don't deal with
    • value: the value as a string; for file uploads, this transparently reads the file every time you request the value
    • file: the file(-like) object from which you can read the data; None if the data is stored a simple string
    • type: the content-type, or None if not specified
    • type_options: dictionary of options specified on the content-type line
    • disposition: content-disposition, or None if not specified
    • disposition_options: dictionary of corresponding options
    • headers: a dictionary(-like) object (sometimes rfc822.Message or a subclass thereof) containing *all* headers

  • __getitem__( key ):
    The request module can be used as a dictionary: i.e. request['foo']. This method first calls the get1() method, then the post1() method and lastly the file() method trying to find the first non-None value to return.
  • getpost( [name], [default], [ignoreCase] ):
    Using given parameters, return get() result if not None, otherwise return post() result if not None, otherwise default.
  • getpost1( [name], [default], [ignoreCase] ):
    Using given parameters, return get1() result if not None, otherwise return post1() result if not None, otherwise default.
  • postget( [name], [default], [ignoreCase] ):
    Using given parameters, return post() result if not None, otherwise return get() result if not None, otherwise default.
  • postget1( [name], [default], [ignoreCase] ):
    Using given parameters, return post1() result if not None, otherwise return get1() result if not None, otherwise default.
  • env( [name], [default] ):
    Returns a dictionary with CGI-like environment information of this request. If name is specified then a single entry is returned if the parameter exists, otherwise default, which defaults to None, if omitted.
  • getHeader( [type] ):
    Return a specific header sent by the browser. If optional type is omitted, a dictionary of all headers is returned.
  • filename():
    Return the original Spyce filename currently being processed.
  • default( value, value2 ):
    (convenience method) Return value if it is not None, otherwise return value2.
Dynamic web pages frequently need to access GET and POST information sent by the browser. Here is an example that shows this is done.
examples/getpost.spy
<html><body>
  Getting GET or POST request information is easy. <br>
  Use the following forms to submit GET or POST info.<br>
  <hr>
  [[-- input forms --]]
  <form action="[[=request.uri('path')]]" method=get>
    get: <input type=text name=Name>
    <input type=submit value=ok>
  </form>
  <form action="[[=request.uri('path')]]" method=post>
    post: <input type=text name=Name>
    <input type=submit value=ok>
  </form>
  <hr>
  [[-- display GET and POST information from request object --]]
  <table><tr>
      <td>request.method()</td>
      <td>[[=request.method()]]</td>
    </tr><tr>
      <td>request.query()</td>
      <td>[[=request.query()]]</td>
    </tr><tr>
      <td>request.get1('name')</td>
      <td>[[=request.get1('name')]]</td>
    </tr><tr>
      <td>request.post1('name')</td>
      <td>[[=request.post1('name')]]</td>
    </tr><tr>
      <td>request.get1('name', 1)</td>
      <td>[[=request.get1('name', 1)]]</td>
    </tr><tr>
      <td>request.post1('name', 1)</td>
      <td>[[=request.post1('name', 1)]]</td>
    </tr><tr>
      <td>request['Name']</td>
      <td>[[=request['Name'] ]]</td>
    </tr><tr>
      <td>request['name']</td>
      <td>[[=request['name'] ]]</td>
    </tr><tr>
      <td>request.get()</td>
      <td>[[=request.get()]]</td>
    </tr><tr>
      <td>request.get1()</td>
      <td>[[=request.get1()]]</td>
    </tr><tr>
      <td>request.post()</td>
      <td>[[=request.post()]]</td>
    </tr><tr>
      <td>request.post1()</td>
      <td>[[=request.post1()]]</td>
    </tr><tr>
      <td>request.getpost()</td>
      <td>[[=request.getpost()]]</td>
    </tr><tr>
      <td>request.getpost1()</td>
      <td>[[=request.getpost1()]]</td>
    </tr><tr>
      <td>request.postget()</td>
      <td>[[=request.postget()]]</td>
    </tr><tr>
      <td>request.postget1()</td>
      <td>[[=request.postget1()]]</td>
    </tr><tr>
      <td>request.get(ignoreCase=1)</td>
      <td>[[=request.get(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.get1(ignoreCase=1)</td>
      <td>[[=request.get1(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.post(ignoreCase=1)</td>
      <td>[[=request.post(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.post1(ignoreCase=1)</td>
      <td>[[=request.post1(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.getpost(ignoreCase=1)</td>
      <td>[[=request.getpost(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.getpost1(ignoreCase=1)</td>
      <td>[[=request.getpost1(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.postget(ignoreCase=1)</td>
      <td>[[=request.postget(ignoreCase=1)]]</td>
    </tr><tr>
      <td>request.postget1(ignoreCase=1)</td>
      <td>[[=request.postget1(ignoreCase=1)]]</td>
  </tr></table>
</body></html>
Run this code.
(requires Spyce-enabled web server)

The example below presents the results of all the method calls list above. Run it to understand the information available.

examples/request.spy
<html><body>
  Using the Spyce request object, we can obtain 
  information sent along with the request. The 
  table below shows some request methods and their 
  return values. Use the form below to post form 
  data via GET or POST. <br>
  <hr>
  [[-- input forms --]]
  <form action="[[=request.uri('path')]]" method=get>
    get: <input type=text name=name>
    <input type=submit value=ok>
  </form>
  <form action="[[=request.uri('path')]]" method=post>
    post: <input type=text name=name>
    <input type=submit value=ok>
  </form>
  <hr>
  [[-- tabulate response information --]]
  <table border=1>
    <tr>
      <td><b>Method</b></td>
      <td><b>Return value</b></td>
    </tr>
    [[ for method in ['uri()', 'uri("path")',
      'uri("query")', 'method()','query()',
      'get()','get1()', 'post()','post1()',
      'getHeader()','env()', 'filename()']: { 
    ]]
      <tr>
        <td valign=top>request.[[=method]]</td>
        <td>[[=eval('request.%s' % method)]]</td>
      </tr>
    [[ } ]]
  </table>
</body></html>
Run this code.
(requires Spyce-enabled web server)

A more complicated form...

examples/form.spy
<html><body>
Form handling. <br><hr>
<b>Form:</b><br>
<form method=post action="[[=request.uri('path')]]">
<table>
  <tr>
    <td valign=top>Text field:</td>
    <td><input name="textField" type=text value="" size=15></td>
  </tr><tr>
    <td valign=top>Textarea field:</td>
    <td><textarea name="textareaField" rows="5" cols="30"></textarea></td>
  </tr><tr>
    <td valign=top>Select field:</td>
    <td><select name="selectField">
      <option>Option 1</option>
      <option>Option 2</option>
      <option>Option 3</option>
    </select></td>
  </tr><tr>
    <td valign=top>Radio field:</td>
    <td>
      <input type="radio" name="radioField" value="Option 1">Option 1<br>
      <input type="radio" name="radioField" value="Option 2">Option 2<br>
      <input type="radio" name="radioField" value="Option 3">Option 3<br>
    </td>
  </tr><tr>
    <td valign=top>Checkbox field:</td>
    <td>
      <input type="checkbox" name="checkField" value="Option 1">Option 1<br>
      <input type="checkbox" name="checkField" value="Option 2">Option 2<br>
      <input type="checkbox" name="checkField" value="Option 3">Option 3<br>
    </td>
  </tr><tr>
    <td valign=top></td>
    <td><input type=submit value="Submit"></td>
  </tr>
</table>
</form><hr>

[[p=request.post1]]
<b>Results:</b><br>
<table>
  <tr><td>Text field:</td><td>[[=p('textField')]]</td></tr>
  <tr><td>Textarea field:</td><td>[[=p('textareaField')]]</td></tr>
  <tr><td>Select field:</td><td>[[=p('selectField')]]</td></tr>
  <tr><td>Radio field:</td><td>[[=p('radioField')]]</td></tr>
  <tr><td>Checkbox field:</td><td>[[=request.post('checkField')]]</td></tr>
</table>
</body></html>
Run this code.
(requires Spyce-enabled web server)

Lastly, the following example shows how to deal with uploaded files.

examples/fileupload.spy
[[\ 
if request.post('ct'):
  response.setContentType(request.post1('ct'))
  if request.file('upfile')!=None:
    response.write(request.file('upfile').value)
  else:
    print 'file not properly uploaded'
  raise spyceDone
]]
<html><body>
  Upload a file and it will be sent back to you.<br>
  [[-- input forms --]]
  <hr>
  <table>
    <form action="[[=request.uri('path')]]" method=post 
        enctype="multipart/form-data">
      <tr>
        <td>file:</td>
        <td><input type=file name=upfile></td>
      </tr><tr>
        <td>content-type:</td>
        <td><input type=text name=ct value="text/html"></td>
      </tr><tr>
        <td><input type=submit value=ok></td>
      </tr>
    </form>
  </table>
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.2. Response

Like the request module, the response module is also loaded implicitly by every Spyce. It provides the following methods:

  • write( string ):
    Sends a string to the client. All writes are buffered by default and sent at the end of Spyce processing to allow appending headers, setting cookies and exception handling. Note that using the print statement is often easier, and stdout is implicitly redirected to the browser.
  • writeln( string ):
    Sends a string to the client, and appends a newline.
  • writeStatic( string ):
    All static HTML strings are emitted to the client via this method, which (by default) simply calls write(). This method is not commonly invoked by the user.
  • writeExpr( object ):
    All expression results are emitted to the client via this method, which (by default) calls write() with the str() of the result object. This method is not commonly invoked by the user.
  • clear( ): Clears the output buffer.
  • flush( ): Sends buffered output to the client immediately. This is a blocking call, and can incur a performance hit.
  • setContentType( contentType ):
    Sets the MIME content type of the response.
  • setReturnCode( code ):
    Set the HTTP return code for this response. This return code may be overriden if an error occurs or by functions in other modules (such as redirects).
  • addHeader( type, data, [replace] ):
    Adds the header line "type: data" to the outgoing response. The optional replace flag determines whether any previous headers of the same type are first removed.
  • unbuffer():
    Turns off buffering on the output stream. In other words, each write is followed by a flush(). An unbuffered output stream should be used only when sending large amounts of data (ie. file transfers) that would take up server memory unnecessarily, and involve consistently large writes. Note that using an unbuffered response stream will not allow the output to be cleared if an exception occurs. It will also immediately send any headers.
  • isCancelled():
    Returns true if it has been detected that the client is no longer connected. This flag will turn on, and remain on, after the first client output failure. However, the detection is best-effort, and may never turn on in certain configurations (such as CGI) due to buffering.
  • timestamp( [thetime] ):
    Timestamps the response with an HTTP Date: header, using the optional thetime parameter, which may be either be the number of seconds since the epoch (see Python time module), or a properly formatted HTTP date string. If thetime is omitted, the current time is used.
  • expires( [thetime] ):
    Sets the expiration time of the response with an HTTP Expires: header, using the optional thetime parameter, which may be either the number of seconds since the epoch (see Python time module), or a properly formatted HTTP date string. If thetime is omitted, the current time is used.
  • expiresRel( [secs] ):
    Sets the expiration time of the response relative to the current time with an HTTP Expires: header. The optional secs (which may also be negative) indicates the number of seconds to add to the current time to compute the expiration time. If secs is omitted, it defaults to zero.
  • lastModified( [thetime] ):
    Sets the last modification time of the response with an HTTP Last-Modified: header, using the optional thetime parameter, which can be either the number of seconds since the epoch (see Python time module), or a properly formatted HTTP date string, or None indicating the current time. If thetime is omitted, this function will default to the last modification time of the Spyce file for this request, and raise an exception if this time can not be determined. Note that, as per the HTTP specification, you should not set a last modification time that is beyond the response timestamp.
  • uncacheable():
    Sets the HTTP/1.1 Cache-Control: and HTTP/1.0 Pragma: headers to inform clients and proxies that this content should not be cached.
The methods are self-explanatory. One of the more interesting things that one could do is to emit non-HTML content types. The example below emits the Spyce logo as a GIF.

examples/gif.spy
[[.import name=include ]]
[[\
  # Spyce can also generate other content types
  # The following code displays the Spyce logo
  response.setContentType('image/gif')
  response.write(include.dump('spyce.gif', 1))
  raise spyceDone
]]
Run this code.
(requires Spyce-enabled web server)

4.3. Error

The error module is implicitly loaded and provides error-handling functionality. An error is any unhandled runtime exception that occurs during Spyce processing. This mechanism does not include exceptions that are not related to Spyce processing (i.e. server-related exceptions), that can be caused before or after Spyce processing by invalid syntax, missing files and file access restrictions. To install a server-level error handler use a configuration file. The default page-level error handler can also be modified in the configuration file. This module allows the user to install page-level error handling code, overriding the default page-level handler, by using one of the following functions:

  • setStringHandler( string ):
    Installs a function that will processes the given string, as Spyce code, for error handling.
  • setFileHandler( file ):
    Installs a function that will processes the given file for error handling.
  • setHandler( fn ):
    Installs the fn function for error handling. The function is passed one parameter, a reference to the error module. From this, all the error information as well as references to other modules and Spyce objects can be accessed.
The error module provides the following information about an error:

  • isError():
    Returns whether an error is being handled.
  • getMessage():
    Return the error message; the string of the object that was raised, or None if there is no current error.
  • getType():
    Return the error type; the type of the object that was raised, or None if there is no current error.
  • getFile():
    Return the file where the error was raised, or None if there is no current error.
  • getTraceback():
    Return the stack trace as an array of tuples, or None if there is no current error. Each tuple entry is of the form: (file, line numbers, function name, code context).
  • getString():
    Return the string of the entire error (the string representation of the message, type, location and stack trace), or None if there is no current error.
The default error handling function uses the following string handler:

[[.module name=transform]]
[[transform.expr('html_encode')]]
<html>
<title>Spyce exception: [[=error.getMessage()]]</title>
<body>
<table border=0>
  <tr><td colspan=2><h1>Spyce exception</h1></td></tr>
  <tr><td valign=top><b>File:</b></td><td>[[=error.getFile()]]</tr>
  <tr><td valign=top><b>Message:</b></td>
    <td><pre>[[=error.getMessage()]]</pre></tr>
  <tr><td valign=top><b>Stack:</b></td><td>
    [[ for frame in error.getTraceback(): { ]]
      [[=frame[0] ]]:[[=frame[1] ]], in [[=frame[2] ]]:<br>
      <table border=0><tr><td width=10></td><td>
        <pre>[[=frame[3] ]]</pre>
      </td></tr></table>
    [[ } ]]
    </td></tr>
</table>
</body></html>

The example below shows the error module in use. Error handling can often be used to send emails notifying webmasters of problems, as this example shows.

examples/error.spy
[[error.setFileHandler('error.spi') ]]
This is a page with an error...
[[ raise 'an error' ]]
Run this code.
(requires Spyce-enabled web server)

examples/error.spi
<h1>Oops</h1>
An error occurred while processing your request. 
We have logged this for our webmasters, and they 
will fix it shortly. We apologize for the inconvenience.
In the meantime, please use the parts of our site that 
actually do work... <a href="somewhere">somewhere</a>.
[[\
  # could redirect the user immediately
  #response.getModule('redirect').external('somewhere.spy')

  # could send an email
  import time
  msg = '''
time: %s
error: %s
env: %s
other info...
''' % (
    time.asctime(time.localtime(time.time())), 
    error.getString(),
    request.env()
  )
  #function_to_send_email('webmaster@foo.com', msg)

  #or perform other generic error handling...
]]

This mechanism is not a subsititute for proper exception handling within the code itself, and should not be abused. It does, however, serve as a useful catch-all for bugs that slip through the cracks.

4.4. Stdout

The stdout module is loaded implicitly and redirects Python's sys.stdout (in a thread-safe manner) to the appropriate response object for the duration of Spyce processing. This allows one to use print, without having to write print >> response, .... The stdout module provides a variable stdout.stdout, which refers to the original stream, but is unlikely to be needed. It may also be useful to know that sys.stderr is, under many configurations, connected to the webserver error log.

In addition, the stdout module provides the following functions for capturing or redirecting output:

  • push( [filename] ):
    Begin capturing output. Namely, the current output stream is pushed onto the stack and replaced with a memory buffer. An optional filename may be associated with this operation (see pop() method below).
  • pop():
    Close current output buffer, and return the captured output as a string. If a filename was associated with the push(), then the string will also be written to that file.
  • capture(f, [*args], [**kwargs] ):
    Push the current stream, call the given function f with any supplied arguments *args and keyword arguments **kwargs, and then pop it back. Capture returns a tuple (r,s), where r is the result returned by f and s is a string of its output.

The example below show how the module is used:

examples/stdout.spy
<html><body>
  [[ print '''Using the stdout module redirects 
    stdout to the response object, so you can use
    <b>print</b>!''']]<br>
  redirecting stdout can be used to...
  [[stdout.push()]]
  [[print 'capture']] out[[='put']]
  [[cached = stdout.pop()]]
  ... for later: <br>
  [[=cached]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.5. Spylambda

The spylambda module is loaded implicitly and allows the definition of functions based on Spyce scripts. The spylambda module provides the following methods:

  • define( args, code, [memoize] ):
    Returns a function that accepts the given args and executes the Spyce script defined by the code parameter. Note that the code is compiled immediately and that spyce.spyceSyntaxError or spyce.spycePythonError exceptions can be thrown for invalid code arguments. The optional memoize parameter sets whether the spyce can or can not be memoized, with the default being false. Memoizing a function means capturing the result and output and caching them, keyed on the function parameters. Later, if a function is called again with the same parameters, the cached information is returned, if it exists, and the function may not actually be called. Thus, you should only memoize functions that are truly functional, i.e. they do not have side-effects: they only return a value and output data to the response object, and their behaviour depends exclusively on their parameters. If you memoize code that does have side-effects, those side-effects may not occur on every invocation.
  • __call__( args, code, _spyceCache ):
    This is an alias to the define function. Because of the special method name, the spylambda module object can be called as if it were a function.
This function is not frequently called directly from Spyce code, because writing the Spyce code argument in a manner that does not conflict with the Spyce tag delimiters is cumbersome. Rather the Spyce lambda syntax is used and translated into this function call at compilation time, as in the example below.

examples/spylambda.spy
[[
  # table template
  table = [[spy! title, data: 
    <table>
      <tr>
        [[for cell in title: {]]
          <td><b>[[=cell]]</b></td>
        [[}]]
      </tr>
      [[for row in data: {]]
        <tr>
          [[for cell in row: {]]
            <td>[[=cell]]</td>
          [[}]]
        </tr>
      [[}]]
    </table> 
  ]]

  # table information
  title = ['Country', 'Size', 'Population', 'GDP per capita']
  data = [
    [ 'USA', '9,158,960', '280,562,489', '$36,300' ],
    [ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
    [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
  ]
]]

[[-- emit web page --]]
<html><body>
  [[ table(title, data) ]]
</body></html>

Run this code.
(requires Spyce-enabled web server)

It often useful to use the spylambda module directly from other Spyce modules that may need to perform significant amounts of output. Rather than calling print repeatedly, it is more convenient to invoke a Spyce, as in the example below. Though highly simplified, this example also shows how Spyce lambdas can be used to easily build a complex rendering environment.

examples/myPortal.spy
[[.import name=myPortal]]
[[
# this data might be pulled from a database
news = {
  'heading': 'News',
  'data': [
    ('<a href="http://www.nytimes.com">nyt</a>', 
      'today', 'sun rose'),
    ('<a href="http://www.cnn.com">cnn</a>', 
      'yesterday', 'sun set'),
    ('<a href="http://news.google.com">goo</a>', 
      'long time ago', 'let there be light!'), ] }
weather = {
  'heading': 'Weather',
  'data': [
    ('nyc', 'too cold'),
    ('seattle', 'too wet'),
    ('tucson', 'too dry'),
    ('houston', 'too humid'),
    ('chicago', 'too windy'),
    ('<a href="http://www.carrier.com/">carrier</a>', 
      'just right'), ] }
movies = {
  'heading': 'Movies',
  'data': [
    ('over-priced theatre', '15 movies'),
    ("'el cheapo", '3 movies'),
    ('home', 'blockbuster'), ] }
selection = [ news, movies, weather ]
]]

<html><body>
    Dear user [[='XYZ']], <br>
    Welcome to your portal. Here are your selected views... <br>
    [[-- a module that does a lot of output --]]
    [[ myPortal.show( selection ) ]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

examples/myPortal.py
from spyceModule import spyceModule

__doc__ = '''This module takes care of presenting the portal.
Spyce lambdas are easier to use to perform the output.'''

class myPortal(spyceModule):
  def start(self):
    self.show = self._api.getModule('spylambda')(spysigPortal, spycodePortal)
    self.showView = self._api.getModule('spylambda')(spysigItem, spycodeItem)

spysigPortal = 'selection'
spycodePortal = '''
  <html><body>
    <table align=center valign=center border=1 width=100% bgcolor="#aaaaaa"><tr>
      <td width=30% >
        [[ for view in selection: {]]
          [[myPortal.showView(view['heading'], view['data'])]]
          <p>
        [[ } ]]
      </td>
      <td align=center valign=center width=50% ><b>main panel</b></td>
      <td align=center valign=center width=20% ><b>other stuff</b></td>
    </tr><table>
  </body></html>
'''

spysigItem = 'heading, data'
spycodeItem = '''
  <table cellspacing=0 border=0 bgcolor="#ffdddd" width=100% >
    <tr><td bgcolor="#bbddff" colspan=[[=max(map(len, data))]]>
      <b>[[=heading]]<b>
    </td></tr>
    [[ for row in data: { ]]
      <tr> 
        [[ for i in row: { ]]
          <td>[[=i]]</td> 
        [[ } ]]
      </tr>
    [[ } ]]
  </table>
'''

4.6. Taglib

The taglib module is loaded implicitly and supports the active tags functionality at runtime. It is expected that the casual user will not have much use for this module, and will only call its functions indirectly by importing tag libraries and using active tags. Primarily, this is because the methods interoperate very tightly and require a very strict calling sequence, which is generated by the Spyce compiler for each active tag it encounters. Nevertheless, for completeness, the taglib module provides the following methods:

  • context: This field is a dictionary that serves as the context in which tags operate. Tags can store variables and evaluate expressions within this context. The tag context contains references to all the loaded modules. Thus, it is valid to refer to something like request.query() in a tag expression. However, it is not valid to change any module variable references. While this will not cause any harm, the user should expect that these new values can be reset by the runtime at any time.
  • load( libname, [libfrom], [libas] ):
    Loads a tag library class named libname from a file called libfrom in the search path, and installed it under the tag prefix libas. The default for libfrom is libname.py. The default for libas is libname. Once installed, a library name is its unique tag prefix.
  • unload( libname ):
    Unload a tag library that is installed under the libname prefix. This is usually performed only at the end of a request.
  • tagPush( libname, tagname, pair ):
    Push a new tag object for a libname:tagname tag onto the tag stack. The pair parameter is a flag indicating whether this is a singleton or a paired tag.
  • tagPop():
    Pop the current tag from the tag stack.
  • getTag():
    Return the current tag object.
  • outPush():
    Begin capturing the current output stream. This is usually called by the tagBegin method.
  • outPopCond():
    End capturing the current output stream, and return the captured contents. It will only "pop" once, even if called multiple times for the same tag. This method is usually called by either the tagEnd(), tagCatch, or tagPop() methods.
  • tagBegin( attrs ):
    This method sets the tag output and variable environment, and then calls the tag's begin() method with the given attrs tag attribute dictionary. This method returns a flag, and the tag body must be processed if and only if this flag is true.
  • tagBody():
    This method sets the tag output and variable environment, and then calls the tag's body() method with the captured output of the body processing. If this method returns true, then the processing of the body must be repeated.
  • tagEnd():
    This method sets the tag output and variable environment, and then calls the tag's end() method. This method must be called if the tagBegin() method completes successfully in order to preserve tag semantics.
  • tagCatch():
    This method should be called if any of the tagBegin, tagBody or tagEnd methods raise an exception. It calls the tag's catch() method with the current exception.

As stated previously, it is expected that the user will not call these methods directly, but rather simply use the active tag functionality that this module supports. Spyce comes with various standard tag libraries. The following example shows a few simple ones in use:

examples/tag.spy
[[.taglib name=core as=spy]]
[[.taglib name=myTaglib as=me]]
<html><body>
<spy:for var=x items="=range(2,6)">
  <me:foo val="=x">size <spy:print val="=x" /></me:foo>
</spy:for>
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.7. Include

Many websites carry a theme across their various pages, which is often achieved by including a common header or footer. The include module provides exactly this functionality, and more. For example, it can also be used to define dynamic site-wide constants, and other similar globals that can not otherwise be initialized with static include directives (due to their dynamic nature). For example, language specific constants may be selected dynamically from the required language include file, based on a GET or POST language parameter or, better yet, from the Accept-Language request header. This module also provides other functions that are similar in nature. For example, it can currently pretty print Spyce code. In the future, it will be able to generate the Spyce and Spyce Powered logo, and possibly other similar trinkets.

  • spyce( file, [context] ):
    Dynamically includes the specified file, and processes it as Spyce code. The return value is that of the included Spyce file. One can optionally provide a context value to the included file. If omitted, the value defaults to None. All currently imported modules are passed along into the included file without re-initialization. However, for each explicit [[.import ]] tag in the included file, a new module is initialized and also finalized up at the end of processing. The include module provides three fields for use inside included files:

    • include.context: This field stores the value passed in at the point of inclusion. Note that if the value is one that is passed by reference (as is the case with object, list, and dictionary types), then the context may be used to pass information back to the including file, in addition to the return value.
    • include.vars: If the include context is of type dictionary, then the vars field is initialized, otherwise it is None. The vars field provides attribute-based access to the context dictionary, merely for convenience. In other words, self.vars.x is equivalent to self.context['x'].
    • include.fromFile: stores the name of the file name from which this file was included.
    Note that either the locals() or globals() dictionaries may be passed in as include contexts. However, be advised that due to Python optimizations of local variable access, any updates to the locals() dictionary may not be reflected in the local namespace under all circumstances and all versions of Python. In fact, this is the reason why the context has been made explicit, and does not simply grab the locals() dictionary. It may, however, safely be used for read access. With respect to the globals() dictionary, it is not advised to pollute this namespace.
  • dump( file, [binary] ):
    Contents of the file are returned. If the binary parameter is true, the file is opened in binary mode. By default, text mode is used.
  • spycecode( file ):
    Contents of the file are returned as HTML formatted Spyce code.
The example below (taken from this documentation file), uses a common header template only requiring two context variables to change the title and the highlighted link:
  [[.import name=include]]
  [[include.spyce('inc/head.spi', 
      {'pagename': 'Documentation', 
       'page': 'manual.html'}) ]]

In head.spi, we use this information to set the title:

  [[.import name=include]]
  <title>[[=include.context['pagename'] ]]</title>

By convention, included files are given the extension .spi.

Below we contrast the difference between static and dynamic includes. A dynamic include is included on each request; a static include is inserted at compile time. A static include runs in the same context, while a dynamic include has a separate context.

examples/include.spy
[[.import name=include]]
<html><body>
  main file<br>
  <hr>
  [[
    context = {'foo': 'old value'}
    result=include.spyce('include.spi', context)
  ]]
  <hr>
  main file again<br>
  context: [[=context]]<br>
  return value: [[=result]]<br>
</body></html>
Run this code.
(requires Spyce-enabled web server)

examples/include.spi
begin include<br>
context: [[=include.context ]]<br>
from: [[=include.fromFile ]]<br>
foo was [[=include.vars.foo]]<br>
setting foo to 'new value' [[include.vars.foo = 'new value']]<br>
returing 'retval'<br>
end include<br>
[[ return 'retval' ]]

examples/includestatic.spy
<html><body>
  [[x=1]]
  main file<br>
  x=[[=x]]<br>
  <hr>
  [[.include file="includestatic.spi"]]
  <hr>
  main file again<br>
  x=[[=x]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

examples/includestatic.spi
begin included file<br>
changing value of x<br>
[[x=2]]
end included file<br>

4.8. Transform

The transform module contains useful text transformation functions, commonly used during web-page generation.

  • html_encode( string, [also] ):
    Returns a HTML-encoded string, with special characters replaced by entity references as defined in the HTML 3.2 and 4 specifications. The optional also parameter can be used to encode additional characters.
  • url_encode( string, ):
    Returns an URL-encoded string, with special characters replaced with %XX equivalents as defined by the URI RFC document.
The transform module also be used to intercept and insert intermediate processing steps when response.writeStatic(), response.writeExpr() and response.write() are called to emit static html, expressions and dynamic content, respectively. It can be useful, for example, to automatically ensure that expressions never produce output that is HTML-unsafe, in other words strings that contain characters such as &, < and >. Many interesting processing functions can be defined. By default, the transform module leaves all output untouched. These processing functions, called filters, can be inserted via the following module functions:

  • static( [ fn ] ):
    Defines the processing performed on all static HTML strings from this point forwards. The fn parameter is explained below.
  • expr( [ fn ] ):
    Defines the processing performed on all the results of all expression tags from this point forwards. The fn parameter is explained below.
  • dynamic( [ fn ] ):
    Defines the processing performed on all dynamic content generated, i.e. content generated using response.write in the code tags. The fn parameter is explained below.

Each of the functions above take a single, optional parameter, which specifies the processing to be performed. The parameter can be one of the following types:

  • None:
    If the paramter is None, or omitted, then no processing is performed other converting the output to a string.
  • Function:
    If a parameter of function type is specified, then that function is called to process the output. The output can be any Python type, and the output may be any Python type. The result is then converted into a string for output. The first parameter to a filter will always be the object to be processed for output. However, the function should be properly defined so as to possibly accept other parameters. The details of how to define filters are explained below.
  • String:
    If a paramter of string type is specified, then the string should be of the following format: "file:name", where file is the location where the function is defined and name is the name of the filter. The file component is optional, and is searched for using the standard module-finding rules. If only the function name is specified, then the default location (inside the transform module itself) is used, where the standard Spyce filters reside. The standard Spyce filters are described below.
  • List / Tuple:
    If a parameter of list or tuple type is specified, its elements should be functions, strings, lists or tuples. The compound filter is recursively defined as f=fn(...f2(f1())...), for the parameter (f1,f2,...,fn).

Having explained how to install filters, we now list the standard Spyce filters and show how they are used:

  • ignore_none( o ):
    Emits any input o except for None, which is converted into an empty string.
  • truncate( o, [maxlen] ):
    If maxlen is specified, then only the first maxlen characters of input o are returned, otherwise the entire original.
  • html_encode( o, [also] ):
    Converts any '&', '<' and '>' characters of input o into HTML entities for safe inclusion in among HTML. The optional also parameter can specify, additional characters that should be entity encoded.
  • url_encode( o ):
    Converts input o into a URL-encoded string.
  • nb_space( o ):
    Replaces all spaces in input o with "&nbsp;".
  • silence( o ):
    Outputs nothing.

The optional parameters to some of these filters can be passed to the various write functions as named parameters. They can also be specified in an expression tag, as in the following example. (One should simply imagine that the entire expression tag is replaced with a call to response.writeExpr).

[[.import name=transform]]
[[ transform.expr(("truncate", "html_encode")) ]]
[[='This is an unsafe (< > &) string... '*100, maxlen=500]] 

In the example above, the unsafe string is repeated 100 times. It is then passed through a truncate filter that will accept only the first 500 characters. It is then passed through the html_encode filter that will convert the unsafe characters into their safe, equivalent HTML entities. The resulting string is emitted.

The parameters (specified by their names) are simply accepted by the appropriate write method (writeExpr() in the case above) and passed along to the installed filter. Note that in the case of compound filters, the parameters are passed to ALL the functions. The html_encode filter is written to ignore the maxlen parameter, and does not fail.

For those who would like to write their own filters, looking at the definition of the truncate filter will help. The other standard filters are in modules/transform.py.

def truncate(o, maxlen=None, **kwargs):

When writing a filter, any function will do, but it is strongly advised to follow the model above. The important points are:

  • The input o can be of any type, not only a string.
  • The function result does not have to be string either. It is automatically stringified at the end.
  • The function can accept parameters that modify its behaviour, such as maxlen, above.
  • It is recommended to provide convenient user defaults for all parameters.
  • The last parameter should be **kwargs so that unneeded parameters are quietly passed along.

Lastly, one can retrieve filters. This can be useful when creating new functions that depend on existing filters, but can not be compounded using the tuple syntax above. For example, one might use one filter or another conditionally. For whatever purpose, the following module function is provided to retreive standard Spyce filters, if needed:

  • create( [ fn ] ):
    Returns a filter. The fn parameter can be of type None, function, string, list or tuple and is handled as in the installation functions discussed above.
The transform module is flexible, but not complicated to use. The example below is not examplary of typical use. Rather it highlights some of the flexibility, so that users can think about creative uses.

examples/transform.spy
[[.import name=transform]]
[[\
def tag(o, tags=[], **kwargs):
  import string
  pre = string.join(map(lambda x: '<'+x+'>',tags))
  tags.reverse()
  post = string.join(map(lambda x: '</'+x+'>',tags))
  return pre+str(o)+post
def bold(o, _tag=tag, **kwargs):
  kwargs['tags'] = ['b']
  return apply(_tag, (o,), kwargs)
def bolditalic(o, _tag=tag, **kwargs):
  kwargs['tags'] = ['b','i']
  return apply(_tag, (o,), kwargs)
myfilter = transform.create(['html_encode', bolditalic])
mystring = 'bold and italic unsafe string: < > &'
def simpletable(o, **kwargs):
  s = '<table border=1>'
  for row in o:
    s=s+'<tr>'
    for cell in row:
      s=s+'<td>'+str(cell)+'</td>'
    s=s+'</tr>'
  s = s+'</table>'
  return s
]]
<html><body>
  install an expression filter:<br>
  [[transform.expr(['html_encode', tag])]]
  1.[[=mystring, tags=['b','i'] ]]
  <br>
  [[transform.expr(myfilter)]]
  2.[[=mystring]]
  [[transform.expr()]]
  <p>
  or use a filter directly:<br>
  1.[[=transform.create(['html_encode',tag])(mystring,tags=['b','i'])]]
  <br>
  2.[[=myfilter(mystring)]]
  <p>
  Formatting data in a table...<br>
  [[=simpletable([ [1,2,3], [4,5,6] ])]]
  <p>
  Though the transform module is flexible, <br>
  most users will probably only install the <br>
  <b>html_encode</b> filter.
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.9. Redirect

The redirect module allows requests to be redirected to different pages, by providing the following methods:

  • internal( file ):
    Performs an internal redirect. All processing on the current page ends, the output buffer is cleared and processing continues at the named file. The browser URI remains unchanged, and does not realise that a redirect has even occurred during processing.
  • external( uri, [permanent] ):
    Performs an external redirect using the HTTP Location header to a new uri. Processing of the current file continues, but the content is ignored (ie. the buffer is cleared at the end). The status of the document is set to 301 MOVED PERMANENTLY or 302 MOVED TEMPORARILY, depending on the permanent boolean parameter, which defaults to false or temporary. The redirect document is sent to the browser, which requests the new relative uri.
  • externalRefresh( uri, [seconds] ):
    Performs an external redirect using the HTTP Refresh header a new uri. Processing of the current file continues, and will be displayed on the browser as a regular document. Unless interrupted by the user, the browser will request the new URL after the specified number of seconds, which defaults to zero if omitted. Many websites use this functionality to show some page, while a file is being downloaded. To do this, one would show the page using Spyce, and redirect with an externalRefresh to the download URI. Remember to set the Content-Type on the target download file page to be something that the browser can not display, only download.
The example below, shows the possible redirects in use:

examples/redirect.spy
[[.import name=redirect]]
<html><body>
  [[ type = request['type']
     url = request['url']
     if url and not type: {
       ]] 
       <font color=red><b>
         please select a redirect type
       </b></font><br> 
       [[
     }
     if type and url: {
       if type=='internal': redirect.internal(url)
       if type=='external': redirect.external(url)
       if type=='externalRefresh': redirect.externalRefresh(url, 3)
       ]] Received POST info: [[=request.post1()]] [[
     }
  ]]
  <form action="[[=request.uri('path')]]" method=post>
    Redirection url:
    <input type=text name=url value=hello.spy><br>
    Redirection type:
    <table border=0>
      <tr><td>
        <input type=radio name=type value=internal>
        internal
      </td></tr>
      <tr><td>
        <input type=radio name=type value=external>
        external
      </td></tr>
      <tr><td>
        <input type=radio name=type value=externalRefresh>
        externalRefresh (3 seconds)
      </td></tr>
    </table>
    <input type=submit value=redirect>
  </form>
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.10. Cookie

This module provides cookie functionality. Its methods are:

  • get( [key] ):
    Return a specific cookie string sent by the browser. If the optional cookie key is omitted, a dictionary of all cookies is returned. The cookie module may also be accessed as an associative array to achieve the same result as calling: namely, cookie['foo'] and cookie.get('foo') are equivalent.
  • set( key, value, [expire], [domain], [path], [secure] ):
    Sends a cookie to the browser. The cookie will be sent back on subsequent requests and can be retreived using the get function. The key and value parameters are required; the rest are optional. The expire parameter determines how long this cookie information will remain valid. It is specified in seconds from the current time. If expire is omitted, no expiration value will be provided along with the cookie header, meaning that the cookie will expire when the browser is closed. The domain and path parameters specify when the cookie will get sent; it will be restricted to certain document paths at certain domains, based on the cookie standard. If these are omitted, then path and/or domain information will not be sent in the cookie header. Lastly, the secure parameter, which defaults to false if omitted, determines whether the cookie information can be sent over an HTTP connection, or only via HTTPS.
  • delete( key ):
    Send a cookie delete header to the browser to delete the key cookie. The same may be achieved by: del cookie[key].
The example below shows to manage browser cookies.

examples/cookie.spy
[[.import name=cookie]]
<html><body>
  Managing cookies is simple. Use the following forms 
  to create and destroy cookies. Remember to refresh 
  once, because the cookie will only be transmitted on 
  the <i>following</i> request.<br>
  [[-- input forms --]]
  <hr>
  <form action="[[=request.uri('path')]]" method=post>
    <table><tr>
      <td align=right>Cookie name:</td>
      <td><input type=text name=name></td>
      <td>(required)</td>
    </tr><tr>
      <td align=right>value:</td>
      <td><input type=text name=value></td>
      <td>(required for set)</td>
    </tr><tr>
      <td align=right>expiration:</td>
      <td><input type=text name=exp> seconds.</td>
      <td>(optional)</td>
    </tr><tr>
      <td colspan=3>
        <input type=submit name=operation value=set>
        <input type=submit name=operation value=delete>
        <input type=submit name=operation value=refresh>
      </td>
    </tr></table>
  </form>
  <hr>
  [[-- show cookies --]]
  Cookies: [[=len(cookie.get().keys())]]<br>
  <table>
    <tr>
      <td><b>name</b></td>
      <td><b>value</b></td>
    </tr>
    [[for c in cookie.get().keys(): {]]
      <tr>
        <td>[[=c]]</td>
        <td>[[=cookie.get(c)]]</td>
      </tr>
    [[ } ]]
  </table>
  [[-- set cookies --]]
  [[\
    operation = request.post('operation')
    if operation:
      operation = operation[0]
      name = request.post('name')[0]
      value = request.post('value')[0]
      if operation == 'set' and name and value:
        cookie.set(name, value)
      if operation == 'delete' and name:
        cookie.delete(name)
  ]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.11. Session

Sessions allow information to be efficiently passed from one request to the next via some browser mechanism: get, post or cookie. The potentially large or sensitive information is stored at the server, and only a short identifier is sent to the client. Sessions are often used to create sequences of pages that represent an application flow. This module manages session state. All session state has an expiration time and is automatically garbage collected.

  • setHandler( type, [ params ] ):
    Selects the session handler. This method must be called before invoking other session functions. The type specifies the handler, and the param(s) is (are) passed to the initialiser of the chosen handler. The type parameter is a string of the format: "file:class", where file is the location where the session handler is defined and class is the name of the session handler. The file name component is optional, and is searched for using the standard module-finding rules. If only the class name is specified, then the default location is used: inside the session module itself, where the standard Spyce session handlers reside.

    The standard Spyce session handlers are listed below, along with the parameters they take. If you would like to implement your own, custom session handler, there are two ways to do so. First, you can have a look at modules/session.py and define your subclass of the sessionHandler class. One would do this when defining a general-purpose session handler, and if you do go to this trouble, please email it in as a contribution. Alternatively, you can simply use the session_user handler, also defined below, to your own install callback functions. The majority of users should be satisfied with the basic session handlers provided.

    • setHandler( 'session_dir', directory ):
      Uses inidividual files in the specified directory to store session information.
    • setHandler( 'session_gdbm', file ):
      Uses the gdbm library to create and manage the session information inside the specified file.
    • setHandler( 'session_bsddb', file ):
      Uses the BSD database library to create and manage the session information inside the specified file.
    • setHandler( 'session_user', getf, setf, delf, idsf, info ):
      Uses user-provided functions to create and manage session information. The parameters are as follows:
      • getf: A function that will be called to get session state, as follows: getf(info, id), where info is the parameter given to setHandler above, and id is the session identifier. This function should ensure that the session has not expired. If an expired session is found, it must be automatically deleted. Note that a delete may never be called on an object, so it is imperative for getf() to delete objects when expiration is detected. If the session has expired, or if the session does not exist, this function should return None, otherwise the session information.
      • setf: A function that will be called to set or create session state, as follows: setf(info, state, expire, serverID, id), where info is the parameter given to setHandler above, state is the actual session information to be preserved, expire is the number of seconds after which this information will be invalidated, serverID is a unique identifier for this server that can be used to avoid race conditions between two Spyce engines generating new session identifiers, and id is the optional session identifier. If an identifier is provided, that session should be updated, otherwise (namely, in the case when id is set to None), a new session identifier should be generated. This function returns the (new or old) session identifier.
      • delf: A function that will be called to set delete a session, as follows: delf(info, id), where info is the parameter given to the setHandler above and id is the session identifier of the session to be invalidated.
      • idsf: A function that will be called to get all the session identifiers, as follows: idsf(info), where info is the parameter given to the setHandler above. This function should return ALL session identifiers, even those that have expired and are to be deleted. Among other purposes, this function is used to automatically clean up session state periodically, by performing a getf() on all sessions. (Remember that according to the semantics defined for getf(), it will delete any expired sessions.)
      • info: At the very least, this is a key that uniquely identifies this session handler. The info variable may also contain any other additional information. It is passed back as-is to each of the session callback functions, as described previously.

  • get( id ):
    Returns the object stored under the given id. If the id does not exist, or was previously used but has expired, then None is returned. As with the cookie module, the session module may be treated as an associative array when retrieving session information.
  • set( data, expire, [id] ):
    Stores the data object under the given id. If id is omitted, then a unique one is generated. On success, an id is returned, otherwise an exception raised. The expire field specifies the number of seconds that the session information is valid for.
  • delete( id ):
    Deletes the session stored under the given id. Note that sessions are automatically deleted upon expiration, so this method need only be used when immediate invalidation is desired. As with the cookie module, the session module may be treated as an associative array when removing session information.
  • autoSession( expire, [method], [name] ):
    This function can remove most of the code associated with session management, by doing it automatically. Namely, it automatically retrieves the session information and resaves it at the end of the request, using the auto, autoID, autoName and autoMethod fields (explained below). The expire parameters acts as before, to specify how long the session information remains valid. The method and name parameters instruct the session module how to find the session identifier. Method can be one of 'get', 'post', or 'cookie', which is the default. The name parameter, under which the session id is stored, defaults to 'spyceSession'. If the lookup is unable to find a session id for this request a new session is created. At the end of the request, the session information is automatically saved, and a cookie automatically generated if the 'cookie' method was chosen. For the 'get' and 'post' methods the user is required to encode the autoID (session id) inside all form targets and urls that are generated.
  • auto:
    The field containing the actual session information, when automatic session management is used. Set it to whatever you like, as long as it as be serialized. Its initial value, for a new session, is None.
  • autoID:
    The session identifier, when automatiic session management is used.
  • autoName:
    The variable named used to identify the cookie or the parameter in the get or post requests containing the session identifier, when automatic session management is used.
  • autoMethod:
    The method used ('cookie', 'post' or 'get') to load and save the session identifier, when automatic session management is used.
The example below shows how a session can be used to count the number of times the same open browser visited our page. The session ID is stored in a cookie that expires when the browser is closed. Note that the session module automatically loads the cookie module if not already loaded and is needed.

examples/session.spy
[[.import name=cookie]]
[[.import name=session args="'session_dir', '/tmp'"]]
<html><body>
  [[-- retrieve session information --]]
  [[\
    sessionid = cookie.get('session')
    if sessionid:
      num = session.get(sessionid)
      if num==None:
        sessionid = None
        num = 0
      else: num = int(num)
    else: num = 0
  ]]
  [[-- output --]]
  Your session ID was: [[=sessionid]]<br>
  [[num = num + 1]]
  You have visited this page [[=num]] time(s).<br>
  [[-- save session information for next time --]]
  [[\
    sessionid = session.set( num, 10, sessionid )
    if sessionid: cookie.set('session', sessionid)
  ]]
  Your session ID is now: [[=sessionid]]<br>
  Session expiration = 10 seconds.<br>
  <b>Note:</b> This example requires write access to 
  the /tmp directory to function correctly.
</body></html>
Run this code.
(requires Spyce-enabled web server)

The next example highlights the convenience of using autoSession. By default, the session identifier is stored using a cookie named 'spyceSession'.

examples/autosession.spy
[[.import name=session args="'session_dir', '/tmp', auto=10"]]
<html><body>
  [[-- count visits --]]
  [[\ 
    if not session.auto: session.auto = 1
    else: session.auto = session.auto + 1
  ]]
  [[-- output --]]
  You have visited this page [[=session.auto]] time(s)<br>
  Your autosession ID is: [[=session.autoID]]<br>
  Autosession expiration = 10 seconds.<br>
  <b>Note:</b> This example requires write access to 
  the /tmp directory to function correctly.
</body></html>
Run this code.
(requires Spyce-enabled web server)

If cookies are not desired, the automatic session identifier session.autoID can also be transmitted via a browser post, as shown:

examples/autosessionpost.spy
[[.import name=session args="'session_dir','/tmp', auto=(10,'post','mysession')"]]
<html><body>
  [[-- count visits --]]
  [[\
    if not session.auto: session.auto = 1
    else: session.auto = session.auto + 1
  ]]
  [[-- output --]]
  You have visited this page [[=session.auto]] time(s)<br>
  Your autosession ID is: [[=session.autoID]]<br>
  Autosession expiration = 10 seconds.<br>
  <table><tr>
    <td>
      <form method=post>
        <input type=submit value=Refresh>
        <input type=hidden name=mysession value=[[=session.autoID]]>
      </form>
      </td><td>
      <form method=post>
        <input type=submit value=Clear>
      </form>
    </td>
  </tr></table><br>
  <b>Note:</b> This example requires write access to 
  the /tmp directory to function correctly.
</body></html>
Run this code.
(requires Spyce-enabled web server)

Finally, one can easily define some new session handling mechanism using callback functions, as this last example shows:

examples/mysession.spy
[[.import names="session,pool"]]
[[-- note: this eg will not work under CGI
     or when you have multiple servers --]]
[[\
  # storing session info as server pool variable,
  # so that example is portable! -- you'll 
  # want to have some persistent DB connection.
  if not pool.has_key('mysession'):
    pool['mysession'] = {}
    pool['mysessioncount'] = 1
  def myget(info, id):
    import time
    try:
      state, expiretime = pool['mysession'][id]
      if int(time.time()) > expiretime:
        del pool['mysession'][id]
        return None
      return state
    except KeyError:
      return None
  def myset(info, state, expire, serverID, id):
    import time
    if not id:
      # lock here if running threaded engine
      id = str(pool['mysessioncount'])
      pool['mysessioncount'] = pool['mysessioncount'] + 1
    pool['mysession'][id] = state, int(time.time())+expire
    return id
  def mydel(info, id):
    try: del pool['mysession'][id]
    except KeyError: pass
  def myids(info):
    return pool['mysession'].keys()
  session.setHandler('session_user', myget, myset, mydel, myids, 'mysession')
  session.autoSession(10)
]]
<html><body>
  [[-- count visits --]]
  [[\ 
    if not session.auto: session.auto = 1
    else: session.auto = session.auto + 1
  ]]
  [[-- output --]]
  You have visited this page [[=session.auto]] time(s)<br>
  Your autosession ID is: [[=session.autoID]]<br>
  Autosession expiration = 10 seconds.<br>
  <b>Note:</b> This example requires a persistent server (i.e. non-CGI)
  to function correctly.
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.12. Pool

The pool module provides support for server-pooled variables. That is support for variables whose lifetime begins when declared, and ends when explicitly deleted or when the server dies. These variables are often useful for storing persistent database connections and other information that may be expensive to compute at each request. Another interesting use of pool variables is to store file- or memory-based lock objects for concurrency control. A pooled variable can hold any Python value.

The pool module may be accessed as a regular dictionary, supporting the usual get, set, delete, has_key, keys, values and clear operations. Note that the pool is shared across all Spyce files. If file-specific variables are desired, simply include the filename in the pool variables name as a tuple [i.e. (filename, variable)], or in some other form.

The pool module also provides access to any server variables that are set in the Spyce engine configuration file. A hashtable of these variables is available as pool.server. The example below shows how the module is used:

examples/pool.spy
[[.import names="pool"]]
<html><body>
  The pool module supports long-lived server-pooled objects,<br>
  useful for database connections, and other variables<br>
  that are expensive to compute.<br>
  [[\
    if pool.has_key('foo'):
      print 'Pooled object foo EXISTS.'
    else:
      pool['foo'] = 1
      print 'Pooled object foo CREATED.'
  ]]
  <br>
  Value: [[=pool['foo'] ]] <p>
  The pool module also gives access to server variables set in
  the server configuration file: <br>
  [[=pool.server]]<br>
  <b>Note:</b> This example requires a long-lived server to
  function correctly, i.e. non-CGI environment.
</body></html>
Run this code.
(requires Spyce-enabled web server)

4.13. Template

In general, a template is useful for separating form from function. Or, in other words, one would like web page designers to play with one file, and programmers to play with another, so that they don't step on each other's toes. A templating engine then puts the two pieces (template and data) together to create the final output. The Spyce language internally provides Spyce lambdas, which can be very useful for templating purposes. This module provides hooks to various external templating engines.

Spyce interacts with the rather powerful Cheetah Python-based templating engine. The Cheetah engine is not included with the Spyce distribution, some recommended installation instructions are provided below. The Cheetah engine is invoked as follows:

  • cheetah( file, [lookup] ):
    Calling this function will invoke the Cheetah engine to compile (and cache) the template file provided. The engine then "runs" the template and fills in the appropriate data from the lookup dictionary, or list of dictionaries. If the lookup is omitted, the convenient default is to use the local and global variables from the current context. The template is filled and the resulting string is returned.

To install Cheetah (instructions correct as of version 0.9.15a1), follow the following steps:

  • Download the latest Cheetah engine from their website.
  • Extract the files from the gzipped tarball into some directory
  • Switch to root user
  • In that directory type: python setup.py install
  • Now, change directory to: /usr/lib/python2.2/site-packages/
  • Type in: chmod -R a+r Cheetah*
  • Type in: chmod a+x `find Cheetah -type d`

In general, that the Python path must simply include the Cheetah installation directory and Spyce will find it. If not, you will see an import error. At this time, the Cheetah engine requires Python version 2.0 or higher.

Support for other templating engines will be added as needed. An example of how templates are used is shown below, with the template files appended thereafter.

examples/template.spy
[[.import name=template]]
[[import sys]]
<html><body>
The template module interfaces with various templating
engines. <br>
It currently supports: 
  <a href="http://www.cheetahtemplate.org">Cheetah</a>
<hr>

[[
  persona = 'world'
  num = 10
]]
<b>Cheetah template:</b><br>
[[ try: { ]]
  [[=template.cheetah('template.tmpl')]]
[[ } except ImportError: { ]]
  Unable to import Cheetah.Compiler from path=[[=sys.path]]
  <br><b>The Cheetah is likely not (properly) installed.</b>
[[ } ]]
<p>

</body></html>
Run this code.
(requires Spyce-enabled web server)

examples/template.tmpl
Hello $persona!
#for i in range($num)
$i #slurp
#end for
Run this code.
(requires Spyce-enabled web server)

4.14. Compress

The compress module supports dynamic compression of Spyce output, and can save bandwidth in addition to static compaction. The different forms of compression supported are described below.

  • spaces( [ boolean ] ):
    Controls dynamic space compression. Dynamic space compression will eliminate consecutive whitespaces (spaces, newlines and tabs) in the output stream, each time it is flushed. The optional boolean parameter defaults to true.

  • gzip( [ level ] ):
    Applies gzip compression to the Spyce output stream, but only if the browser can support gzip content encoding. Note that this function will fail if the output stream has already been flushed, and should generally only be used with buffered output streams. The optional level parameter specifies the compression level, between 1 and 9 inclusive. A value of zero disables compression. If level is omitted, the default gzip compression level is used. This function will automatically check the request's Accept-Encoding header, and set the response's Content-Encoding header.

The example below shows the compression module in use.

examples/compress.spy
[[.import name=compress args="gzip=1, spaces=1"]]
[[\
  response.write('<html><body>')
  response.write('  Space compression will remove these     spaces.<br>')
  response.write('  gzip compression will highly compress this:<br>')
  for i in range(1000):
    response.write('  hello')
  response.write('</body></html>')
]]
Run this code.
(requires Spyce-enabled web server)

Note that the compression functions need not be called at the beginning of the input, but before the output stream is flushed. Also, to really see what is going on, you should telnet to your web server, and provide something like the following request.

GET /spyce/examples/compress.spy HTTP/1.1
Accept-Encoding: gzip

4.15. Automaton

The current release of the automaton module is preliminary and is still in flux. The automaton module provides support for state machine-based application design, which is often useful when designing websites with application flows. The state machine is a directed, labelled graph. It has states (nodes with names), and transitions (directed edges with names). One of the states is defined to be a begin state for the machine. Every state has a send function, a receive function and a set of outgoing edges.

The basic idea behind the operation of the automaton module is as follows: The application is at some state when a request comes in. The receive function for that state is invoked to process the input from the browser. Based on this input the receive function returns some edge label, which takes the application from the current state to its new state. The send function of this new state is invoked to emit the appropriate application page. The data that returns from this page will be processed by the corresponding receive function, and so on. All you need to remember between requests is which state the application is in, which can be done via get or post, or via cookies using the cookie module. Better yet (to keep application states private and on the server for security reasons), one can store the state label in the session using the session module.

A state machine can be defined programmatically using the following functions:

  • state( name, send, recv ):
    Add a new state labelled name with associated send and recv functions.
  • transition( state1, name, state2 ):
    Add a new edge labelled name from state1 to state2. There is always a self-referencing edge with the label None, but this can be overidden.
  • begin( state ):
    Define a given state to be the begin state.
  • define( sm, begin ):
    Define an entire automaton sm all at once, where sm is a hashtable. The keys are the states and the values are triplets with a send function, a receive function and an edge hashtable. The edge hashtable has names of the edges as keys and the target states as values. The begin state is given.

To step through the state machine transitions, you call:

  • step( [state] ):
    If state is specified, then call the receive function of that state. The receive function returns an edge label, which points to the new state. If no state is specified, just set the new state to the begin state of the automaton. Then, call the send function of the new state. Note that the send function is responsible for encoding its own state label, for use on the subsequent client request.

Future releases of this module may add support for different types of send and receive handlers. For example, it is probably useful to be able to internally redirect to various Spyce pages for send processing, rather than inline functions. It may also be possible to pass information among the different functions, which could be useful, for example, in handling error messages during form processing. It may also be useful to define a sequence of states, where previous and next are implicit edges.

The following examples, shows the above in action:

examples/automaton.spy
[[.import name=automaton]]
[[.import name=session args="'session_dir', '/tmp', auto=10"]]
[[\
if not session.auto: session.auto = {
  'name': '',
}

step1send = [[spy:
  <html><body>
    <form action=automaton.spy method=post>
      <table border=0>
        <tr><td colspan=2>Name: <input type=text size=20></td></tr>
        <tr>
          <input type=hidden name=state value=step1>
          <td></td>
          <td align=right><input type=submit name=dir value="next"></td>
        </tr>
      </table>
    </form>
  </body></html>]]
def step1recv():
  if request.post1('dir') == 'next': return 'next'

step2send = [[spy:
  <html><body>
    <form action=automaton.spy method=post>
      <table border=0>
        <tr><td colspan=2>Age: <input type=text size=20></td></tr>
        <tr>
          <input type=hidden name=state value=step2>
          <td align=left><input type=submit name=dir value="prev"></td>
          <td align=right><input type=submit name=dir value="next"></td>
        </tr>
      </table>
    </form>
  </body></html>]]
def step2recv():
  if request.post1('dir') == 'prev': return 'prev'
  if request.post1('dir') == 'next': return 'next'

step3send = [[spy:
  <html><body>
    <form action=automaton.spy method=post>
      step3
      <input type=hidden name=state value=step3>
      <input type=submit name=dir value=prev>
      <input type=submit name=dir value=next>
    </form>
  </body></html>]]
def step3recv():
  if request.post1('dir') == 'prev': return 'prev'
  if request.post1('dir') == 'next': return 'next'

step4send = [[spy:
  <html><body>
    Thanks.
  </body></html>]]
def step4recv():
  pass

automaton.define({
  'step1': ( step1send, step1recv, {
    'next': 'step2',
  }),
  'step2': ( step2send, step2recv, {
    'next': 'step3',
    'prev': 'step1',
  }),
  'step3': ( step3send, step3recv, {
    'next': 'step4',
    'prev': 'step2',
  }),
  'step4': ( step4send, step4recv, {
  }),
}, 'step1')

state = request.post1('state')
automaton.step(state)
]]

[[--
spyce file
spyce inline
function or method reference
inline code
--]]
Run this code.
(requires Spyce-enabled web server)

4.16. TOC

The TOC module provides support for constructing a table contents for a lengthy document, such as this user documentation. The primary task of the TOC module is to maintain a document tree, and initiate callbacks at the appropriate points in the document. Note that this module may automatically force a secondary processing of the Spyce file to resolve forward references.

The module provides the following methods to segment the document:

  • begin( data, [tag] ):
    Increase the nesting level and add a new section. The data is stored in the document tree, and used for callbacks (see later). An optional tag may be associated with the node, otherwise one will automatically be generated. The function b() is equivalent.
  • next( data, [tag] ):
    Add a new section at the same nesting level. The data is stored in the document tree, and used for callbacks (see later). An optional tag may be associated with the node, or one will be automatically generated. The function n() is equivalent.
  • end():
    Decrease the nesting level. The function e() is equivalent.
  • anchor( data, [tag] ):
    Set data and optionally the tag associated with the root of the document tree. If the tag is omitted, it defaults to the string 'root'.
  • level( depth, data, [tag] ):
    Start a new section at given depth with given data and optional tag. The necessary begin(), next() and end() calls are automatically made, based on the current document depth, so both types of calls can be inter-mixed.
  • l1( data, [tag] ):
    Start a level 1 section. This function merely calls level(1, datatag). The functions, l2()...l9() are similarly defined.

The following methods provide access to document information:

  • getTag():
    Return the tag of the current document section.
  • getNumbering( [tag] )
    Return the numbering of some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. The numbering is an array of numbers. This function may return 'None' on the first pass through a document.
  • getData( [tag] )
    Return the data associated with some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. This function may return 'None' on the first pass through a document.
  • getDepth( [tag] )
    Return the depth of some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. This function may return 'None' on the first pass through a document.
  • getNextTag( [tag] )
    Return the tag of the section following some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. If this is the last section of the document, then this function will return 'None'. This function may return 'None' on the first pass through a document.
  • getPrevTag( [tag] )
    Return the tag of the section before some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. If this is the first section of the document, then this function will return 'None'. This function may return 'None' on the first pass through a document.
  • getParentTag( [tag] )
    Return the tag of the section above (or containing) some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. If this is the top-most section of the document, then this function will return 'None'. This function may return 'None' on the first pass through a document.
  • getChildrenTags( [tag] )
    Return a list (possibly empty) of tags of the sections directly contained within some section of the document identified by the given tag. If the tag is omitted, the current document section is assumed. This function may return a shorter list than anticipated or 'None', on the first pass through a document.

The TOC modules can make callbacks to handlers that format the document correctly. The handlers should be defined and registered before the first section break in the document. The following functions register handlers:

  • setDOC_PUSH( f ):
    Register a function f to be called when the nesting depth of the document increases.
  • setDOC_POP( f ):
    Register a function f to be called when the nesting depth of the document decreases.
  • setDOC_START( f ):
    Register a funtion f to be called at the beginning of a section.
  • setDOC_END( f ):
    Register a function f to be called at the end of a section.
  • setTOC_PUSH( f ):
    Register a function f to be called when the nesting depth of the table of contents increases.
  • setTOC_POP( f ):
    Register a function f to be called when the nesting depth of the table of contents decreases.
  • setTOC_ENTRY( f ):
    Register a function f to be called for each table of contents entry.

Each callback function should be of the form:

f(depth, tag, numbering, data),
where: depth is the nesting depth, tag is the associated tag, numbering is the position array, and data is the associated data of the section for which the callback was made.

The DOC callbacks are made as the sections are encountered. The TOC callbacks are made while printing the table of contents. If the modules detects that forward references exist in the document, the document will be processed twice, and only the second output will be sent. Note that buffering MUST be turned on for this to function correctly.

To display a table of contents, define the appropriate TOC callback functions and call:

  • showTOC(): Display the table of contents.
For an example of how to use the TOC module, please refer to the source Spyce file of this documentation.

4.17. Writing Modules

Writing your own Spyce modules is simple. Let us begin with a basic example called myModule. It is a module that implements one function named foo().

examples/myModule.py
from spyceModule import spyceModule

class myModule(spyceModule):
  def foo(self):
    print 'foo called'

Saving this code in myModule.py in the same directory as the Spyce script, or somewhere on the module path, we could use it as expected:

[[.import name=myModule]]
[[ myModule.foo() ]]

A Spyce module can be any Python class that derives from spyceModule.spyceModule. Do not override the __init__(...) method because it is inherited from spyceModule and has an fixed signature that is expected by the Spyce engine's module loader. The inherited method accepts a Spyce API object, a Bastion of spyce.spyceWrapper, an internal engine object, and stores it in self._api. This is the building block for all the functionality that any module provides. The available API methods of the wrapper are (listed in spyceModule.spyceModuleAPI):

  • getFilename: Return filename of current Spyce
  • getCode: Return processed Spyce (i.e. Python) code
  • getCodeRefs: Return python-to-Spyce code line references
  • getModRefs: Return list of import references in Spyce code
  • getServerObject: Return unique (per engine instance) server object
  • getServerGlobals: Return server configuration globals
  • getServerID: Return unique server identifier
  • getModules: Return references to currently loaded modules
  • getModule: Get module reference. The module is dynamically loaded and initialised if it does not exist (ie. if it was not explicitly imported, but requested by another module during processing)
  • setModule: Add existing module (by reference) to Spyce namespace (used for includes)
  • getGlobals: Return the Spyce global namespace dictionary
  • registerModuleCallback: Register a callback for modules change
  • unregisterModuleCallback: Unregister a callback for modules change
  • getRequest: Return internal request object
  • getResponse: Return internal response object
  • setResponse: Set internal response object
  • registerResponseCallback: Register a callback for when internal response changes
  • unregisterResponseCallback: Unregister a callback for when internal response changes
  • spyceString: Return a spyceCode object of a string
  • spyceFile: Return a spyceCode object of a file
  • spyceModule: Return Spyce module class
  • spyceTaglib: Return Spyce taglib class
  • setStdout: Set the stdout stream (thread-safe)
  • getStdout: Get the stdout stream (thread-safe)

For convenience, one can sub-class the spyceModulePlus class instead of the regular spyceModule. The spyceModulePlus defines a self.modules field, which can be used to acquire references to other modules loaded into the Spyce environment. The response module, for instance, would be referenced as self.modules.response. Modules are loaded on demand, if necessary. The spyceModulePlus also contains a self.globals field, which is a reference to the Spyce global namespace dictionary, though this should rarely be needed.

Note: It is not expected that many module writers will need the entire API functionality. In fact, the vast majority of modules will use a small portion of the API, if at all. Many of these functions are included for just one of the standard Spyce modules that needs to perform some esoteric function.

Three Spyce module methods, start(), init([args]) and finish(error) are special in that they are automatically called by the runtime during Spyce request startup, processing and cleanup, respectively. The modules are started in the order in which module directives appear in the file, before processing begins. The implicitly loaded modules are always loaded first. The init method is called during Spyce processing at the location of the module directive in the file, with the optional args attribute is passed as the arguments of this call. Finally, after Spyce processing is complete, the modules are finalized in reverse order. If there is an unhandled exception, it will be wrapped in a spyce.spyceException object and passed as the first parameter to finish(). During successful completion of Spyce processing (i.e. without exception), the error parameter is None. The default inherited start, init and finish methods from spyceModule are noops.

Note 2: When writing a Spyce module, consider carefully why you are selecting a Spyce module over a regular Python module. If it is just code, that does not interact with the Spyce engine, then a regular Python import instead of an Spyce [[.import]] can just as easily bring in the necessary code, and is preferred. In other words, choose a Spyce module only when there is a need for per-request initialization or for one of the engine APIs.

Module writers are encouraged to look at the existing standard modules as examples and the definitions of the core Spyce objects in spyce.py as well. If you write or use a novel Spyce module that you think is of general use, please email your contribution, or a link to it. Also, please keep in mind that the standard modules are designed with the goal of being minimalist. Much functionality is readily available using the Python language libraries. If you think that they should be expanded, also please send a note.

5. TAGS

The previous chapter discussed the Spyce module facility, the standard Spyce modules and how users can create their own modules to extend Spyce. Spyce functionality can also be extended via active tags, which are defined in tag libraries. This chapter describes what Spyce active tags are, and how they are used. We then describe each of the standard active tag libraries and, finally, how to define new tags libraries.

It is important, from the outset, to define what an active tag actually does. A few illustrative examples may help. The examples below all use tags that are defined in the core tag library, that has been installed under the spy prefix, as follows:

  [[.taglib name=core as=spy ]]

  • <spy:print val="=2+2"/>
    Rather than emitting itself as plain text, this tag will output 4.
  • <spy:let var="foo" val="bar"/>
    This tag will assign the constant string value bar to a variable named foo in the tag context, which will then be available to other tags that follow later in the document.
  • <spy:for items="=range(5)">
      <spy:print value="=foo"/>
    </spy:for>

    As expected, these tags will print the value of foo, set to bar above, 5 times.
Note that the same output could have been achieved in many different ways, and entirely without active tags. The manner in which you choose to organize your script or application, and when you choose active tags over other alternatives, is a matter of personal preference. Notice also that active tags entirely control their output and what they do with their attributes and the result of processing their bodies (in fact, whether the body of the tag is even processed). Tags can even supply additional syntax constraints on their attributes that will be enforced at compile-time. Most commonly a tag could require that certain attributes exist, and possibly that it be used only as a single or only as a paired (open and close) tag. Unlike early versions of HTML, active tags must be strictly balanced, and this will be enforced by the Spyce compiler.

Below, each individual standard Spyce tag library is documented, followed by a description of how one would write a new active tag library. The following general information will be useful for reading that material.

  • Active tags are installed using the [[.taglib]] directive, under some prefix. Active tags are of the format <pre:name ... >, where pre is the prefix under which the tag library was installed, and name is defined by the tag library. In the following tag library documentation, the prefix is omitted from the syntax.
  • Tags store variables and evaluated expression within a separate tag context. This tag context dictionary is available via taglib.context (i.e. the context field of the taglib module). The tag context contains references to all the loaded modules. Thus, it is valid to refer to something like request.query() in a tag expression. However, it is not valid to change any module variable references. While this will not cause any harm, the user should expect that these new values can be reset at any time.
  • The following notation is used in the documentation of the tag libraries below:
    • <name .../> : The tag should be used as a singleton.
    • <name ... > ... </name> : The tag should be used as an open-close pair.
    • [ x ] : The attribute is optional. Attributes not enclosed in brackets are required.
    • foo|bar : indicates that an attribute may be one of two constant strings. The underlined value is the default.
    • string : an arbitrary string constant
    • expression : may be a string constant, and may be of the form '=expr', where expr is a Python expression that will be evaluated in the tag context.

5.1. Core

The core active tag library is modelled after some of the functionality that exists in Java's JSTL. It is still in the preliminary design stages, and more tags are expected. Currently, it provides the following active tags:

  • <print val=expression [encode=false|html|url] [default=expression] />
    Outputs the value of the val expression. If there is an error and a default is provided, the default will be evaluated instead. The output may be encoded to be HTML- or URL-safe, depending on the encode attribute.
  • <let var=string val=expression />
    Sets the variable var to the value val in the tag context.
  • <let var=string val=expression> ... </let>
    Same as above, except that the scope of the variable is that of the tag body, and the value of the variable, if it existed prior to the start tag, is restored after the end tag.
  • <unlet var=string />
    Unset (i.e. delete) the variable var in the tag context.
  • <if test=expression> ... </if>
    Evaluate test and conditionally process body of tag.
  • <for items=expression [var=string] [counter=string]> ... </for>
    Iterate through a tuple or list of items and process the body each time. The current item can optionally be stored in variable named by var, and the current iteration number (starting at zero) can optionally be stored in a variable named by counter.

5.2. Writing Tag Libraries

Creating your own active tags is quite easy, and this section explains how. You may want to create your own active tags for a number of reasons. More advanced uses of tags include database querying and separation of business logic. On the other hand, you might consider creating simpler task-specific tag libraries. For example, if you do not wish to rely on style-sheets you could easily define your own custom tags to perform the formatting in a consistent manner at the server. These are only a few of the uses for tags.

Request to users: If you like the idea of active tags, please help. More tag libraries need to be written! For ideas, please start at the library listing at Sun's JSP site. As you will see, writing a Spyce active tag is far simpler than writing a JSP tag.

We begin with a basic example:

examples/myTaglib.py
from spyceTag import spyceTagLibrary, spyceTagPlus

class tag_foo(spyceTagPlus):
  name = 'foo'
  def syntax(self):
    self.syntaxPairOnly()
    self.syntaxExist('val')
    self.syntaxNonEmpty('val')
  def begin(self, val):
    val = self.contextEval(val)
    self.getOut().write('<font size="%s"><b>' % str(val))
    return 1
  def end(self):
    self.getOut().write('</b></font><br>')

class myTaglib(spyceTagLibrary):
  tags = [
    tag_foo, 
  ]

Saving this code in myTaglib.py, in the same directory as your script or anywhere else in the search path, one could then use the foo active tag (defined above), as follows:

examples/tag.spy
[[.taglib name=core as=spy]]
[[.taglib name=myTaglib as=me]]
<html><body>
<spy:for var=x items="=range(2,6)">
  <me:foo val="=x">size <spy:print val="=x" /></me:foo>
</spy:for>
</body></html>
Run this code.
(requires Spyce-enabled web server)

An active tag library can be any Python class that derives from spyceTag.spyceTagLibrary. The interesting aspects of this class definition to implementors are:

  • tags:
    This field is usually all that requires redefinition. It should be a list of the classes (as opposed to instances) of the active tags.
  • start():
    This methd is invoked by the engine upon loading the library. The inherited method is a noop.
  • finish():
    This method is invoked by the engine upon unloading the library after a request. The inherited method is a noop.
Each active tag can be any Python class that derives from spyceTag.spyceTag. The interesting aspects of the class definition for tag implementors are:

  • name:
    This field MUST be overidden to indicate the name of the tag that this class defines.
  • buffer:
    This flag indicates whether the processing of the body of the tag should be performed in its own output buffer, or unbuffered. Buffering is necessary when a tag wants to transform, or otherwise use, the output of processing its body. The inherited default is false.
  • syntax():
    This method is invoked at compile time to perform any additional tag-specific syntax checks. The inherited method returns None, which means that there are no syntax errors. If a syntax error is detected, this function should return a string with a helpful message about the problem. Alternatively, one could raise an spyceTagSyntaxException.
  • begin( ... ):
    This method is invoked when the corresponding start tag is encountered in the document. All the attributes of the tag are passed in by name. In the case of a paired tag, this method is expected to return a boolean flag. A true return value indicates that the body of the tag should be processed. Otherwise, it is skipped. The inherited method simply returns true.
  • body( contents ):
    This method is invoked when the body of the tag has completed processing. It will, of course, not be called if there is no tag body, as is the case with singleton tags. It will also not be called if the begin() method has chosen to skip body processing. If the tag uses a buffer for capturing processing output (see above), then the string output of the body processing has been captured and stored in contents. It is the responsibility of this method to emit something, if necessary. If the tag does not buffer, then contents will be None, and the output has already been written to the enclosing scope. This method is expected to return a boolean flag. If the flag is true, then the body will be processed again, followed by another invocation of this method. The inherited method returns false.
  • end():
    This method is invoked when the corresponding end tag is encountered, if it exists. The runtime engine semantics ensure that if the begin method terminates successfully, this method will get called for the closing of a paired tag. The inherited method is a noop.
  • catch( ex ):
    If any exception occurs in the begin(), body() or end() methods or from the body processing, this method will be called. The parameter ex holds the value that was thrown. The inherited method will re-raise the exception.
  • getPrefix():
    Return the prefix under which this tag library was installed.
  • getAttributes():
    Return a dictionary of tag attributes.
  • getPaired():
    Return true if this is a paired (open and close) tag, or false if it is a singleton.
  • getParent():
    Return the object of the direct parent tag, or None if this is the root active tag. Plain (inactive) tags do not have associated objects in this hierarchy.
  • getOut():
    Return the (possibly buffered) output stream that this tag should write to.
  • getContext():
    Return the tag context dictionary, where all tags variables are kept and expression are evaluated. The context contains references to each of the loaded Spyce modules. These variables may used to access module functionality, but they should not be deleted or modified.
  • getBuffered():
    Returns true if the tag output stream is a local buffer, or false if the output is connected to the enlosing scope.
For convenience, tag implementors may wish to derive their implementations from spyceTagPlus, which provides some useful additional methods:
  • contextSet( name, (exists,value) ):
    Accepts a variable name and a tuple containing an exists flag and a value. If the flag is true, then the variable is assigned the value within the tag context. If the flag is false, the variable is deleted from the context dictionary. This function returns the previous state of this variable, as per contextGet().
  • contextGet( name ):
    Returns the current state of the variable name in the tag context. The state is a tuple containing a flag whether the variable is defined and its value.
  • contextEval( expr ):
    Evaluates a string expr as follows. If the string begins with an '=', then the rest of the string is treated as a Python expression. This is expression is evaluated within the tag context dictionary, and the result is returned. Otherwise, the parameter is treated as a string constant and returned as-is.
  • contextGetModule( name ):
    Return a reference to a module from the tag context. The module is loaded, if necessary.
  • syntaxExist( [must]* ):
    Ensure that the list of attributes given in must are all defined in the attributes of this tag. Otherwise, a spyceTagSyntaxException is thrown.
  • syntaxExistOr( [mustgroups]* ):
    Ensure that at least one of the lists of attributes specified in mustgroups satisfies syntaxExist(). Otherwise, a spyceTagSyntaxException is thrown.
  • syntaxExistOrEx( [mustgroups]* ):
    Ensure that exactly one of the lists of attributes specified in mustgroups satisfies syntaxExist(). Otherwise, a spyceTagSyntaxException is thrown.
  • syntaxNonEmpty( [names]* ):
    Ensure that if the attributes listed in names exist, then each of them does not contain an empty string value. Otherwise, a spyceTagSyntaxException is thrown. Note that the actual existence of a tag is checked by syntaxExists(), and that this method only checks that a tag is non-empty. Specifically, there is no exception raised from this method, if the attribute does not exist.
  • syntaxValidSet( name, validSet ):
    Ensure that the value of the attribute name, if it exists, is one of the values in the set validSet. Otherwise, a spyceTagSyntaxException is raised.
  • syntaxPairOnly():
    Ensure that this tag is a paired tag. Otherwise, a spyceTagSyntaxException is thrown.
  • syntaxSingleOnly():
    Ensure that this tag is a singleton tag. Otherwise, a spyceTagSyntaxException is thrown.
Despite the length of this description, most tags are trivial to write, as shown in the initial example. The easiest way to start is by having at a look at various implemented tag libraries, such as tags/core.py. The more curious reader is welcome to look at the tag library internals in spyceTag.py and modules/taglib.py. The tag semantics are ensured by the Spyce compiler (see spyceCompile.py), though it is likely easier simply to look at the generated Python code using the "spyce -c" command-line facility.

6. INSTALLATION

Spyce can be installed and used in many configurations. Hopefully, one of the ones below will suit your needs. If not, feel free to email the lists asking for assistance in using a different setup. If you have successfully set up Spyce by some other method, please email me the details so that I can post them for others. Lastly, if you had troubles following these instructions, please send an email with suggestions on how to improve them.

6.1. Overview

Spyce supports a variety of installation methods (automated versus manual), webserver adapters (FastCGI, mod_python, proxy, CGI and command-line) and operating system environments (Linux and Windows), which require separate discussion and configuration-specific tweaks. These specifics are kept to an absolute minimum, however, and, wherever possible, the configuration of the Spyce engine is performed through a common configuration file.

The supported adapters are:

  • Fast CGI:The default Spyce integration with Apache is acheived via FastCGI, a CGI-like interface that is relatively fast, because it does not incur the large process startup overhead on each request.
  • mod_python: If you really must have the fastest Spyce implementation (see the performance numbers), it is currently through an Apache module called mod_python. Spyce has been tested with mod_python version 2.7.6. You can try to find some mod_python rpms here, but in general one must compile mod_python from sources. The reason for this is because mod_python links with the Python library it finds on your system at compile time. Thus, even if you have the correct Python version installed on your system, mod_python will be using the Python library version on the system where it was compiled. Also, note that mod_python (or rather Apache) needs a Python that has been compiled without threading, so you may need to recompile Python as well for this reason. The process is not very difficult (just the usual: ./configure; make; make install dance), but hopefully someone will suggest a better route in time. In any case, make sure you can first get mod_python running on your system, if that is that is your chosen Apache integration route.
  • Web server: Another fast alternative is to serve Spyce files via a proxy. This involves running Spyce in web-server mode, and configuring the main web server to forward the appropriate requests. The built-in Spyce web server can also be used to serve requests directly, but this is highly discouraged for production environments.
  • CGI: Failing these alternatives you can always process requests via regular CGI, but this alternative is the slowest option and is intended primarily for those who do not have much control over their web environments.
  • Command line: Lastly, one can use Spyce as a command-line tool for pre-processing Spyce pages and creating static HTML files.
6.2. Requirements

Spyce (the core engine and all the standard modules) currently requires Python version 1.5 or greater, and Apache version 1.3.x or greater.

However, Spyce is developed and tested mostly with Python version 2.2.x, and using Apache version 2.0.x.

Spyce currently does not require thread support. Spyce uses no version-specific Apache features. It is highly recommended that you use Python version 2.1 or higher, because certain common operations can be difficult without the new nested scoping functionality.

If you find a runtime bug, please let me know.

6.3. Installation Methods

Spyce can be installed in a variety of ways:

6.3.1. Automated installation - RPM

The easiest way to install Spyce on Linux is via RPM. Download the file from the link above and, as root user on your system, type in the following command: (If you upgrading Spyce, it is recommended to uninstall the previous version using rpm -e spyce, and then install a fresh copy, as opposed to using the rpm -U option.)

  rpm -i spyce-1.3.10-1.rpm

This will:

  • Install the Spyce engine
  • Install the web-based and command-line Spyce processors
  • Configure Apache to process .spy files via fast cgi, if installed or regular cgi otherwise
  • Restart the server
  • Install the Spyce documentation under http://localhost/spyce
Note that the default Apache installation does not come with FastCGI configured. Thus, you will be running Spyce via CGI, which is slow. If you install FastCGI, you will get a considerable performance boost. Alternatively, you could configure the mod_python adapter, or use the proxy server, for a similar performance boost.

Note to Redhat users: Spyce should install without a hitch on RedHat 8.0 machines and up. Redhat (up until version 8.0) still used Python version 1.5, as many standard scripts depended on it. If you want to run Spyce with some of the newer Python2 rpms, you will need to change top line of the run_spyceCmd.py, run_spyceCGI.py, run_spyceModpy.py and verchk.py scripts, or reconfigure your path so that the default python is the version that you want.

Note to Mandrake users: Users have reported this distribution to have no problems with the RPM.

6.3.2. Automated installation - Windows executable

On Windows platforms, the easiest method to install Spyce is via the executable installer. Python is required on the system prior to installation. It is also recommended to install the ActivePython distribution, as Spyce will automatically detect and use various Windows-specific features. It is advised to install Apache before Spyce, so that the Spyce installer can automatically modify the configuration file. You will need to install FastCGI or mod_python separately. Otherwise, Spyce will function via CGI.

The Windows installer installs the Spyce engine in a user-specified directory, copies and compiles the Spyce documentation, configures Apache, registers an uninstaller and also registers the .spy file types with the shell for easy compilation. Don't forget to check the Apache configuration file, and restart Apache. If you have any problems with the installer please send an email.

6.3.3. Manual installation

One could also install Spyce manually from the source tarball (using FastCGI), as follows:

  • Ensure that you have Apache and FastCGI installed and functioning.
  • Extract the source tarball into some directory.
  • Execute make in this directory to compile the python modules and build the documentation.
  • (Optional) As root user, execute "make install", to install spyce into /usr/share/spyce.
  • Create a link to the command-line executable:
    "ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce"
    or wherever you have chosen to install it.
  • Create a link to the documentation in /usr/share/doc:
    "ln -sf /usr/share/spyce/docs/ /usr/share/doc/spyce"
    or wherever you have chosen to install it.
  • Add the following lines to your /etc/httpd/conf/httpd.conf file, and replace the XXX with the appropriate path.
    # XXX = Spyce program directory
    
    # This section asks your web server to serve the 
    # Spyce documentation from http://localhost/spyce/.
    
    Alias /spyce/ "XXX/docs/"
    <Directory "XXX/docs">
        Options Indexes
        AllowOverride None
        Order allow,deny
        Allow from all
    </Directory>
    
    ###################
    # Spyce via cgi or fcgi
    
    # This section is the default. It provides a default
    # mechanism to process .spy files. On a vanilla Apache
    # installation this will be done via CGI, which is 
    # quite slow. If the FastCGI module is properly 
    # installed, should automatically be used instead.
    
    AddHandler spyce-cgi-handler spy
    Action spyce-cgi-handler "/spyce-cgi/run_spyceCGI.py"
    ScriptAlias /spyce-cgi/ "XXX/"
    <Location /spyce-cgi/>
      <IfModule mod_fastcgi.c>
        # If mod_fastcgi not installed, we get plain cgi
        SetHandler fastcgi-script
      </IfModule>
    </Location>
    # If FastCGI is installed, it will be picked up 
    # automatically. On Linux, you can also omit this section 
    # and use a dynamic fcgi server instead.
    <IfModule mod_fastcgi.c>
      FastCgiServer "XXX/run_spyceCGI.py" -port 7654 -initial-env FCGI_PORT=7654
    </IfModule>
    # On Windows ONLY, please uncomment the following line.
    # ScriptInterpreterSource registry
    
    ###################
    # Spyce via mod_python
    
    # This section allows Spyce to be invoked via the mod_python,
    # yet another alternative with decent performance. Comment
    # the CGI/FastCGI section above entirely, and uncomment the
    # following lines, if you choose to use this instead.
    # (Note that the doubly commented lines, can remain commented
    # depending on your configuration).
    
    #<IfModule mod_python.c>
    #  AddHandler python-program .spy
    #  PythonHandler run_spyceModpy::spyceMain
    #  PythonPath "sys.path+[r'XXX']"
    #  #PythonOption SPYCE_CONFIG "/mydir/spyce.conf"
    #  # !!! Do NOT turn on. !!!
    #  PythonOptimize Off
    #</IfModule>
    
    ###################
    # Spyce via proxy (on port 8000)
    
    # This section direct Apache to process Spyce requests via
    # a Spyce proxy server. Comment the CGI/FastCGI section above,
    # and uncomment the following lines.
    # NB: Remember to start the Spyce proxy server...
    #   spyce -l -p 8000 /document_root
    # If you would like to run your server on another port,
    # start the proxy on that port (using the -p switch)
    # and change the RewriteRule below accordingly.
    
    #<IfModule mod_rewrite.c>
    #  RewriteEngine On
    #  RewriteRule ^(.*\.spy) http://localhost:8000$1 [p]
    #</IfModule>
    
    
  • If you are installing on Windows, please also read follow the following instructions.
  • Restart Apache, and you should be set.
Test the installation by browsing:
  http://localhost/spyce/examples/hello.spy

Alternative CGI configuration: The alternative CGI configuration directs the webserver to execute the Spyce file itself, not the Spyce engine. The Spyce file should have execute permissions for the web server, and the first line should be:

  #! /usr/bin/python /home/username/spyce/run_spyceCGI.py
Then add the following line to the httpd.conf, or to the .htaccess file in the same directory.
  AddHandler cgi-script spy
And ensure that the directory itself has the ExecCGI option enabled.

Please refer to the Apache documentation, specifically ExecCGI option, Directory, Location, AllowOverride and Apache CGI documentation, for more information on how to get a standard CGI setup working.

6.4. Configuration Specifics

While the default configuration above should suffice many users, it is often the case that you would like to use Spyce under a different adapter or in a different way. The following notes should be helpful, but should not be considered complete. Many other configurations are possible, and if you come across one that you find useful, please let me know.

6.4.1. Mod_Python

The mod_python webserver integration route is the fastest currently available. Try this option if you really need more speed, or if you simply can not get FastCGI to work. Before you try to install Spyce, first get mod_python to work! You may have to compile from sources, and possibly do the same for Python. (See the documentation at: mod_python.) You might be able to find some useful python rpms here and mod_python rpms here. Windows versions of mod_python are available as well.

To use Spyce via mod_python:

  • Install Spyce.
  • Then, edit the httpd.conf to comment the cgi parts of the inserted Spyce configuration lines and uncomment the mod_python parts.
  • Restart Apache
6.4.2. Web Server

Yet another alternative for running Spyce, it to run it as a web-server either serving the web as a proxy server or as a primary. The Spyce web server is exceptionally feature poor, thus using it as a proxy behind a real web server is advised. Moreover, using it as the primary web server in a production is highly discouraged. The Spyce web server configuration is defined in a section of the runtime configuration file. To run it as a proxy server behind Apache, following these instructions:

  • Install Spyce.
  • Then, uncomment the lines in the httpd.conf to comment the cgi parts and uncomment the proxy parts.
  • Start the Spyce web server. The command-line syntax for starting the server is:
    spyce -l [-p port] [<root>]
  • Restart Apache
6.4.3. Windows

If you are installing Spyce manually on Windows, remember to add the following line to Apache's httpd.conf:

  ScriptInterpreterSource registry

This assumes that Python has registered itself with the Windows registry to run .py files. Otherwise, you can also omit this line, but make sure that the first line of the run_spyceCGI.py file points to a valid Python executable, as in:

  #! c:/progs/python/python.exe

If you are running using IIS on Windows, you can take a look at how to configure IIS or PWS for Python/CGI scripts.

The basics for getting IIS to work with Spyce are:

  • Start the IIS administration console. You get to it from the Control Panel. Click on Administrative Tools, and then Internet Services Manager.
  • Drill down to your Default Web Site. Right click and select Properties.
  • Select the Home Directory tab, and click on the Configuration... button near the bottom right.
  • Add an application mapping. On the executable line you should type the equivalent of:
    "c:\program files\python22\python.exe" "c:\program files\spyce\spyceCGI.py".
    Set the extension to .spy, or whatever you like.
    Limit the Verbs to GET,POST.
    Select the Script engine and Check that file exists check-boxes.
  • Click OK twice. Make sure to propagate these properties to all sub-nodes. That is, click Select All and then OK once more.
  • You should now be able to browse .spy files within your website. Note, that this is a very slow mechanism, since it utilizes CGI and restarts the Spyce engine on each request.
  • Using the Spyce proxy web server or installing FastCGI are much advised for the vast majority of environments.

7. ADDENDA

List of appendices:

7.1. Performance

Although flexibility usually outweighs raw performance in the choice of technology, it is nice to know that the technology that you have chosen is not a resource hog, and can scale to large production sites. The current Spyce implementation is comparable to its cousin technologies: JSP, PHP and ASP. We ran a micro-benchmark using hello.spy and equivalents. All benchmark files are available in the misc/benchmark directory.

examples/hello.spy
<html><body>
  Hello [[print 'world!',]]
  [[ for i in range(10): { ]]
    [[=i]]
  [[ } ]]
</body></html>
Run this code.
(requires Spyce-enabled web server)

Spyce was measured under CGI, FCGI, mod_python and proxy configurations. For calibration the static HTML, CGI-C, CGI-Python and FCGI-Python tests were performed. In the case of CGI-C, the request is handled by a compiled C program with the appropriate printf statements. In the case of CGI-Python, we have an executable Python script with the appropriate print statements. FCGI-Python is a similar script that is FCGI enabled. ASP was measured on a different machine, only to satisfy curiosity; those results are omitted.

Configuration Hello world
Spyce-modpython 250
Spyce-proxy 180
Spyce-FCGI 100
Spyce-CGI 5
JSP 100
PHP 450
Python-FCGI 140
C-CGI 180
Python-CGI 25
Static HTML 1500

The throughput results (shown above in requests per second) were measured on a Intel PIII 700MHz, with 128 MB of RAM and a 512 KB cache running RedHat Linux 7.2 (2.4.7-10 kernel), Apache 1.3.22 and Python 2.2 using loopback (http://localhost/...) requests. Since each of the script languages requires an initial compilation phase (of which JSP seems the longest), the server was warmed up with 100 requests before executing 1000 measured requests with a concurrency level of 3, using the ab (Apache benchmark) tool. Figures are rounded to the nearest 25 requests/second.

Conclusion: Both the mod_python and FCGI version can handle large websites, as the Spyce engine and cache persist between requests. The CGI version takes a hit in recompiling Spyce files on every request. This may be alleviated using a disk-based Spyce cache (as opposed to the current memory-based implementation).

7.2. History

The initial idea for a Python-based HTML scripting language came in May 1999, a few months after I had first learned of Python, while working with JSP on some website. The idea was pretty basic and I felt that someone was bound to implement it sooner or later, so I waited. But, nobody stepped up to the task, and the idea remained little more than a design in my head for two and a half years. In early 2002, after having successfully used Python extensively for various other tasks and gaining experience with the language, I began to revisit my thoughts on a Python-based HTML scripting language, and by late May 2002 the beta of version 1.0 was released.

Version 1.0 had support for standard features like get and post, cookies, session management, etc. Development was still on-going, but Spyce was mature and being used on live systems. Support of various features was enhanced for about a week or two, and then a new design idea popped into my head.

Version 1.1 was the first modular release of Spyce. Lots of prior functionality was shipped out of the core engine and into standard modules. Many, many new modules and features were added. Spyce popularity rose to the top percentile of SourceForge projects and the user base grew.

Version 1.2 represented a greatly matured release of Spyce. Spyce got a totally revamped website and documentation, and development continued...

For more detail, please refer to the change log. As always, user feedback is welcome and appreciated.

7.3. Related Work

Links to websites and projects that are related in some way to Spyce (listed alphabetically).


© 2002 Rimon Barr
email: rimon AT acm DOT org
Spyce Powered SourceForge Logo [[ Spyce ]]
Python Server Pages
version 1.3.10