|
|
|
Documentation
Spyce - Python Server Pages (PSP)
User Documentation
Release 2.1
[ Multi-Page Format ]
TABLE OF CONTENTS
1. INTRODUCTION
This document aims to be the authoritative source of
information about Spyce, usable as a comprehensive refence, a user guide and a
tutorial. It should be at least skimmed from beginning to end,
so you have at least an idea of the functionality available and can refer
back for more details as needed.
Spyce is a server-side language that supports elegant and
efficient Python-based dynamic HTML generation.
Spyce allows embedding Python in pages similar to how JSP embeds Java,
but Spyce is far more than a JSP clone. Out of the box, Spyce provides
development as rapid as other modern frameworks like Rails, but with an
cohesive design rather than a morass of special cases.
Spyce's modular design makes it very flexible
and extensible. It can also be used as a command-line utility for static text
pre-processing or as a web-server proxy.
Spyce's performance is comparable to the
other solutions in its class.
Note: This manual assumes a knowledge of Python and focusses
exclusively on Spyce. If you do not already know Python, it is easy to
learn via this short tutorial, and has
extensive documentation.
1.1. Rationale / competitive analysis
This section is somewhat dated. We plan to update it soon.
A natural question to ask is why one would choose Spyce over JSP, ASP, PHP,
or any of the other HTML scripting languages that
perform a similar function. We compare Spyce with an array of exising tools:
- Java Server Pages, JSP, is a widely popular, effective and
well-supported solution based on Java Servlet technology. Spyce differs from
JSP in that it embeds Python code among the HTML, thus providing a number of
advantages over Java.
- Python is a high-level scripting language,
where rapid prototyping is syntactically easier to perform.
- There is no need for a separate "expression langauge" in Spyce;
Python is well-suited for both larger modules and active tag scripting.
- Python
is interpreted and latently typed, which can be advantageous for
prototyping, especially in avoiding unnecessary binary incompatibility of
classes for minor changes.
- Spyce code is of first-order in the Spyce
language, unlike JSP, which allows you to create useful Spyce lambda
functions.
- Creating new active tags and modules is simpler in
Spyce than in JSP.
- Spyce is better-integrated than JSP; to get similar functionality
in JSP, you have to add JSF (Java Server Faces) and Tiles, or
equivalents.
- PHP is another popular webserver module for dynamic
content generation. The PHP interpreter engine and the language itself were
explicitly designed for the task of dynamic HTML generation, while Python is
a general-purpose scripting language.
- Spyce leverages from the extensive
development effort in Python: since any Python library can be imported and
reused, Spyce does not need to rebuild many of the core function libraries
that have been implemented by the PHP project.
- Use of Python
often simplifies integration of Spyce with existing system environments.
- Spyce code is also first-order in the Spyce language and Spyce supports
active tags.
- Spyce is modular in its design, allowing
users to easily extend its base functinality with add-on modules.
- The Spyce engine can be run from the command-line, which allows
Spyce to be used as an HTML preprocessor.
Spyce,
like PHP, can run entirely within the process space of a webserver or via
CGI (as well as other web server adapters), and has been benchmarked to be
competitive in performance.
- ASP.NET is a Microsoft technology
popular with Microsoft Internet Information Server (IIS) users. Visual
Basic .NET and C# are both popular implementation languages.
- Spyce provides the power of the ASP.NET "component" development style
without trying to pretend that web applications live in a stateful,
event-driven environment. This is a leaky abstraction that causes
ASP.NET to have a steep learning curve while the user learns
where the rough edges are.
- ASP.NET is not well-supported outside the IIS environment. Spyce can
currently run as a standalone or proxy server, under mod_python (Apache),
or under CGI and FastCGI, which are
supported in the majority of web server environments. Adapters have also
been written for Xitami, Coil, Cheetah -- other web servers and frameworks.
- Spyce is open-source, and free.
- WebWare with Python Server Pages, PSP, is another
Python-based open-source development. PSP is similar in design to the Spyce
language, and shares many of the same benefits. Some important differences
include
- Spyce supports both
Python chunks (indented Python) as well as PSP-style statements (braced
Python).
- Spyce supports active tags and component-based development
- Spyce code is first-order in the Spyce language
PSP is also an integral part of
WebWare, an application-server framework similar to Tomcat Java-based
application server of the Apache Jakarta project. Spyce is to WebWare as JSP
is to Tomcat. Spyce is far simpler to install and run than WebWare (in the
author's humble opinion), and does not involve notions such as application
contexts. It aims to do only one thing well: provide a preprocessor and
runtime engine for the dynamic generation of HTML using embedded Python.
- Zope is an object-oriented open-source application server,
specializing in "content management, portals, and custom applications." Zope
is the most mature Python web application development environment, but
to a large degree suffers from
second-system syndrome.
In the author's opinion, Zope is to a large degree responsible for the
large number of python
web environments: a few years ago, it was de rigeur for talented programmers
to try Zope, realize it was a mess, and go off to write their own framework.
Zope provides a scripting language called DHTML and can call
extensions written in Perl or Python. Spyce embeds Python directly in the
HTML, and only Python. It is an HTML-embedded language, not an application
server.
Spyce strikes a unique balance between power and simplicity.
Many users have said that this
is "exactly what they have been waiting for". Hopefully, this is the correct
point in the design space for your project as well.
1.2. Design Goals
As a Spyce user, it helps to understand the broad design goals of this tool.
Spyce is designed to be:
- Minimalist: The philosophy behind the design of Spyce is only
to include features that particularly enhance its functionality over the
wealth that is already available from within Python. One can readily import
and use Python modules for many functions, and there is no need to recode
large bodies of functionality.
- Powerful: Spyce aims to free the programmer from as much
"plumbing"-style drudgery as possible through features such as
Active Handlers
and reusable Active Tags.
- Modular: Spyce is built to be extended with
Spyce modules and
Active Tags
that provide additional functionality over the core engine
capabilities and standard Python modules. New features in the core engine
and language are rationalised against the option of creating a new module or
a new tag library. Standard Spyce modules and tag libraries are those that
are considered useful in a general setting and are included in the default
Spyce distribution. Users and third-parties are encouraged to develop their
own Spyce modules.
- Intuitive: Obey user expectations. Part of this is avoiding
special cases.
- Convenient: Using Spyce should be made as efficient as possible.
This, for example, is the reason behind the choice of [[ as delimeters over alternatives such as <? (php) and <% (jsp).
(However, ASP/JSP-style delimeters are also supported, so if you're
used to that style and like it, feel free to continue using it with Spyce.)
Functions and
modules are also designed with as many defaults as possible.
There are no XML configuration files in Spyce.
- Single-purpose: To be the best, most versatile, wildly-popular
Python-based dynamic HTML engine. Nothing more; nothing less.
- Fast: Performance is
important. It is expected that Spyce will perform comparably with any other
dynamic, scripting solutions available.
Now, let's start using Spyce...
2. LANGUAGE
The basic structure of a Spyce script is an HTML file with embeddings. There
are six types of possible embeddings among the plain HTML text:
- <taglib:name attr1=val1 ...>
Active tags may include presentation and action code.
- [[-- Spyce comment --]]
Enclosed code is elided from the compiled Spyce class.
- [[\ Python chunk ]]
Embed python in your code.
- [[! Python class chunk ]]
Like chunks, but at the class level rather than the spyceProcess method.
- [[ Python statement(s) ]]
Like chunks, but may include braces to indicate that the block should continue after the ]].
- [[= Python expression ]]
Output the result of evaluating the given expression.
- [[. Spyce directive ]]
Pass options to the Spyce compiler.
- [[spy lambda ]]
Allows dynamic compilation of Spyce code to a python function.
Each Spyce tag type has a
unique beginning delimeter, namely [[, [[\, [[=, [[. or [[--. All
tags end with ]], except comment tags, which
end with --]].
Since
[[ and
]]
are special Spyce delimeters, one would escape them as
\[[ and
\]]
for use in HTML text. They can not be escaped within Python code, but the
string expressions
("["*2) and
("]"*2), or equivalent expressions,
can be used instead, or the brackets can be conveniently separated with a
space in the case of list or slicing expressions.
2.1. Plain HTML and Active Tags
Static plain HTML strings are printed as they are encountered. Depending on
the compacting mode of the
Spyce compiler, some whitespace may be eliminated. The Spyce transform module, for example, may
further pre-processes this string, by inserting transformations into the
output pipe. This is useful, for example, for dynamic compression of the
script result.
The Spyce language supports tag libraries.
Once a tag library is imported under some name, mytags, then all static
HTML tags of the form <mytags:foo ... > become "active".
That is, code from the tag library is executed at that point in the document.
Tags can control their output, conditionally skip or loop the execution of
their bodies, and can interact with other active tags in the document. They
are similar, in spirit and functionality, to JSP tags. Tag libraries and modules (discussed later) can both
considerably reduce the amount of code on a Spyce page, and increase code
reuse and modularity.
2.2. Spyce Comments
Syntax: [[-- comment --]]
Spyce comments are ignored, and do not produce any output, meaning that they
will not appear at the browser even in the HTML source. The first line of a
Spyce file, if it begins with the characters #!, is also considered a
comment, by Unix scripting convention. Spyce comments do not nest.
2.3. Spyce Directives
Syntax: [[. directive ]]
Spyce directives directly affect the operation of the Spyce compiler. There
is a limited set of directives, listed and explained below:
-
[[.compact mode=mode]] :
Spyce can output the static HTML strings in various modes of compaction,
which can both save bandwidth and improve download times without visibly
affecting the output. Compaction of static HTML strings is performed once
when the input Spyce file is compiled, and there is no additional run-time
overhead beyond that. Dynamically generated content from Python code tags
and expressions is not compacted nor altered in any way. Spyce can operate
in one of the compaction modes listed below. One can use the compact
tag to change the compaction mode from that point in the file forwards.
- off: No compaction is performed. Every space and newline in the
static HTML strings is preserved.
- space: Space compaction involves reducing any consecutive runs
of spaces or tabs down to a single space. Any spaces or tabs at the
beginning of a line are eliminated. These transformations will not affect
HTML output, barring the <pre> tag, but can considerably reduce the
size of the emitted text.
- line: Line compaction eliminates any (invisible) trailing
whitespace at the end of lines. More significantly it improves the indented
presentation of HTML, by ignoring any lines that do not contain any static
text or expression tags. Namely, it removes all the whitespace, including
the line break, surrounding the code or directives on that line. This
compaction method usually "does the right thing", and produces nice HTML
without requiring tricky indentation tricks by the developer. It is,
therefore, the initial compaction mode.
- full: Full compaction applies both space and line compaction. If
the optional mode attribute is omitted, full compaction mode is the
default value assumed.
-
[[.import name=name from=file as=name args=arguments]] :
The import directive loads and defines a Spyce module into the global
context. (The [[.module ... ]]directive
is synonymous.) A Spyce module is a
Python file, written specifically to interact with Spyce. The name
parameter is required, specifying the name of the Python class to load. The
file parameter is optional, specifying the file where the named class
is to be found. If omitted, file will equal name.py. The file path can be absolute or
relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current
script directory, in that order. Users are encouraged to name or prefix their
modules uniquely so as not to be masked by system modules or tag libraries.
The as parameter is optional, and specifies the name under which the
module will be installed in the global context. If omitted, this parameter
defaults to the name parameter. Lastly, the optional args parameter
provides arguments to be passed to the module initialization function. All
Spyce modules are start()ed before Spyce processing begins,
init()ed at the point where the directive is placed in the code, and
finish()ed after Spyce processing terminates. It is convention to
place modules at, or near, the very top of the file unless the location of
initialization is relevant for the functioning of the specific module.
[[.import names="name1,name2,..."]] :
An alternative syntax allows convenient loading of multiple Spyce modules.
One can not specify non-standard module file locations, nor rename the
modules using this syntax.
-
[[.taglib name=name from=file as=name]] :
The taglib directive loads a Spyce tag library. A Spyce tag library is a Python file, written
specifically to interact with Spyce. The name parameter specifies
the name of the Python class to load if using a 1.x-style taglib;
otherwise it is ignored. The file parameter is
optional, specifying the file where the named class is to be found. If
omitted, file will equal name.py. The file
path can be absolute or relative. Relative paths are scanned in the Spyce
home, user-configurable server
path directories and current script directory, in that order. Users are
encouraged to name or prefix their tag libraries uniquely so as not to be
masked by system tag libraries and modules. The as parameter is
optional, and specifies the unique tag prefix that will be used to identify
the tags from this library. If omitted, this parameter defaults to the name
parameter. It is convention to place tag library directives at, or near, the
very top of the file. The tags only become active after the point of the tag
library directive.
Also note that the configuration parameter globaltags allows you
to set up tag libraries globally, freeing you from having to specify the
taglib directive on each page that uses a tag. By default, globaltags
installs core under the spy: prefix, and form under the f: prefix.
(Tag libraries specified in globaltags are only loaded if the Spyce compiler
determines they are actually used on the page, so there is no performance
difference between globaltags and manually setting up taglib for each page.)
There are some additional directives that are only legal when
defining an active tag library.
It is important to note that Spyce directives are processed at compile
time, not during the execution of the script, much like directives in C, and
other languages. In other words, they are processed as the Python code for the
Spyce script is being produced, not as it is being executed. Consequently, it
is not possible to include runtime values as parameters to the various
directives.
2.4. Python Statements
Syntax: [[ statement(s) ]]
The contents of a code tag is one or more Python statements. The statements
are executed when the page is emitted. There will be no output unless the
statements themselves generate output.
The statements are separated with semi-colons or new lines, as in regular
Python scripts. However, unlike regular Python code, Python statements do
not nest based on their level of indentation. This is because
indenting code properly in the middle of HTML is difficult on the developer.
To alleviate this problem, Spyce supports a slightly modifed Python syntax:
proper nesting of Spyce statements is achieved using begin- and end-braces:
{ and },
respectively. These MUST be used, because the compiler regenerates the
correct indentation based on these markers alone. Even single-statement blocks
of code must be wrapped with begin and end braces. (If you prefer to use
Python-like indentation, read about chunks).
The following Spyce code, from the Hello World!
example above:
[[ for i in range(10): { ]]
[[=i]]
[[ } ]]
|
produces the following indented Python code:
for i in range(10):
response.writeStatic(' ')
response.writeExpr(i)
response.writeStatic('\n')
|
Without the braces, the code produced would be unindented and, in this case,
also invalid:
for i in range(10):
response.writeStatic(' ')
response.writeExpr(i)
response.writeStatic('\n')
|
Note how the indentation of the expression does not affect the indentation of
the Python code that is produced; it merely changes the number of spaces in
the writeStatic string. Also note that unbalanced
open and close braces within a single tag are allowed, as in the example
above, and they modify the indentation level outside the code tag. However,
the braces must be balanced across an entire file. Remember: inside the [[ ... ]] delimiters, braces are always
required to change the indentation level.
2.5. Python Chunks
Syntax: [[\ Python chunk ]]
There are many Python users that experience anguish, disgust or dismay upon
reading the previous section: "Braces!? Give me real, indented Python!". These
intendation zealots will be more comfortable using Python chunks, which is why
Spyce supports them. Feel free to use Spyce statements or chunks
inter-changeably, as the need arises.
A Python chunk is straight Python code, and the internal indentation is
preserved. The entire block is merely outdented (or indented) as a whole, such
that the first non-empty line of the block matches the indentation level of
the context into which the chunk was placed. Thus, a Python chunk can not
affect the indentation level outside its scope, but internal indentation is
fully respected, relative to the first line of code, and braces ({, }) are not required, nor
expected for anything but Python dictionaries. Since the first line of code is
used as an indentation reference, it is recommended that the start delimeter
of the tag (i.e. the [[\) be placed on its own
line, above the code chunk, as shown in the following example:
[[\
def printHello(num):
for i in range(num):
response.write('hello<br>')
printHello(5)
]]
|
Naturally, one should not use braces here for purposes of indentation,
only for Python dictionaries. Additional braces will merely generate Python
syntax errors in the context of chunks. To recap: a Python statement tag
should contain braced Python; A Python chunk tag should contain regular
indented Python.
2.6. Python Class Chunks
Syntax: [[! Python class chunk ]]
Behind the scenes, your Spyce files are compiled into a class called spyceImpl. Your Spyce script runs in a method of this class
called spyceProcess. Class chunks allow you to
specify code to be placed inside the class, but outside the main method,
analogously to the "<%!" token in JSP code. (If you would like to
see your Spyce file in compiled Python form, use the following command-line:
spyce.py -c myfile.spy.)
This is primarily useful when defining active
handlers without using a separate .py file: active handlers are the first
thing that the spyceProcess calls, even before any "python chunks."
For a handler callback to be visible at this stage, it needs to be defined
at the class level. Class chunks to the rescue:
|
examples/handlerintro.spy
|
[[!
def calculate(self, api, x, y):
self.result = x * y
]]
<spy:parent title="Active Handler example" />
<f:form>
<f:text name="x:float" default="2" label="first value" />
<f:text name="y:float" default="3" label="second value" />
<f:submit handler="self.calculate" value="Multiply" />
</f:form>
<p>
Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
</p>
| |
Run this code
|
2.7. Python Expressions
Syntax: [[= expression ]]
The contents of an expression tag is a Python expression. The result of that
expression evaluation is printed using the its string representation.
The Python object None is special cased to output as the empty string
just as it is in the Python interactive shell. This is almost always
more convenient when working with HTML. (If you really want a literal
string 'None' emitted instead, use response.write in a statement or chunk.)
The Spyce transform module, can
pre-processes this result, to assist with mundane tasks such as ensuring that
the string is properly HTML-encoded, or formatted.
2.8. Spyce Lambdas
Syntax: [[spy [params] : spyce lambda code ]]
or: [[spy! [params] : spyce lambda code ]]
A nice feature of Spyce is that Spyce scripts are first-class members of the
language. In other words, you can create a Spyce lambda (or function) in any
of the Spyce Python elements (statements, chunks and expressions). These can
then be invoked like regular Python functions, stored in variables for later
use, or be passed around as paramaters. This feature is often very useful for
templating (example shown below), and can also be used to implement more
esoteric processing functionality, such as internationalization, multi-modal
component frameworks and other kinds of polymorphic renderers.
It is instructive to understand how these functions are generated. The [[spy ... : ... ]] syntax is first
translated during compilation into a call to the define() function of the spylambda module. At runtime, this
call compiles the Spyce code at the point of its definition, and returns a
function. While the invocation of a Spyce lambda is reasonably efficient, it
is certainly not as fast as a regular Python function invocation. The
spycelambda can be memoized (explained in the spylambda module section) by using
the [[spy! ... : ... ]]
syntax. However, even with this optimization one should take care to use
Python lambdas and functions when the overhead of Spyce parsing and invocation
is not needed.
Note that Spyce lambdas do not currently support nested variable scoping, nor
default parameters. The global execution context (specifically, Spyce modules)
of the Spyce lambda is defined at the point of its execution.
|
examples/spylambda.spy
|
[[\
# table template
table = [[spy! title, data:
<table>
<tr>
[[for cell in title: {]]
<td><b>[[=cell]]</b></td>
[[}]]
</tr>
[[for row in data: {]]
<tr>
[[for cell in row: {]]
<td>[[=cell]]</td>
[[}]]
</tr>
[[}]]
</table>
]]
# table information
title = ['Country', 'Size', 'Population', 'GDP per capita']
data = [
[ 'USA', '9,158,960', '280,562,489', '$36,300' ],
[ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
[ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
]
]]
[[-- emit web page --]]
<html><body>
[[ table(title, data) ]]
</body></html>
| |
Run this code
|
2.9. ASP/JSP syntax
Finally, due to popular demand, because of current editor support and people
who actually enjoy pains in their wrists, the Spyce engine will respect
ASP/JSP-like delimeters. In other words, it will also recognize the following
syntax:
The two sets of delimeters may be used interchangeably within the same file,
though for the sake of consistency this is not recommended.
3. RUNTIME
Having covered the Spyce language syntax, we now move to describing the
runtime processing. Each time a request comes in, the cache of compiled Spyce
files is checked for the compiled version of the requisite Spyce file. If one
is not found, the Spyce file is quickly read, transformed, compiled and cached
for future use.
The compiled Spyce is initialized, then processed, then finalized. The
initialization consists of initializing all the Spyce modules. The Spyce file
is executed top-down, until the end is reached or an exception is thrown,
whichever comes first. The finalization step then finalizes each module in
reverse order of initialization, and any buffered output is automatically
flushed.
3.1. Exceptions
The Spyce file is executed top-down, until the end of the file is reached, a
valued is returned, or an exception is thrown,
whichever comes first. If the code terminates via an unhandled exception, then
it is caught by the Spyce engine. Depending on the exception type, different
actions are taken:
- spyceDone can be raised at any time to stop the Spyce processing
(without error) at that point. It is often used to stop further output, as
in the example below that emits a binary image file. The spyceDone
exception, however, is more useful for modules writers. In regular Spyce
code one could simply issue a return statement,
with the same effect.
- spyceRedirect is used by the
redirect module. It causes the
Spyce engine to immediately redirect the request to another Spyce file
internally. Internally means that we do not send back a redirect to
the browser, but merely clear the output buffer and start processing a new
script.
- All other exceptions that occur at runtime will be processed via
the Spyce error module. This
module will emit a default error message, unless the user has installed some
other error handler.
Note that non-runtime exceptions, such as exceptions caused by compile errors,
missing files, access restrictions and the like, are handled by the server.
The default server error handler
can be configured via the server configuration file.
|
examples/gif.spy
|
[[.import name=include ]]
[[\
# Spyce can also generate other content types
# The following code displays the Spyce logo
response.setContentType('image/gif')
import os.path, spyce
path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
response.write(include.dump(path, 1))
raise spyceDone
]]
| |
Run this code
|
3.2. Code Transformation
While the minutia of the code transformation that produces Python code from
the Spyce sources is of no interest to the casual user, it has some slight,
but important, ramifications on certain aspects of the Python language
semantics when used inside a Spyce file.
The result of the Spyce compilation is some Python code, wherein the majority
of the Spyce code actually resides in a single function called
spyceProcess. If you are curious to see the result of a Spyce
compilation, execute: "spyce -c".
It follows from the compilation transformation that:
- Any functions defined within the Spyce file are actually nested
functions within the spyceProcess function.
- The use of global variables within Spyce code is not supported,
but also not needed. If nested scoping is available (Python versions
>2.1) then these variables will simply be available. If not, then you
will need to pass variables into functions as default parameters, and will
not be able to update them by value (standard Python limitations). It is
good practice to store constants and other globals in a single class, or to
to place them in a single, included file, or both.
The global Spyce namespace is reserved for special variables, such as
Spyce and Python modules. While the use of the keyword global is not explicitly checked, it will pollute this
space and may result in unexpected behaviour or runtime errors.
- The lifetime of variables is the duration of a request. Variables with
lifetimes longer than a single request can be stored using the pool module.
3.3. Dynamic Content
The most common use of Spyce is to serve dynamic HTML content, but it should
be noted that Spyce can be used as a general purpose text engine. It can be
used to generate XML, text and other output, as easily as HTML. In fact, the
engine can also be used to generate dynamic binary data, such as images, PDF
files, etc., if needed.
The Spyce engine can be installed
in a number of different configurations that can produce dynamic output.
Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is
slower, since a new engine must be created for each request. See the
configuration section for details.
3.4. Static Content
A nice feature of Spyce is that it can be invoked both from within a web
server to process a web request dynamically and also from the command-line.
The processing engine itself is the same in both cases. The command-line
option is actually just a modified CGI client, and is often used to
pre-process static content, such as this manual.
Some remarks regarding command-line execution specifics are in order. The
request and response objects for a command-line request are connected to
standard input and output, as expected. A minimal CGI-like environment is
created among the other shell environment variables. Header and cookie lookups
will return None and the engine will accept input on stdin for POST
information, if requested. There is also no compiler cache, since the process
memory is lost at the end of every execution.
Most commonly, Spyce is invoked from the command-line to generate static .html
ouput. Spyce then becomes a rather handy and powerful .html preprocessing
tool. It was used on this documentation to produce the consistent headers and
footers, to include and highlight the example code snippets, etc...
The following makefile rule comes in handy:
%.html: %.spy
spyce -o $@ $<
|
3.5. Command line
The full command-line syntax is:
Spyce 2.1
Command-line usage:
spyce -c [-o filename.html] <filename.spy>
spyce -w <filename.spy> <-- CGI
spyce -O filename(s).spy <-- batch process
spyce -l [-d file ] <-- proxy server
spyce -h | -v
-h, -?, --help display this help information
-v, --version display version
-o, --output send output to given file
-O send outputs of multiple files to *.html
-c, --compile compile only; do not execute
-w, --web cgi mode: emit headers (or use run_spyceCGI.py)
-q, --query set QUERY_STRING environment variable
-l, --listen run in HTTP server mode
-d, --daemon run as a daemon process with given pidfile
--conf [file] Spyce configuration file
To configure Apache, please refer to: spyceApache.conf
For more details, refer to the documentation.
http://spyce.sourceforge.net
Send comments, suggestions and bug reports to <rimon-AT-acm.org>.
|
3.6. Configuration
Since there are a variety of very different
installation
alternatives for the Spyce
engine, effort has been invested in consolidating all the various runtime
configuration options. By default, the Spyce engine will search for a file
called spyceconf.py in its installation directory. An alternative file
location may be specified via the --conf command-line option.
The spyce configuration file is a valid python module; any python code
may be used. To avoid duplication, we recommend starting with
"from spyceconf import *" and
override only select settings. One thing you cannot do from the config module
is access the Spyce server object spyce.getServer(),
since it has not been initialized yet.
You may access the loaded configuration module from Spyce scripts and from
Python modules using the config attribute of the
server object. Or, simply as the spyceConfig module,
regardless of it actual file name. For example:
|
examples/config.spy
|
[[\
import spyceConfig
home = spyceConfig.SPYCE_HOME
]]
[[= home ]]
| |
Run this code
|
Below is the configuration file that this server is running. The length of the
file is primarily due to the thoroughness of the comments:
import os, sys
import spycePreload
SPYCE_HOME = spycePreload.guessSpyceHome()
path = [os.path.join(SPYCE_HOME, 'modules'), os.path.join(SPYCE_HOME, 'contrib', 'modules')]
path.append(os.path.join(SPYCE_HOME, 'tags'))
path.append(os.path.join(SPYCE_HOME, 'contrib', 'tags'))
if os.environ.has_key('SPYCE_PATH'):
path += os.environ['SPYCE_PATH'].split(os.pathsep)
originalsyspath = list(sys.path)
sys.path.extend(path)
globaltags = [
('core', 'core.py', 'spy'),
('form', 'form.py', 'f'),
(None, 'render.spi', 'render'),
]
defaultparent = "/parent.spi"
import error
errorhandler = error.serverHandler
pageerrortemplate = ('string', 'error', 'defaultErrorTemplate')
cache = 'memory'
check_mtime = True
debug = False
globals = {}
imports = []
root = os.path.join(SPYCE_HOME, 'www')
sys.path.append(root)
import tempfile
tmp = tempfile.gettempdir()
validation_render = 'render:validation'
from sqlalchemy.ext.sqlsoup import SqlSoup
try:
db = SqlSoup('sqlite:///www/demos/to-do/todo.db')
except:
db = None
import session
session_store = session.DbmStore(tmp)
session_path = '/'
session_expire = 24 * 60 * 60
def nevervalidator(login, password):
return None
def testvalidator(login, password):
if login == 'spyce' and password == 'spyce':
return 2
return None
login_defaultvalidator = testvalidator
from _coreutil import FileStorage
login_storage = FileStorage(os.path.join(SPYCE_HOME, 'www', 'login-tokens'))
login_render = 'render:login'
loginrequired_render = 'render:login_required'
indexFiles = ['index.spy', 'index.html']
minthreads = 5
maxthreads = 10
maxqueuesize = 50
check_modules_and_restart = True
ipaddr = ''
port = 8000
adminport = None
mime = [os.path.join(SPYCE_HOME, 'spyce.mime')]
www_handlers = {
'spy': 'spyce',
'/': 'directory',
None: 'dump'
}
cgi_allow_only_redirect = False
param_filters = []
file_filters = []
|
3.7. Server utilities
Like any application server, the Spyce server provides several facilities
that can aid development.
3.7.1. The Spyce scheduler
Spyce provides a scheduler that allows you to easily define tasks to
run at specified times or intervals inside the Spyce server. This
allows your tasks to leverage the tools Spyce gives you, as well as
any global data your application maintains within Spyce, such as
cached data or database connection pools. This also has the advantage
(over, say, crontab entries) of keeping your application self-contained,
making it easier to deploy changes from a development machine to production.
The Spyce scheduler is currently only useful if you are running
in webserver mode. If you run under mod_python, CGI, or FastCGI,
you could approximate scheduler behavior by storing tasks and checking
to see if any are overdue with every request received; this would
be an excellent project for someone wishing to get started in Spyce
development.
A module for scheduling arbitrary callables to run at given times
or intervals, modeled on the naviserver API. Scheduler runs in
its own thread; callables run in this same thread, so if you have
an unusually long callable to run you may wish to give it its own
thread, for instance,
schedule(3600, lambda: threading.Thread(target=longcallable).start())
Scheduler does not provide subsecond resolution.
Public functions are threadsafe.
Classes |
| | |
- Task
class Task |
| |
Instantiated by the schedule methods.
Instance variables:
nextrun: epoch seconds at which to run next
interval: seconds before repeating
callable: function to invoke
last: if True, will be unscheduled after nextrun
(Note that by manually setting last on a Task instance, you
can cause it to run an arbitrary number of times.) |
| |
Methods defined here:
- __init__(self, firstrun, interval, callable, once)
- __repr__(self)
| |
|
examples/scheduling.py
|
import spyce, scheduler
def delete_unsubmitted():
db = spyce.SPYCE_GLOBALS['dbpool'].connection()
sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"
db.execute(sql)
scheduler.schedule_daily(00, 10, delete_unsubmitted)
|
3.7.2. spyceUtil
Most of the spyceUtil module is interesting only to internal operations,
but several functions are more generally applicable:
- url2file( url, relativeto=None )
Returns the filesystem path of the file represented by url, relative
to a given path. For example, url2file('/index.spy') or
url2file('img/header.png', request.filename()).
- exceptionString( )
Every python programmer writes this eventually: returns a string containing
the description and stacktrace for the most recent exception.
3.8. Modules
The Spyce language, as described above, is simple and small. Most
functionality is provided at runtime through Spyce modules and Python modules.
The standard Spyce modules are documented here; some other modules are
also distributed in the contrib/ directory.
Non-implicit modules are
imported using the Spyce [[.import]] directive. Python modules are
imported using the Python import keyword. Remember that
modules need to have the same read permissions as regular files that you
expect the web server to read.
Modules may be imported with a non-default name using the as attribute
to the .import directive. This is discouraged for standard Spyce modules; the
session module, for example, expects to find or
otherwise load a module named cookie in the Spyce environment.
Once included, a Spyce module may be accessed anywhere in the Spyce code.
3.8.1. DB (implicit)
Spyce integrates an advanced database module to make reading and modifying
your data faster and less repetitive.
Just initialize the db reference in your Spyce config file following
the examples given there, and you're all set.
The general idea is, db.tablename represents the tablename table
in your database, and provides hooks for reading and modifying data in that
table in pure Python, no SQL required. We find this gives 90% of the benefits
of an object-relational mapping layer, without making developers learn another
complex tool. And since the Spyce db module is part of
SQLAlchemy, probably the most advanced
database toolkit in the world, the full ORM approach is available to those
who want it.
Here's a quick example of reading
and inserting data from a table called todo_lists:
|
examples/db.spy
|
<spy:parent title="To-do demo" />
[[!
def list_new(self, api, name):
if api.db.todo_lists.selectfirst_by(name=name):
raise HandlerError('New list', 'a list with that description already exists')
api.db.todo_lists.insert(name=name)
api.db.flush()
]]
(This is an self-contained example using the same database as the
<a href=/demos/to-do/index.spy>to-do demo</a>.)
<h2>To-do lists</h2>
[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
<spy:ul data="[L.name for L in lists]" />
<h2>New list</h2>
<f:form>
<f:submit value="New list" handler=self.list_new />:
<f:text name=name value="" />
</f:form>
| |
Run this code
|
Full SqlSoup documentation follows.
Loading objects
Loading objects is as easy as this:
>>> users = db.users.select()
>>> users.sort()
>>> users
[MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0),
MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]
Of course, letting the database do the sort is better (".c" is short for ".columns"):
>>> db.users.select(order_by=[db.users.c.name])
[MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
Field access is intuitive:
>>> users[0].email
u'student@example.edu'
Of course, you don't want to load all users very often. The common case is to
select by a key or other field:
>>> db.users.selectone_by(name='Bhargan Basepair')
MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)
Select variants
All the SqlAlchemy Query select variants are available.
Here's a quick summary of these methods:
- get(PK): load a single object identified by its primary key (either a scalar, or a tuple)
- select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by.
- select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options.
- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected.
- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected.
- count, count_by: returns an integer count of the rows selected.
See the SqlAlchemy documentation for details:
- general info and examples
- details on constructing WHERE clauses
Modifying objects
Modifying objects is intuitive:
>>> user = _
>>> user.email = 'basepair+nospam@example.edu'
>>> db.flush()
(SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so
multiple updates to a single object will be turned into a single UPDATE
statement when you flush.)
To finish covering the basics, let's insert a new loan, then delete it:
>>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
>>> db.flush()
>>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
>>> db.delete(loan)
>>> db.flush()
You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more,
this time using the loans table's delete method. (For SQLAlchemy experts:
note that no flush() call is required since this
delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules
apply here as to the select methods:
>>> db.loans.insert(book_id=book_id, user_name=user.name)
MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
>>> db.flush()
>>> db.loans.delete(db.loans.c.book_id==2)
You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2:
>>> db.loans.update(db.loans.c.book_id==2, book_id=1)
>>> db.loans.select_by(db.loans.c.book_id==1)
[MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
Joins
Occasionally, you will want to pull out a lot of data from related tables all at
once. In this situation, it is far
more efficient to have the database perform the necessary join. (Here
we do not have "a lot of data," but hopefully the concept is still clear.)
SQLAlchemy is smart enough to recognize that loans has a foreign key
to users, and uses that as the join condition automatically.
>>> join1 = db.join(db.users, db.loans, isouter=True)
>>> join1.select_by(name='Joe Student')
[MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
You can compose arbitrarily complex joins by combining Join objects with
tables or other joins.
>>> join2 = db.join(join1, db.books)
>>> join2.select()
[MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]
If you join tables that have an identical column name, wrap your join with "with_labels",
and all the columns will be prefixed with their table name:
>>> db.with_labels(join1).select()
[MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
users_password='student',users_classname=None,users_admin=0,
loans_book_id=1,loans_user_name='Joe Student',
loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
Advanced usage
You can access the SqlSoup's engine attribute to compose SQL directly.
The engine's execute method corresponds
to the one of a DBAPI cursor, and returns a ResultProxy that has fetch methods
you would also see on a cursor.
>>> rp = db.engine.execute('select name, email from users order by name')
>>> for name, email in rp.fetchall(): print name, email
Bhargan Basepair basepair+nospam@example.edu
Joe Student student@example.edu
You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details.
You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively.
3.8.2. Request (implicit)
The request module is loaded implicitly into every Spyce environment.
The spyce configuration file gives two lists that affect the request module:
param_filters and file_filters. param_filters is a list of functions to run
against the GET and POST variables in the request; file filters is the same,
only for files uploaded. Each function will be passed the request module
and a dictionary when it is called; each will be called once for GET and once for POST
with each new request.
These hooks exist because the request dictionaries should not be modified
in an ad-hoc manner; these allow you to set an application-wide policy
in a well-defined manner. You might, for instance, disallow all file uploads
over 1 MB.
Here's an example that calls either Html.clean
or Html.escape (not shown) to ensure that no potentially harmful html can
be injected in user-editable areas of a site:
|
examples/filter.py
|
def htmlFilter(request, d):
try:
toClean = request._post['__htmlfields'][0].split(',')
except KeyError:
toClean = []
for key in d:
if key in toClean:
d[key] = [Html.clean(s) for s in d[key]]
else:
d[key] = [Html.escape(s) for s in d[key]]
|
The request module provides the following methods:
- login_id:
Returns the id generated by your validator function if the user has logged in
via spy:login or spy:login_required, or None if the user is unvalidated. (See the
core tag library for details on
the login tags.)
- uri( [component] ):
Returns the request URI, or some
component thereof. If the optional component parameter is specified,
it should be one of the following strings:
'scheme',
'location',
'path',
'parameters',
'query' or
'fragment'.
- method():
Returns request method type (GET, POST,
...)
- query():
Returns the request query string
- get( [name], [default], [ignoreCase] ):
Returns request GET
information. If name is specified then a single list of values is
returned if the parameter exists, or default, which defaults to an
empty list,
if the parameter does not exist. Parameters without values are skipped,
though empty string values are allowed. If name is omitted, then a
dictionary of lists is returned. If ignoreCase is true, then the
above behaviour is performed in a case insensitive manner (all parameters
are treated as lowercase).
- get1( [name], [default], [ignoreCase] ):
Returns request GET
information, similarly to (though slightly differently from) the function
above. If name is specified then a single string is returned if the
parameter exists, or default, which default to None, if the parameter
does not exist. If there is more than one value for a parameter, then only
one is returned. Parameters without values are skipped, though empty string
values are allowed. If name is omitted, then a dictionary of strings is
returned. If the optional ignoreCase flag is true, then the above
behaviour is performed in a case insensitive manner (all parameters are
treated as lowercase).
- post( [name], [default], [ignoreCase] ):
Returns request
POST information. If name is specified then a single list of values
is returned if the parameter exists, or default, which defaults to
an empty list, if the parameter does not exist. Parameters without values are
skipped, though empty string values are allowed. If name is omitted, then a
dictionary of lists is returned. If ignoreCase is true, then the
above behaviour is performed in a case insensitive manner (all parameters
are treated as lowercase). This function understands form information
encoded either as 'application/x-www-form-urlencoded' or
'multipart/form-data'. Uploaded file parameters are not included in this
dictionary; they can be accessed via the file method.
- post1( [name], [default], [ignoreCase] ):
Returns request
POST information, similarly to (though slightly differently from) the
function above. If name is specified then a single string is returned
if the parameter exists, or default, which defaults to None, if the
parameter does not exist. If there is more than one value for a parameter,
then only one is returned. Parameters without values are skipped, though
empty string values are allowed. If name is omitted, then a dictionary of
strings is returned. If the optional ignoreCase flag is true, then
the above behaviour is performed in a case insensitive manner (all
parameters are treated as lowercase). This function understands form
information encoded either as 'application/x-www-form-urlencoded' or
'multipart/form-data'. Uploaded file parameters are not included in this
dictionary; they can be accessed via the file method.
- file( [name], [ignoreCase] ):
Returns files POSTed in the
request. If name is specified then a single cgi.FieldStorage class is
returned if such a file parameter exists, otherwise None. If name is
omitted, then a dictionary of file entries is returned. If the optional
ignoreCase flag is true, then the above behaviour is performed in a
case insensitive manner (all parameters are treated as lowercase). The
interesting fields of the FieldStorage class are:
- name: the field name, if specified; otherwise None
- filename: the filename, if specified; otherwise None; this is
the client-side filename, not the filename in which the content is stored
- a temporary file you don't deal with
- value: the value as a string; for file uploads, this
transparently reads the file every time you request the value
- file: the file(-like) object from which you can read the data;
None if the data is stored a simple string
- type: the content-type, or None if not specified
- type_options: dictionary of options specified on the
content-type line
- disposition: content-disposition, or None if not specified
- disposition_options: dictionary of corresponding options
- headers: a dictionary(-like) object (sometimes rfc822.Message
or a subclass thereof) containing *all* headers
- __getitem__( key ):
The request module can be used as a
dictionary: i.e. request['foo']. This method first calls the get1() method,
then the post1() method and lastly the file() method trying to find the
first non-None value to return. If no value is found, then this method
returns None. Note: Throwing an exception seemed too strong a semantics, and
so this is a break from Python. One can also iterate over the request
object, as if over a dictionary of field names in the get1 and post1
dictionaries. In the case of overlap, the get1() dictionary takes
precedence.
- getpost( [name], [default], [ignoreCase] ):
Using given
parameters, return get() result if not None, otherwise return post() result
if not None, otherwise default.
- getpost1( [name], [default], [ignoreCase] ):
Using given
parameters, return get1() result if not None, otherwise return post1()
result if not None, otherwise default.
- postget( [name], [default], [ignoreCase] ):
Using given
parameters, return post() result if not None, otherwise return get() result
if not None, otherwise default.
- postget1( [name], [default], [ignoreCase] ):
Using given
parameters, return post1() result if not None, otherwise return get1()
result if not None, otherwise default.
- env( [name], [default] ):
Returns a dictionary with CGI-like
environment information of this request. If name is specified then a
single entry is returned if the parameter exists, otherwise default,
which defaults to None, if omitted.
- getHeader( [type] ):
Return a specific header sent by the
browser. If optional type is omitted, a dictionary of all headers is
returned.
- filename( [path] ):
Return the Spyce filename of the request
currently being processed. If an optional path parameter is provided,
then that path is made relative to the Spyce filename of the request
currently being processed.
- stack( [i] ):
Returns a stack of files processed by the
Spyce runtime. If i is provided, then a given frame is returned,
with negative numbers wrapping from the back as per Python convention.
The first (index zero) item on the stack is the filename
corresponding to the URL originally requested. The last (index -1) item
on the stack is the current filename being processed. Items are added
to the stack by includes,
Spyce lambdas, and
internal redirects.
- default( value, value2 ):
(convenience method) Return
value if it is not None, otherwise return value2.
The example below presents the results of all the method calls list above. Run
it to understand the information available.
|
examples/request.spy
|
<html><body>
Using the Spyce request object, we can obtain
information sent along with the request. The
table below shows some request methods and their
return values. Use the form below to post form
data via GET or POST. <br>
<hr>
[[-- input forms --]]
<form action="[[=request.uri('path')]]" method=get>
get: <input type=text name=name>
<input type=submit value=ok>
</form>
<form action="[[=request.uri('path')]]" method=post>
post: <input type=text name=name>
<input type=submit value=ok>
</form>
<hr>
[[-- tabulate response information --]]
<table border=1>
<tr>
<td><b>Method</b></td>
<td><b>Return value</b></td>
</tr>
[[ for method in ['uri()', 'uri("path")',
'uri("query")', 'method()','query()',
'get()','get1()', 'post()','post1()',
'getHeader()','env()', 'filename()']: {
]]
<tr>
<td valign=top>request.[[=method]]</td>
<td>[[=eval('request.%s' % method)]]</td>
</tr>
[[ } ]]
</table>
</body></html>
| |
Run this code
|
Lastly, the following example shows how to deal with uploaded files.
|
examples/fileupload.spy
|
[[\
if request.post('ct'):
response.setContentType(request.post1('ct'))
if request.file('upfile')!=None:
response.write(request.file('upfile').value)
else:
print 'file not properly uploaded'
raise spyceDone
]]
<html><body>
Upload a file and it will be sent back to you.<br>
[[-- input forms --]]
<hr>
<table>
<form action="[[=request.uri('path')]]" method=post
enctype="multipart/form-data">
<tr>
<td>file:</td>
<td><input type=file name=upfile></td>
</tr><tr>
<td>content-type:</td>
<td><input type=text name=ct value="text/html"></td>
</tr><tr>
<td><input type=submit value=ok></td>
</tr>
</form>
</table>
</body></html>
| |
Run this code
|
3.8.3. Response (implicit)
Like the request module, the response module is also loaded implicitly into every
Spyce environment. It provides the following methods:
- write( string ):
Sends a string to the client. All
writes are buffered by default and sent at the end of Spyce processing to
allow appending headers, setting cookies and exception handling. Note that
using the print statement is often easier, and
stdout is implicitly redirected
to the browser.
- writeln( string ):
Sends a string to the client, and
appends a newline.
- writeStatic( string ):
All static HTML strings are
emitted to the client via this method, which (by default) simply calls
write(). This method is not commonly invoked by the user.
- writeExpr( object ):
All expression results are emitted to
the client via this method, which (by default) calls write() with the str()
of the result object. This method is not commonly invoked by
the user.
- clear( ): Clears the output buffer.
- flush( ): Sends buffered output to the client immediately. This
is a blocking call, and can incur a performance hit.
- setContentType( contentType ):
Sets the MIME content
type of the response.
- setReturnCode( code ):
Set the HTTP return code for this
response. This return code may be overriden if an error occurs or by
functions in other modules (such as redirects).
- addHeader( type, data, [replace] ):
Adds the header line
"type: data" to the outgoing response. The
optional replace flag determines whether any previous headers of the
same type are first removed.
- unbuffer():
Turns off buffering on the output stream. In
other words, each write is followed by a flush(). An unbuffered output
stream should be used only when sending large amounts of data (ie. file
transfers) that would take up server memory unnecessarily, and involve
consistently large writes. Note that using an unbuffered response stream
will not allow the output to be cleared if an exception occurs. It will also
immediately send any headers.
- isCancelled():
Returns true if it has been detected that the
client is no longer connected. This flag will turn on, and remain on, after
the first client output failure. However, the detection is best-effort, and
may never turn on in certain configurations (such as CGI) due to buffering.
- timestamp( [t] ):
Timestamps the response with an HTTP
Date: header, using the optional t
parameter, which may be either be the number of seconds since the epoch
(see Python time
module), or a properly formatted HTTP date string. If t is omitted,
the current time is used.
- expires( [t] ):
Sets the expiration time of the
response with an HTTP Expires: header, using the
optional t parameter, which may be either the number of seconds
since the epoch (see Python time
module), or a properly formatted HTTP date string. If t is omitted,
the current time is used.
- expiresRel( [secs] ):
Sets the expiration time of the
response relative to the current time with an HTTP Expires: header. The optional secs (which may
also be negative) indicates the number of seconds to add to the current time
to compute the expiration time. If secs is omitted, it defaults to zero.
- lastModified( [t] ):
Sets the last modification time of
the response with an HTTP Last-Modified: header,
using the optional t parameter, which can be either the number
of seconds since the epoch (see Python time
module), or a properly formatted HTTP date string, or None indicating the
current time. If t is omitted, this function will default to the last
modification time of the Spyce file for this request, and raise an exception
if this time can not be determined. Note that, as per the HTTP
specification, you should not set a last modification time that is beyond
the response timestamp.
- uncacheable():
Sets the HTTP/1.1 Cache-Control: and HTTP/1.0 Pragma: headers to inform clients and proxies that this
content should not be cached.
The methods are self-explanatory. One of the more interesting things that one could do is
to emit non-HTML content types. The example below emits the Spyce logo as a GIF.
|
examples/gif.spy
|
[[.import name=include ]]
[[\
# Spyce can also generate other content types
# The following code displays the Spyce logo
response.setContentType('image/gif')
import os.path, spyce
path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
response.write(include.dump(path, 1))
raise spyceDone
]]
| |
Run this code
|
3.8.4. Redirect
The redirect module allows requests to be redirected to different pages, by
providing the following methods:
- internal( uri ):
Performs an internal redirect. All
processing on the current page ends, the output buffer is cleared and
processing continues at the named uri.
The browser URI remains
unchanged, and does not realise that a redirect has even occurred during
processing.
- external( uri, [permanent] ):
Performs an external redirect
using the HTTP Location header to a new uri. Processing of the
current file continues unless you raise spyceDone,
but the content is ignored (ie. the buffer is
cleared at the end). The status of the document is set to 301 MOVED
PERMANENTLY or 302 MOVED TEMPORARILY, depending on the permanent
boolean parameter, which defaults to false or temporary. The redirect
document is sent to the browser, which requests the new relative uri.
- externalRefresh( uri, [seconds] ):
Performs an external
redirect using the HTTP Refresh header a new uri. Processing of the
current file continues, and will be displayed on the browser as a regular
document. Unless interrupted by the user, the browser will request the new
URL after the specified number of seconds, which defaults to zero if
omitted. Many websites use this functionality to show some page, while a
file is being downloaded. To do this, one would show the page using Spyce,
and redirect with an externalRefresh to the download URI. Remember to set
the Content-Type on the target download file page
to be something that the browser can not display, only download.
The example below, shows the possible redirects in use:
|
examples/redirect.spy
|
[[.import name=redirect]]
<html><body>
[[ type = request['type']
url = request['url']
if url and not type: {
]]
<font color=red><b>
please select a redirect type
</b></font><br>
[[
}
if type and url: {
if type=='internal': redirect.internal(url)
if type=='external': redirect.external(url)
if type=='externalRefresh': redirect.externalRefresh(url, 3)
]] Received POST info: [[=request.post1()]] [[
}
]]
<form action="[[=request.uri('path')]]" method=post>
Redirection url:
<input type=text name=url value=hello.spy><br>
Redirection type:
<table border=0>
<tr><td>
<input type=radio name=type value=internal>
internal
</td></tr>
<tr><td>
<input type=radio name=type value=external>
external
</td></tr>
<tr><td>
<input type=radio name=type value=externalRefresh>
externalRefresh (3 seconds)
</td></tr>
</table>
<input type=submit value=redirect>
</form>
</body></html>
| |
Run this code
|
3.8.5. Cookie
This module provides cookie functionality. Its methods are:
- get( [key] ):
Return a specific cookie string sent by the
browser. If the optional cookie key is omitted, a dictionary of all
cookies is returned. The cookie module may also be accessed as an
associative array to achieve the same result as calling: namely, cookie['foo'] and cookie.get('foo') are equivalent.
- set( key, value, [expire], [domain], [path], [secure] ):
Sends a cookie to the browser. The cookie will be sent back on
subsequent requests and can be retreived using the get function. The
key and value parameters are required; the rest are optional.
The expire parameter determines how long this cookie information will
remain valid. It is specified in seconds from the current time. If expire is
omitted, no expiration value will be provided along with the cookie header,
meaning that the cookie will expire when the browser is closed. The
domain and path parameters specify when the cookie will get
sent; it will be restricted to certain document paths at certain domains,
based on the cookie standard. If these are omitted, then path and/or domain
information will not be sent in the cookie header. Lastly, the secure
parameter, which defaults to false if omitted, determines whether the cookie
information can be sent over an HTTP connection, or only via HTTPS.
- delete( key ):
Send a cookie delete header to the browser to
delete the key cookie. The same may be achieved by: del cookie[key].
The example below shows to manage browser cookies.
|