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 a server-side language that supports simple and
efficient Python-based dynamic HTML generation, otherwise called Python
Server Pages (PSP). Those who are familiar with JSP, PHP, or ASP and like
Python, should have a look at Spyce. Its 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.
Like JSP, PHP, ASP and other similar HTML scripting languages it allows the
generation of dynamic 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. 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 of
classes for minor changes. Third, Spyce code is of first-order in the Spyce
language, unlike JSP, which allows you to create useful Spyce lambda
functions. 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 web server 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. Spyce can
currently run under mod_python (Apache), as well as under CGI and FastCGI, or as a proxy server, which is
supported in the majority of web server environments. Adapters have also
been written for Xitami, Coil, Cheetah -- other web servers and frameworks.
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, as discussed
above, 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 (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." 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 an HTML-embedded language, not an application
server.
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 5000 lines of code -- tiny
compared to PHP or Zope, etc. The standard modules comprise another
2000 lines.
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.
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).
Note that ASP/JSP-style delimeters are supported as well! 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>
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.
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 some other port number, or omit the -p switch
and assume the default, which is port 80. Replace root with the root
document directory; the default is the current directory. All web requests are
served relative to the server's root document directory.
Windows users should note that there is no command called spyce installed on their system. They should execute,
instead:
then you will see the file: root/some_path/hello.spy
and 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 compiled Spyce
script. It sometimes helps to understand the transformation that is taking
place. Execute:
spyce -c hello.spy
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 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.
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 caller. Spyce lambdas do not currently support nested variable
scoping, nor default parameters, which Python users may be accustomed to.
Note that braces in "[parameters]" above indicate
that a Spyce lambda may have zero or more 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, 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 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 Spyce compiler to
convert primarily Python code to... Python code!
[[.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.
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 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, or formatted.
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.
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 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')
response.write(include.dump('spyce.gif', 1))
raise spyceDone
]]
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.
The first three options exhibit high performance; the CGI approach is
slowest, since a new engine must be created for each request:
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. The CGI-mode, while the slowest alternative, is
especially convenient during the development of Python modules,
ensuring that modules always get reloaded. Another alternative is to
use the debug
configuration switch.
Others: Spyce abstracts its operating environment using a thin
indirection layer. Spyce users have written small Spyce adapters for the
Xitami webserver and also to integrate with the Coil framework. Writing your
own adapter, should the need arise, is therefore a realistic possibility.
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 v1.3.13, 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 process
spyce -l [-p port] [-d file ] [<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
-d, --daemon run as a daemon process with given pidfile
-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.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 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 its
built-in default values. In general, command-line options supercede
configuration file options which, in turn, supercede built-in configuration
defaults. The options specified in the Spyce configuration file are
server-level configuration options (as opposed to file-level options). An
example configuration file is provided, called spyce.conf.eg.
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 script (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.
Alternatively, one could decide to perform development under CGI mode.
This ensures that a newly loaded Spyce engine is used on every request.
3.6.9. globals
The globals section is also entirely optional. Any "key: expr"
pair will be stored in a dictionary, h, as h[key]=eval(expr). In other words,
the expression is evaluated first, and the result is stored in the dictionary
using the given key. This dictionary is accessible as:
within a...
as
Spyce script
pool.server
(with pool module loaded)
Spyce module
self.wrapper.server.globals
Python module
spyce.getServerGlobals()
(with spyce imported)
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. Other handlers may readily
be added to the Spyce webserver, but it is never intended to be a fully
featured web server.
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. Although other entry points into the engine code
as possible, the most convenient entry points are in spyce.py:
spyceFileHandler( request, response, filename, [sig], [args], [kwargs], [config_file] ) explanation forthcoming; read the code for now, or send an email
spyceStringHandler( request, response, code, [sig], [args], [kwargs], [config_file] ) explanation forthcoming; read the code for now, or send an email
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, just like Python modules. Spyce modules are objects. They provide
methods and fields. One uses them as regular Python objects. Modules are
start()ed before pre-Spyce processing, and finish()ed post-Spyce
processing. They are init()ialized 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. In other words, you should rename
modules only if you have read their implementations and know what you are
doing.
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. The latter two are loaded only if Spyce lambdas and active tags
are used in the script, respectively. 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. 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.
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>
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>
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>
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
]]
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:
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' ]]
<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>
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.
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>
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:
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, include.vars.x is
equivalent to include.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.
spyceStr( file, [context] ): Same as spyce(), but
performs no output and instead returns the processed included Spyce file as
a string.
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:
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.
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 function input can be any
Python type, and the function output may be any Python type. The result is
then converted into a string and emitted. 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
" ".
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>
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>
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>
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 can 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>
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>
Finally, one can easily define some new session handling mechanism using
callback functions, as this last example shows:
examples/mysession.spy
[[.import names="pool,session"]]
[[-- 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>
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>
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>
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>')
]]
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:
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, data, tag).
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):
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 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. Form
The form active tag library is designed to simplify the generation of forms.
The tags in this library closely follow the names of HTML form tags. The active
tags automatically look up the appropriate data values or defaults. In time,
more tags will be added for server-side verification.
<form
[method=expr] [action=expr] [value=expr] [default=expr] ...> </form>
Begin a new form. The method parameter is optional and defaults
to 'GET'. The action is evaluated in the tag context. Both parameters
are emitted. The value parameter is a dictionary of values, which
overrides any submitted form values. The default parameter is a
dictionary of values that is overridden by any submitted values.
<submit
[name=expr] [value=expr] ... />
Create a submit button. The name and value parameters are
evaluated within the tag context and emitted.
<hidden
name=expr [value=expr] [default=expr] .../>
Create a hidden form field. The name parameter is evaluated and
emitted. Both the value and default optional parameters are
expressions and are evaluated. The value emitted is, in order of decreasing
priority: local tag value, form tag value, value in submitted request
dictionary, local tag default, form tag default. We search this list for the
first non-None value.
<text
name=expr [value=expr] [default=expr] [size=expr] [maxlength=expr] .../>
Create a form text field. The name parameter is evaluated and
emitted. Both the value and default optional parameters are
expressions and are evaluated. The value emitted is, in order of decreasing
priority: local tag value, form tag value, value in submitted request
dictionary, local tag default, form tag default. We search this list for the
first non-None value. The size and maxlength optional
parameters are evaluated and emitted.
<password
name=expr [value=expr] [default=expr] [size=expr] [maxlength=expr] .../>
Create a form password field. Parameters are the same as for text
fields, explained above.
<textarea
name=expr [value=expr] [rows=expr] [cols=expr] ...>default</textarea>
Create a form textarea field. The name parameter is evaluated and
emitted. The value optional parameter is evaluated. A default
may be provided in the body of the tag. The value emitted is, in order of
decreasing priority: local tag value, form tag value, value in submitted
request dictionary, local tag default, form tag default. We search this list
for the first non-None value. The rows and cols optional
parameters are evaluated and emitted.
<radio
name=expr value=expr [checked] [default] .../>
Create a form radio-box. The name and value parameters are
evaluated and emitted. A checked and default flags affect
whether this box is checked. The box is checked based on the following
values, in decreasing order of priority: local tag value, form tag value,
value in submitted request dictionary, local tag default, form tag default.
We search this list for the first non-None value.
<checkbox
name=expr value=expr [checked] [default] .../>
Create a form check-box. Parameters are the same as for radio
fields, explained above.
<select
name=expr [value=expr] [default=expr] [multiple] [size=expr] ...>...</select>
Create a form select block. The name parameter and the optional
size parameters are evaluated and emitted. The value and
default optional parameters are evaluated and serve to select the
nested option fields. The multiple flag sets whether multiple
selections are allowed.
<option
[text=expr] [value=expr] [selected] [default] .../>
<option
[value=expr] [selected] [default] ...>text</option>
Create a form selection option. This tag must be nested within a
select tag. The text optional parameter is evaluated and
emitted in the body of the tag. It can also be provided in the body of the
tag, as per the HTML standard. The optional value parameter is
evaluated and emitted. The selected and default flags determine
which options are selected. The options is selected based on the following
values, in decreasing order of priority: local tag value, select tag value,
form tag value, value in submitted request dictionary, local tag default,
select tag default, form tag default. We search this list for the first
non-None value.
5.3. 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, separation of business logic, or
component rendering. 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. Another convenient use for
tags is to automatically fill forms with session data. These are only a few of
the uses for tags. 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'
mustend = 1
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))
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:
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 with the current output stream
(unbuffered) or with a new, buffered output stream. Buffering is useful for
tags that may want to transform, or otherwise use, the output of processing
their own bodies, before it is sent to the client. The inherited default is
false.
conditional: This flag indicates whether this tag may
conditionally control the execution of its body. If true, then the begin()
method of the tag must return true to process the tag body, or false to skip
it. If the flag is set to false, then return value of the begin() method is
ignored, and the body executed (unless an exception is triggered). Some
tags, such as the core:if tag, require this
functionality, and will set the flag true. Many other kinds of tags do not,
thus saving a level of indentation (which is unfortunately limited in Python
-- hence the need for this switch). The inherited default is false.
loops: This flag indicates whether this tag may want to loop
over the execution of its body. If true, then the body() method of the tag
must return true to repeat the body processing, or false to move on to the
end() of the tag. If the flag is set to false, then the return value of the
body() method is ignored, and the body is not looped. Some tags, such as the
core:for tag, require this functionality, and
will set the flag true. Many other kinds of tags do not, thus saving a level
of indentation. The inherited default is false.
catches: This flag indicates whether this tag may want to
catch any exceptions that occur during the execution of its body. If true,
then the catch() method of the tag will be invoked on exception. If the flag
is false, the exception will continue to propagate beyond this point. Some
tags, such as the core:catch, require this
functionality, and will set the flag true. Many other kinds of tags do not,
thus saving a level of indentation. The inherited default is false.
mustend: This flag indicates whether this tag wants the
end() method to get called, if the begin() completes successfully, no
matter what. In other words, the call to end() is placed in the finally
of try-finally block which begins just after the begin(). This is useful for
tag cleanup, such as in the core:let tag, which
sets this flag to true in order to ensure that variables are removed from
the tag context. However, many other tags do not perform anything in the
end() of their tag, or perhaps perform operations that are not important in
the case of an exception. Such tags do not require this functionaliy, thus
saving a level of indentation. 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. This method may return a boolean flag. If
conditional is set to true (see above), then this flag indicates
whether the body of the tag should be processed (true), or skipped (false).
The inherited method performs no operation, except to return true.
body( contents ): This method is invoked when the body of
the tag has completed processing. It will be called also for a
singleton, which we assume simply has an empty body. However, it will not be
called if the begin() method has chosen to skip body processing entirely. If
the tag sets buffer to true for capturing the body 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 any output has already been written to the
enclosing scope. If the loops flag is set to true, then 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. And
again, and again, until false is received. The inherited tag method performs
nothing and returns false.
end(): This method is invoked when the corresponding end tag
is encountered. For a singleton tag, we assume that the end tag immediately
follows the begin, and still invoke this method. If the mustend flag
has been set to true, then the runtime engine semantics ensure that if the
begin method terminates successfully, this method will get called,
even in the case of an exception during body processing. The inherited
method is a noop.
catch( ex ): If the catches flag has been set to
true, then if any exception occurs in the begin(), body() or end() methods
or from within the body processing, this method will be invoked. The
parameter ex holds the value that was thrown. The inherited method
simply re-raises 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( [name] ): Return the object of the direct parent
tag, or None if this is the root active tag. Plain (inactive) tags do not
have objects associated with them in this hierarchy. If the optional
name parameter is provided, then we search up the tree for an active
tag of the same library and with the given name. If such a tag can not be
found, then return None.
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 (and version 3.0.3 with apache 2.0.37). 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.
Users have written a number of additional Spyce adapters, including an adapter
for the Xitami extension protocol and an adapter for the Coil framework.
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.
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.13-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
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"
# #PythonOptimize On
#</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.
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:
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.
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:
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:
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.
Related work - Links to
other related projects online
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>
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
modpython publisher
300
Spyce-proxy
200
JSP
100
PHP
450
Spyce-FCGI
100
Python-FCGI
140
Spyce-CGI
8
Python-CGI
25
C-CGI
180
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.
Version 1.3 introduced active tags. More performance work, more
modules, etc. Development continues...
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).