spyce
         
home     documentation     download     Spyce logo


Documentation

Spyce - Python Server Pages (PSP)
User Documentation

Release 2.1

[ Multi-Page Format ]

TABLE OF CONTENTS


1. INTRODUCTION

This document aims to be the authoritative source of information about Spyce, usable as a comprehensive refence, a user guide and a tutorial. It should be at least skimmed from beginning to end, so you have at least an idea of the functionality available and can refer back for more details as needed.

Spyce is a server-side language that supports elegant and efficient Python-based dynamic HTML generation. Spyce allows embedding Python in pages similar to how JSP embeds Java, but Spyce is far more than a JSP clone. Out of the box, Spyce provides development as rapid as other modern frameworks like Rails, but with an cohesive design rather than a morass of special cases.

Spyce's modular design makes it very flexible and extensible. It can also be used as a command-line utility for static text pre-processing or as a web-server proxy.

Spyce's performance is comparable to the other solutions in its class.

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

1.1. Rationale / competitive analysis

This section is somewhat dated. We plan to update it soon.

A natural question to ask is why one would choose Spyce over JSP, ASP, PHP, or any of the other HTML scripting languages that perform a similar function. We compare Spyce with an array of exising tools:

  • 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.
    • Python is a high-level scripting language, where rapid prototyping is syntactically easier to perform.
    • There is no need for a separate "expression langauge" in Spyce; Python is well-suited for both larger modules and active tag scripting.
    • Python is interpreted and latently typed, which can be advantageous for prototyping, especially in avoiding unnecessary binary incompatibility of classes for minor changes.
    • Spyce code is of first-order in the Spyce language, unlike JSP, which allows you to create useful Spyce lambda functions.
    • Creating new active tags and modules is simpler in Spyce than in JSP.
    • Spyce is better-integrated than JSP; to get similar functionality in JSP, you have to add JSF (Java Server Faces) and Tiles, or equivalents.

  • 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 leverages 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.
    • 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.
    • Spyce is modular in its design, allowing users to easily extend its base functinality with add-on modules.
    • The Spyce engine can be run from the command-line, which allows Spyce to be used as an HTML preprocessor.
    Spyce, like PHP, can run entirely within the process space of a webserver or via CGI (as well as other web server adapters), and has been benchmarked to be competitive in performance.
  • ASP.NET is a Microsoft technology popular with Microsoft Internet Information Server (IIS) users. Visual Basic .NET and C# are both popular implementation languages.
    • Spyce provides the power of the ASP.NET "component" development style without trying to pretend that web applications live in a stateful, event-driven environment. This is a leaky abstraction that causes ASP.NET to have a steep learning curve while the user learns where the rough edges are.
    • ASP.NET is not well-supported outside the IIS environment. Spyce can currently run as a standalone or proxy server, under mod_python (Apache), or under CGI and FastCGI, which are supported in the majority of web server environments. Adapters have also been written for Xitami, Coil, Cheetah -- other web servers and frameworks.
    • Spyce is open-source, and free.
  • WebWare with Python Server Pages, PSP, is another Python-based open-source development. PSP is similar in design to the Spyce language, and shares many of the same benefits. Some important differences include
    • Spyce supports both Python chunks (indented Python) as well as PSP-style statements (braced Python).
    • Spyce supports active tags and component-based development
    • Spyce code is first-order in the Spyce language
    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 (in the author's humble opinion), 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." Zope is the most mature Python web application development environment, but to a large degree suffers from second-system syndrome. In the author's opinion, Zope is to a large degree responsible for the large number of python web environments: a few years ago, it was de rigeur for talented programmers to try Zope, realize it was a mess, and go off to write their own framework.

    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 an HTML-embedded language, not an application server.

Spyce strikes a unique balance between power and simplicity. 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.

1.2. Design Goals

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

  • Minimalist: 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.
  • Powerful: Spyce aims to free the programmer from as much "plumbing"-style drudgery as possible through features such as Active Handlers and reusable Active Tags.
  • Modular: Spyce is built to be extended with Spyce modules and Active Tags 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 or a new tag library. Standard Spyce modules and tag libraries 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. Part of this is avoiding special cases.
  • 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). (However, ASP/JSP-style delimeters are also supported, so if you're used to that style and like it, feel free to continue using it with Spyce.) Functions and modules are also designed with as many defaults as possible. There are no XML configuration files in Spyce.
  • 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.

Now, let's start using Spyce...

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:

  • <taglib:name attr1=val1 ...>
    Active tags may include presentation and action code.
  • [[-- Spyce comment      --]]
    Enclosed code is elided from the compiled Spyce class.
  • [[\  Python chunk         ]]
    Embed python in your code.
  • [[!  Python class chunk  ]]
    Like chunks, but at the class level rather than the spyceProcess method.
  • [[   Python statement(s)  ]]
    Like chunks, but may include braces to indicate that the block should continue after the ]].
  • [[=  Python expression    ]]
    Output the result of evaluating the given expression.
  • [[.  Spyce directive      ]]
    Pass options to the Spyce compiler.
  • [[spy  lambda             ]]
    Allows dynamic compilation of Spyce code to a python function.
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 within 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.

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 compiler, some whitespace may be eliminated. The Spyce transform module, for example, may further pre-processes this string, by inserting transformations into the output pipe. This is useful, for example, for dynamic compression of the script result.

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. They are similar, in spirit and functionality, to JSP tags. 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 compiler. There is a limited set of directives, listed and explained below:

  • [[.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 specifies the name of the Python class to load if using a 1.x-style taglib; otherwise it is ignored. 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.

    Also note that the configuration parameter globaltags allows you to set up tag libraries globally, freeing you from having to specify the taglib directive on each page that uses a tag. By default, globaltags installs core under the spy: prefix, and form under the f: prefix. (Tag libraries specified in globaltags are only loaded if the Spyce compiler determines they are actually used on the page, so there is no performance difference between globaltags and manually setting up taglib for each page.)

    There are some additional directives that are only legal when defining an active tag library.

It is important to note that Spyce directives are processed at compile time, not during the execution of the script, much like directives in C, and other languages. In other words, they are processed as the Python code for the Spyce script is being produced, not as it is being executed. Consequently, it is not possible to include runtime values as parameters to the various directives.

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. Feel free to use Spyce statements or chunks inter-changeably, as the need arises.

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, nor expected for anything but Python dictionaries. 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 Class Chunks

Syntax: [[! Python class chunk ]]

Behind the scenes, your Spyce files are compiled into a class called spyceImpl. Your Spyce script runs in a method of this class called spyceProcess. Class chunks allow you to specify code to be placed inside the class, but outside the main method, analogously to the "<%!" token in JSP code. (If you would like to see your Spyce file in compiled Python form, use the following command-line: spyce.py -c myfile.spy.)

This is primarily useful when defining active handlers without using a separate .py file: active handlers are the first thing that the spyceProcess calls, even before any "python chunks." For a handler callback to be visible at this stage, it needs to be defined at the class level. Class chunks to the rescue:

examples/handlerintro.spy
[[!
def calculate(self, api, x, y):
    self.result = x * y
]]

<spy:parent title="Active Handler example" />
<f:form>
    <f:text name="x:float" default="2" label="first value" />
    <f:text name="y:float" default="3" label="second value" />

    <f:submit handler="self.calculate" value="Multiply" />
</f:form>

<p>
Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
</p>
Run this code

2.7. 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 Python object None is special cased to output as the empty string just as it is in the Python interactive shell. This is almost always more convenient when working with HTML. (If you really want a literal string 'None' emitted instead, use response.write in a statement or chunk.)

The Spyce transform module, can pre-processes this result, to assist with mundane tasks such as ensuring that the string is properly HTML-encoded, or formatted.

2.8. 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 of the spylambda module. At runtime, this call compiles the Spyce code at the point of its 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

2.9. ASP/JSP syntax

Finally, due to popular demand, because of current editor support and people who actually enjoy pains in their wrists, the Spyce engine will respect ASP/JSP-like delimeters. In other words, it will also recognize the following syntax:

The two sets of delimeters may be used interchangeably within the same file, though for the sake of consistency this is not recommended.

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. Internally means that we do not send back a redirect to the browser, but merely clear the output buffer and start processing a new script.
  • All other exceptions that occur at runtime 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')
  import os.path, spyce
  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
  response.write(include.dump(path, 1))
  raise spyceDone
]]
Run this code

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 using 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. Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is slower, since a new engine must be created for each request. See the configuration section for details. 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, as expected. 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 compiler cache, since 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 2.1
Command-line usage:
  spyce -c [-o filename.html] <filename.spy>
  spyce -w <filename.spy>                <-- CGI
  spyce -O filename(s).spy               <-- batch process
  spyce -l [-d file ]                    <-- 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
    -d, --daemon         run as a daemon process with given pidfile
    --conf [file]        Spyce configuration file
To configure Apache, please refer to: spyceApache.conf
For more details, refer to the documentation.
  http://spyce.sourceforge.net
Send comments, suggestions and bug reports to <rimon-AT-acm.org>.

3.6. Configuration

Since 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 spyceconf.py in its installation directory. An alternative file location may be specified via the --conf command-line option.

The spyce configuration file is a valid python module; any python code may be used. To avoid duplication, we recommend starting with "from spyceconf import *" and override only select settings. One thing you cannot do from the config module is access the Spyce server object spyce.getServer(), since it has not been initialized yet.

You may access the loaded configuration module from Spyce scripts and from Python modules using the config attribute of the server object. Or, simply as the spyceConfig module, regardless of it actual file name. For example:

examples/config.spy
[[\
  import spyceConfig
  home = spyceConfig.SPYCE_HOME
]]

[[= home ]]
Run this code

Below is the configuration file that this server is running. The length of the file is primarily due to the thoroughness of the comments:

# NOTE: do note write code that directly imports this module 
# (except when you are writing a custom configuration module.)
# This is a recipe for trouble, since spyce allows the user
# to specify the configuration module filename on the commandline.
# Instead, use import spyce; spyce.getServer().config.

import os, sys
import spycePreload

# Determine SPYCE_HOME dynamically.
# (you can hardcode SPYCE_HOME if you really want to, but it shouldn't be necessary.)
SPYCE_HOME = spycePreload.guessSpyceHome()

# The spyce path determines which directories are searched for when
# loading modules (with [[.import]]) and tag libraries (with [[.taglib]]
# and the globaltags configuration later in this file.
#
# By default, the Spyce installation directory is always searched
# first. Any directories in the SPYCE_PATH environment are also
# searched.
#
# If you need to import from .py modules in nonstandard locations
# (i.e., not in your python installation's library directory),
# you will want to add their directories to sys.path as well, as
# done here for the error module.  (However, Spyce automagically
# changes sys.path dynamically so you will always be able to import
# modules in the same directory as your currently-processing .spy file.)
#
# path += ['/usr/spyce/inc/myapplication', '/var/myapp/lib']
path = [os.path.join(SPYCE_HOME, 'modules'), os.path.join(SPYCE_HOME, 'contrib', 'modules')]
path.append(os.path.join(SPYCE_HOME, 'tags'))
path.append(os.path.join(SPYCE_HOME, 'contrib', 'tags'))
if os.environ.has_key('SPYCE_PATH'):
    path += os.environ['SPYCE_PATH'].split(os.pathsep)
# provide originalsyspath so if someone wants to maintain a config file via
# "from spyceconf import *"
# he can remove the above modifications if desired.
originalsyspath = list(sys.path)
sys.path.extend(path)

# The globaltags option specifies a group of tag libraries that will be autoloaded
# as if [[.taglib name=libname from=file as=prefix]] were specified in every .spy
# file.  (There is no performance hit if the library is not used on a page;
# the Spyce compiler optimizes it out.)
#
# The format is ('libname', 'file', 'prefix').
# For a 2.0-style tag library, the libname attribute is ignored.  Passing None is fine.
#
# globaltags.append(('mytag', 'mytaglib.py', 'my'))
# globaltags.append((None, 'taglib2.py', 'my2'))
globaltags = [
    ('core', 'core.py', 'spy'),
    ('form', 'form.py', 'f'),
    (None, 'render.spi', 'render'),
    ]

# The default parent template is the one that is used by <spy:parent>
# if no src attribute is given, specified as an absolute url.
defaultparent = "/parent.spi"

# The errorhandler option sets the server-level error handler.  These
# errors include spyce.spyceNotFound, spyce.spyceForbidden,
# spyce.spyceSyntaxError and spyce.pythonSyntaxError.  (file-level
# error handling is defined within Spyce scripts using the error module.)
#
# The server will call the error handler as errorhandler(request, response, error).
#
# Please look at the default function if you are considering writing your own
# server error handler.
import error
errorhandler = error.serverHandler

# The pageerror option sets the default page-level error handler.
# "Page-level" means all runtime errors that occur during the
# processing of a Spyce script (i.e. after the compilation phase has
# completed successfully)
#
# The format of this option is one of:
#   ('string', 'MODULE', 'VARIABLE')
#   ('file', 'URL')
# (This format is used since the error template must be transformed into
# compiled spyce code, which can't be done before the configuration file
# is completely loaded.)
#
# Please refer to the default template to see how to define your own
# page-level error handlers.
#
# pageerrortemplate = ('file', '/error.spy')
pageerrortemplate = ('string', 'error', 'defaultErrorTemplate')

# The cache option affects the underlying cache mechanism that the
# server uses to maintain compiled Spyce scripts. Currently, Spyce
# supports two cache handlers:
#
#   cache = 'memory'
#   OR
#   cache = 'file'
#   cachedir = '/tmp' # REQUIRED: directory in which to store compiled files
#
# Why store the cache in the filesystem instead of memory?  The main
# reason is if you are running under CGI or mod_python.  Under
# mod_python, Apache will kick off a number of separate spyce
# processes; each would have its own memory cache; using the
# filesystem avoids wasteful duplication.  (Pointing the cachedir to a
# ramdisk makes it almost as fast as a memory cache.)  Under CGI of
# course, the python process isn't persistent so file caching is the
# only option to avoid expensive recompilation with each request.
#
# If you are running multiple Spyce instances on the same machine,
# they cannot share the same cachedir.  Give each a different cachedir,
# or use the in-memory cache type.
cache = 'memory'

# The check_mtime option affects the caching of compiled Spyce code.
# When True, Spyce will check file timestamps with each request and
# recompile if they have been modified.  Setting this to False can
# speed up a "production server" but you will have to restart the
# server and (if using a file cache) clear out the cachedir
# to have changes made in the spyce code take effect.
check_mtime = True

# The debug option turns on a LOT of logging to stderr.
debug = False

# The globals section defines server-wide constants. The hashtable is
# accessible as "pool" within any Spyce file (with the pool
# method loaded), or as self._api.getServerGlobals() within any Spyce
# module.
#
# globals = {'name': "My Website", 'four': 2+2}
globals = {}

# You may wish to pre-load various Python modules during engine initialization.
# Once imported, they will be in the python module cache.
#
# (You may of course use normal imports at any time in this configuration script;
# however, imports specified here are run after the Spyce server is
# completely initialized, making it safe to access the server internals via
# import spyce; spyce.getServer()...)
#
# imports = ['myModule', 'myModule2']
imports = []

# The root option defines the path from which Spyce requests are processed.
# I.e., when a request for http://yourserver/path/foo.spy arrives,
# Spyce looks for foo.spy in <root>/path/.
# 
# root = '/var/www/html'
root = os.path.join(SPYCE_HOME, 'www')
# feel free to comment this next line out if you don't have python modules (.py) in your web root
sys.path.append(root)

# some parts of spyce may need to create temporary files; usually the default is fine.
# BUT if you do override this, be sure to also override other parts of the config
# that reference it.  (currently just session_store)
import tempfile
tmp = tempfile.gettempdir()

# active tag to render form validation errors
validation_render = 'render:validation'


#####
# database connection
#####

from sqlalchemy.ext.sqlsoup import SqlSoup

# Examples:
# db = SqlSoup('postgres://user:pass@localhost/dbname')
# db = SqlSoup('sqlite:///my.db')
# db = SqlSoup('mysql://user:pass@localhost/dbname')
#
# SqlSoup takes the same URLs as an SqlAlchemy Engine.  See 
# http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing
# for more examples.
try:
  db = SqlSoup('sqlite:///www/demos/to-do/todo.db')
except:
  db = None

#####
# session options -- see docs/mod_session.html for details
#####

import session

session_store = session.DbmStore(tmp)
# session_store = session.MemoryStore()

session_path = '/'

session_expire = 24 * 60 * 60 # seconds

#####
# login options
#####

# The spyce login system uses the session storage
# defined above; a key called _spy_login will be added to each session.

# validators must be a function that takes login and password as
# arguments, and returns a pickle-able object (usually int or string)
# representing the ID of the logged in user, or None if login fails.
#
# You'll need to supply your own to hook into your database or other
# validation system; see the pyweboff config.py for an example of doing
# this.
def nevervalidator(login, password):
        return None

def testvalidator(login, password):
    if login == 'spyce' and password == 'spyce':
        return 2
    return None

login_defaultvalidator = testvalidator

# How to store login tokens.  FileStorage comes with Spyce,
# but it's easy to create your own Storage class if you want to put them
# in your database, for instance.
from _coreutil import FileStorage
# It's not a good idea to put login-tokens off of www/ in production!
# It's just done this way here to keep the spyce source tree relatively clean.
login_storage = FileStorage(os.path.join(SPYCE_HOME, 'www', 'login-tokens'))

# tags to render login form; must be visible in the globaltags search space.
# These come from tags/render.spi; to make your own, just create a tag 
# that takes the same parameters and add the library to the globaltags list above.
login_render = 'render:login'
loginrequired_render = 'render:login_required'

######
# webserver options -- does not affect mod_python or *CGI configurations
######

# indexFiles specifies a list of files to look for
# in a directory if directory itself is requested.
# The first matching file will be selected.
# If empty, a directory listing will be served instead.
#
# indexFiles = ['index.spy', 'index.html', 'index.txt']
indexFiles = ['index.spy', 'index.html']

# The Spyce webserver uses a threaded concurrency model.  (Historically,
# it also offered "no concurrency" and forking.  If for some strange
# reason you really want no concurrency, set minthreads=maxthreads=1.
# Forking was removed entirely because it performed over 10x slower
# than threading on Linux and Windows.)
#
# Do note that because of the Python GIL (global interpeter lock),
# only one CPU of a multi-CPU machine can execute Python code at a time.
# If this is your situation, mod_python may be a better option for you.
minthreads = 5
maxthreads = 10
# number of pending requests to accept
maxqueuesize = 50

# Restart the webserver if a python module changes.
# Spyce does this by running the "real" server in a subprocess; when that
# server detects changed modules, it exits and Spyce starts another one.
#
# Spyce will check for changes every second, or when a request
# is made, whichever comes first.
#
# It's highly recommended to turn this off for "production" servers,
# since checking each module for each request is a performance hit.
check_modules_and_restart = True

# The ipaddr option defines which IP addresses the server will listen on.
# empty string for all.
ipaddr = ''

# The port option defines which TCP port the server will listen on.
port = 8000
# Port that provides an interactive Python console interface to
# the webserver's guts.  Currently no password protection is offered;
# don't expose this to the outside world!
adminport = None

# The mime option is a list of filenames. The files should
# be definitions of mime-types for common file extensions in the
# standard Apache format.
#
# mime: ['/etc/mime.types']
mime = [os.path.join(SPYCE_HOME, 'spyce.mime')]

# The www_handlers option defines the hander used for files of
# arbitrary extensions.  (The None key specifies the default.)
# The currently supported handlers are:
#   spyce     - process the file at the requested path as a spyce script
#   directory - display directory listing
#   dump      - transfer the file at the requested path verbatim, 
#     providing an appropriate "Content-type" header, if it is known.
# (It's difficult to use the actual instance methods here since we
# don't have a handle to the WWW server object.  So, we use strings
# and let spyceWWW eval them later.)
www_handlers = {
  'spy': 'spyce',
  '/':   'directory',
  None:  'dump'
}


######
# (F)CGI options
######

# Forbid direct requests to the cgi script and discard command line arguments.
#
# Reason: http://www.cert.org/advisories/CA-1996-11.html
#
# You may need to disable this for
#  1) non-Apache servers that do not set the REDIRECT_STATUS environment
#     variable.
#  2) when using the alternative #! variant of CGI configuration
cgi_allow_only_redirect = False


######
# standard module customization
######

# (request module)

# param filters and file filters are lists of functions that are called
# for all GET and POST parameters or files, respectively.
# Each callable should expect two arguments: a reference to the
# request object/spyce module, and the dictionary being filtered.
# (Thus, each callable in param_filters will be called twice; once for the
# GET dict and once for POST. Each callable in file_filters will only be called once.)
param_filters = []
file_filters = []

3.7. Server utilities

Like any application server, the Spyce server provides several facilities that can aid development. 3.7.1. The Spyce scheduler

Spyce provides a scheduler that allows you to easily define tasks to run at specified times or intervals inside the Spyce server. This allows your tasks to leverage the tools Spyce gives you, as well as any global data your application maintains within Spyce, such as cached data or database connection pools. This also has the advantage (over, say, crontab entries) of keeping your application self-contained, making it easier to deploy changes from a development machine to production.

The Spyce scheduler is currently only useful if you are running in webserver mode. If you run under mod_python, CGI, or FastCGI, you could approximate scheduler behavior by storing tasks and checking to see if any are overdue with every request received; this would be an excellent project for someone wishing to get started in Spyce development.

 
 
scheduler
index
/var/spyce-2.1/scheduler.py

A module for scheduling arbitrary callables to run at given times
or intervals, modeled on the naviserver API.  Scheduler runs in
its own thread; callables run in this same thread, so if you have
an unusually long callable to run you may wish to give it its own
thread, for instance,
 
schedule(3600, lambda: threading.Thread(target=longcallable).start())
 
Scheduler does not provide subsecond resolution.
 
Public functions are threadsafe.

 
Modules
       
atexit
sys
threading
time
traceback

 
Classes
       
Task

 
class Task
    Instantiated by the schedule methods.
 
Instance variables:
  nextrun: epoch seconds at which to run next
  interval: seconds before repeating
  callable: function to invoke
  last: if True, will be unscheduled after nextrun
 
(Note that by manually setting last on a Task instance, you
can cause it to run an arbitrary number of times.)
 
  Methods defined here:
__init__(self, firstrun, interval, callable, once)
__repr__(self)
examples/scheduling.py
import spyce, scheduler

def delete_unsubmitted():
    db = spyce.SPYCE_GLOBALS['dbpool'].connection()
    sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"
    db.execute(sql)

# delete alerts that were created over a week ago but but still not submitted 
scheduler.schedule_daily(00, 10, delete_unsubmitted)

3.7.2. spyceUtil

Most of the spyceUtil module is interesting only to internal operations, but several functions are more generally applicable:

  • url2file( url, relativeto=None )
    Returns the filesystem path of the file represented by url, relative to a given path. For example, url2file('/index.spy') or url2file('img/header.png', request.filename()).
  • exceptionString( )
    Every python programmer writes this eventually: returns a string containing the description and stacktrace for the most recent exception.

3.8. Modules

The Spyce language, as described above, is simple and small. Most functionality is provided at runtime through Spyce modules and Python modules.

The standard Spyce modules are documented here; some other modules are also distributed in the contrib/ directory.

Non-implicit modules are imported using the Spyce [[.import]] directive. Python modules are imported using the Python import keyword. Remember that modules need to have the same read permissions as regular files that you expect the web server to read.

Modules may be imported with a non-default name using the as attribute to the .import directive. This is discouraged for standard Spyce modules; the session module, for example, expects to find or otherwise load a module named cookie in the Spyce environment.

Once included, a Spyce module may be accessed anywhere in the Spyce code. 3.8.1. DB (implicit)

Spyce integrates an advanced database module to make reading and modifying your data faster and less repetitive.

Just initialize the db reference in your Spyce config file following the examples given there, and you're all set.

The general idea is, db.tablename represents the tablename table in your database, and provides hooks for reading and modifying data in that table in pure Python, no SQL required. We find this gives 90% of the benefits of an object-relational mapping layer, without making developers learn another complex tool. And since the Spyce db module is part of SQLAlchemy, probably the most advanced database toolkit in the world, the full ORM approach is available to those who want it.

Here's a quick example of reading and inserting data from a table called todo_lists:

examples/db.spy
<spy:parent title="To-do demo" />

[[!
def list_new(self, api, name):
    if api.db.todo_lists.selectfirst_by(name=name):
        raise HandlerError('New list', 'a list with that description already exists')
    api.db.todo_lists.insert(name=name)
    api.db.flush()
]]

(This is an self-contained example using the same database as the
<a href=/demos/to-do/index.spy>to-do demo</a>.)

<h2>To-do lists</h2>

[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
<spy:ul data="[L.name for L in lists]" />

<h2>New list</h2>
<f:form>
<f:submit value="New list" handler=self.list_new />:
<f:text name=name value="" />
</f:form>
Run this code

Full SqlSoup documentation follows.

Loading objects

Loading objects is as easy as this:

    >>> users = db.users.select()
    >>> users.sort()
    >>> users
    [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), 
     MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]

Of course, letting the database do the sort is better (".c" is short for ".columns"):

    >>> db.users.select(order_by=[db.users.c.name])
    [MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
     MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]

Field access is intuitive:

    >>> users[0].email
    u'student@example.edu'

Of course, you don't want to load all users very often. The common case is to select by a key or other field:

    >>> db.users.selectone_by(name='Bhargan Basepair')
    MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)

Select variants

All the SqlAlchemy Query select variants are available. Here's a quick summary of these methods: - get(PK): load a single object identified by its primary key (either a scalar, or a tuple) - select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by. - select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options. - selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected. - selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected. - count, count_by: returns an integer count of the rows selected. See the SqlAlchemy documentation for details: - general info and examples - details on constructing WHERE clauses

Modifying objects

Modifying objects is intuitive:

    >>> user = _
    >>> user.email = 'basepair+nospam@example.edu'
    >>> db.flush()

(SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so multiple updates to a single object will be turned into a single UPDATE statement when you flush.)

To finish covering the basics, let's insert a new loan, then delete it:

    >>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    >>> db.flush()

    >>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
    >>> db.delete(loan)
    >>> db.flush()

You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more, this time using the loans table's delete method. (For SQLAlchemy experts: note that no flush() call is required since this delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules apply here as to the select methods:

    >>> db.loans.insert(book_id=book_id, user_name=user.name)
    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    >>> db.flush()
    >>> db.loans.delete(db.loans.c.book_id==2)

You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2:

    >>> db.loans.update(db.loans.c.book_id==2, book_id=1)
    >>> db.loans.select_by(db.loans.c.book_id==1)
    [MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]

Joins

Occasionally, you will want to pull out a lot of data from related tables all at once. In this situation, it is far more efficient to have the database perform the necessary join. (Here we do not have "a lot of data," but hopefully the concept is still clear.) SQLAlchemy is smart enough to recognize that loans has a foreign key to users, and uses that as the join condition automatically.

    >>> join1 = db.join(db.users, db.loans, isouter=True)
    >>> join1.select_by(name='Joe Student')
    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]

You can compose arbitrarily complex joins by combining Join objects with tables or other joins.

    >>> join2 = db.join(join1, db.books)
    >>> join2.select()
    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
     id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]

If you join tables that have an identical column name, wrap your join with "with_labels", and all the columns will be prefixed with their table name:

    >>> db.with_labels(join1).select()
    [MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
                          users_password='student',users_classname=None,users_admin=0,
                          loans_book_id=1,loans_user_name='Joe Student',
                          loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]

Advanced usage

You can access the SqlSoup's engine attribute to compose SQL directly. The engine's execute method corresponds to the one of a DBAPI cursor, and returns a ResultProxy that has fetch methods you would also see on a cursor.

    >>> rp = db.engine.execute('select name, email from users order by name')
    >>> for name, email in rp.fetchall(): print name, email
    Bhargan Basepair basepair+nospam@example.edu
    Joe Student student@example.edu

You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details.

You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively. 3.8.2. Request (implicit)

The request module is loaded implicitly into every Spyce environment.

The spyce configuration file gives two lists that affect the request module: param_filters and file_filters. param_filters is a list of functions to run against the GET and POST variables in the request; file filters is the same, only for files uploaded. Each function will be passed the request module and a dictionary when it is called; each will be called once for GET and once for POST with each new request.

These hooks exist because the request dictionaries should not be modified in an ad-hoc manner; these allow you to set an application-wide policy in a well-defined manner. You might, for instance, disallow all file uploads over 1 MB.

Here's an example that calls either Html.clean or Html.escape (not shown) to ensure that no potentially harmful html can be injected in user-editable areas of a site:

examples/filter.py
def htmlFilter(request, d):
  # note that spoofing __htmlfields doesn't help attacker get unsafe html in;
  # we always call either clean() or escape().
  try:
    # don't use request['__htmlfields'], or you will recurse infinitely
    toClean = request._post['__htmlfields'][0].split(',')
  except KeyError:
    toClean = []
  for key in d:
    if key in toClean:
      d[key] = [Html.clean(s) for s in d[key]]
    else:
      d[key] = [Html.escape(s) for s in d[key]]

The request module provides the following methods:

  • login_id:
    Returns the id generated by your validator function if the user has logged in via spy:login or spy:login_required, or None if the user is unvalidated. (See the core tag library for details on the login tags.)
  • 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 an empty list, 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 an empty list, 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. If no value is found, then this method returns None. Note: Throwing an exception seemed too strong a semantics, and so this is a break from Python. One can also iterate over the request object, as if over a dictionary of field names in the get1 and post1 dictionaries. In the case of overlap, the get1() dictionary takes precedence.
  • 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( [path] ):
    Return the Spyce filename of the request currently being processed. If an optional path parameter is provided, then that path is made relative to the Spyce filename of the request currently being processed.
  • stack( [i] ):
    Returns a stack of files processed by the Spyce runtime. If i is provided, then a given frame is returned, with negative numbers wrapping from the back as per Python convention. The first (index zero) item on the stack is the filename corresponding to the URL originally requested. The last (index -1) item on the stack is the current filename being processed. Items are added to the stack by includes, Spyce lambdas, and internal redirects.

  • default( value, value2 ):
    (convenience method) Return value if it is not None, otherwise return value2.
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

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

3.8.3. Response (implicit)

Like the request module, the response module is also loaded implicitly into every Spyce environment. 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( [t] ):
    Timestamps the response with an HTTP Date: header, using the optional t 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 t is omitted, the current time is used.
  • expires( [t] ):
    Sets the expiration time of the response with an HTTP Expires: header, using the optional t parameter, which may be either the number of seconds since the epoch (see Python time module), or a properly formatted HTTP date string. If t 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( [t] ):
    Sets the last modification time of the response with an HTTP Last-Modified: header, using the optional t 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 t 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')
  import os.path, spyce
  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
  response.write(include.dump(path, 1))
  raise spyceDone
]]
Run this code

3.8.4. Redirect

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

  • internal( uri ):
    Performs an internal redirect. All processing on the current page ends, the output buffer is cleared and processing continues at the named uri. 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 unless you raise spyceDone, 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

3.8.5. 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.

<