Welcome to TinCan’s documentation!¶
Introduction¶
TinCan is a Python web framework, based on the Bottle micro-framework and the Chameleon templating engine. TinCan is based on the “code-behind” paradigm popularized by ASP.Net, convention over configuration, and separation of presentation and back-end logic.
Philosophy¶
Be WSGI Compliant¶
It’s the default standard for Python web frameworks. Only a fool wouldn’t support it.
Don’t Reinvent the Wheel¶
TinCan is mostly Bottle and Chameleon. Why rewrite the basic guts of an application server, when they’ve already been written many times? Why write a new templating engine, when there’s existing ones out there? TinCan tries to leverage existing software as much as possible.
Keep It Simple, Stupid¶
I wanted to get on with the business of actually using TinCan to do things, so I didn’t clutter it up with extra features that would be difficult to implement. (That’s why there’s currently no way to choose templating engines other than Chameleon. I tried to allow that, and it proved overly complex. Maybe some day.)
Don’t Be Gratuitously Bossy¶
TinCan is not tightly coupled with an ORM, and never will be. I personally dislike ORM’s, and I am not alone. Moreover, not all web applications need to use an SQL database in the first place.
This is of no loss, even for those who prefer to use an ORM, since
it is easy enough use an ORM with TinCan (just add it to the script you make
to load the webapp, and include the ORM in the webapp’s config
dictionary).
Code-Behind¶
It’s maligned by many, but I personally like it, and it does not necessarily mean repudiating the MVC paradigm (see MVC Comes in Different Forms).
Don’t Repeat Yourself¶
One of the things I dislike about most Python web frameworks is that I’m always tiresomely repeating “this is route foo, serviced by controller foo (which defines variables bar, baz and blort), which uses template foo (which renders variables bar, baz and blort).” This is completely unnecessary; the relationships should be obvious by virtue of the similar names. The framework should figure it out and just “do the right thing” by default.
MVC Comes in Different Forms¶
If a programmer shows a little discipline, the template page will describe how data is presented to the user, and how he or she can interact with it. A view by any other name is still a view. The code-behind will tie that view into the underlying business logic, and bridge the gap between the two. A controller by any other name is still a controller.
If a programmer don’t show discipline, s/he can clutter templates up with bits of Python code that represent controller logic, and clutter the controller up with bits of presentation details. Of course s/he can. That’s not the framework’s fault; that’s the programmer’s fault. Bad code can be written in any language, for any platform.
The Alternative is PHP or CGI, not Django or Flask¶
I’m not the only one out there who doesn’t like all the repeating yourself that most MVC frameworks make you do. There’s still a lot of developers out there writing CGI scripts or using PHP, because CGI and PHP don’t make you do all the busywork that most frameworks do. (Can they be blamed? It really feels silly when all you want is to get a quick, small site up.)
That’s a pity, because CGI is hideously inefficient and PHP is, well, just plain hideous. (A root namespace with literally thousands of builtins cluttering it up, builtins with no consistent naming convention? Ugh!)
Hopefully, by making it easier for people to code efficient web sites using a well-designed language that’s easy to learn, I’ll encourage more people to create efficient sites in a well-designed language. If not, well, at least I’ll enable myself to do so.
Meaningful Error Messages (If You Want Them)¶
Bottle can be notoriously tight-lipped about what’s going wrong if and when things go wrong. (And when you’re developing and debugging code, things go wrong). TinCan tries as hard as possible to catch errors and log them. Enable logging, and when you see a 500 error, you will almost always have a log file with a traceback in it helping to pinpoint the cause.
(Note that WSGI provides no default means for logging errors, so if you’re running TinCan via WSGI, it will be totally silent unless you pass tincan.launch
a suitable logger. The simple test server created by the launch
command always logs to standard error.)
Essential Characteristics¶
- Peaceful Coexistence
A TinCan webapp is a Bottle webapp. You can also do things the Bottle way… in the same webapp! Bottle-style webapps have access to the same Chameleon templating engine that TinCan uses.
- No Configuration
Just make a tree of appropriately-named files, and TinCan will figure it all out. The file tree defines the routes, much like in a web server.
- Header Directives Can Get You a Lot
Need to have one controller associated with multiple views? TinCan can do that! See
#python
in the Header Directives section.
Tutorial¶
First Step: Create a Directory for Your Webapp¶
TinCan is a lot like a web server; it serves a directory of files. There is
one catch, though: that directory must contain a WEB-INF
subdirectory.
It’s OK if WEB-INF
ends up being empty (as it can, for a really simple
webapp). If WEB-INF
is missing, TinCan will conclude the directory
doesn’t contain a webapp and will refuse to serve it. (This is a deliberate
feature, added to prevent serving directories that don’t contain webapps.)
So the first thing we must do is create a directory to hold our new webapp.
Let’s call it demo
:
$ mkdir demo demo/WEB-INF
Adding Some Content¶
Use your favorite editor to create a file called hello.pspx
, and insert
the following content into it:
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello, World</h1>
<p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
</body>
</html>
Save the file to disk, and start up a test server:
$ launch demo
You should see something like the following:
adding route: /hello.pspx (GET)
Bottle v0.12.16 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
If you already have a server listening on port 8080, this naturally won’t work.
Use the --port
option to tell launch
to listen on another port, e.g.:
$ launch --port=8000
When you have a working test server, point your browser at
http://localhost:8080/hello.pspx
and you should see something like the following:
What just happened?
TinCan found the
hello.pspx
file.In that file, it found no special header directives. It also found no
hello.py
file.So TinCan fell back to its default behavior, and created an instance of the
tincan.Page
class (the base class of all code-behind logic), and associated the template code inhello.pspx
with it.The
${page.request.environ['PATH_INFO']}
expression references objects found in that standard page object to return the part of the HTTP request path that is being interpreted as a route within the webapp itself.
Adding Some Content of Our Own¶
Use Control-C to force the test server to quit, then add two more files. First,
a template, hello2.pspx
:
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello Again, World</h1>
<p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
<p>The current time on the server is ${time}.</p>
</body>
</html>
Next, some code-behind in hello2.py
:
import time
import tincan
class Hello2(tincan.Page):
def handle(self):
self.time = time.ctime()
This time, when you launch the test server, you should notice a second route being created:
$ ./launch demo
adding route: /hello.pspx (GET)
adding route: /hello2.pspx (GET)
Bottle v0.12.16 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
When you visit the new page, you should see something like this:
What new happened this time?
You created a code-behind file with the same name as its associated template (only the extensions differ).
Because the file names match, TinCan deduced that the two files were related, one containing the code-behind for the associated template.
TinCan looked in the code-behind file for a something subclassing
tincan.Page
, and created an instance of that class, complete with the standardrequest
andresponse
instance variables.It then called the
handle(self)
method of that class, which defined yet another instance variable.Because that instance variable did not start with an underscore, TinCan considered it to be exportable, and exported it as a template variable.
Suppose you had written the following code-behind instead:
from time import ctime
from tincan import Page
class Hello2(Page):
def handle(self):
self.time = ctime()
Every class is a subclass of itself, so what stops TinCan from getting all confused now that there are two identifiers in this module, Page
and Hello2
, both of which are subclasses of tincan.Page
? For that matter, what if you had defined your own subclass of tincan.Page
and subclassed it further, e.g.:
from time import ctime
from mymodule import MyPage
class Hello2(MyPage):
def handle(self):
self.time = ctime()
The answer is that the code would still have worked (you might want to try the first example above just to prove it). When deciding what class to use, TinCan looks for the deepest subclass of tincan.Page it can find in the code-behind. So in a code-behind file that contains both a reference to tincan.Page
itself, and a subclass of tincan.Page
, the subclass will always “win.” Likewise, a subclass of a subclass will always “win” over a direct subclass of the parent class.
A code-behind file need not share the same file name as its associated
template. If you use the #python
header directive, you can tell TinCan
exactly which code-behind file to use. For example:
#python other.py
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello Again, World</h1>
<p>This page was called on the route ${page.request.environ['PATH_INFO']}.</p>
<p>The current time on the server is ${time}.</p>
</body>
</html>
What happens if you edit hello2.pspx
to look like the above but do not rename hello2.py
to other.py
? What happens if you make hello2.pspx
run with hello.py
(and vice versa)? Try it and see!
(Note that it is generally not a good idea to gratuitously name things inconsistently, as it makes it hard for other people — or even you, at a later time — to easily figure out what is going on.)
Error Pages¶
If you tried the exercises in the paragraph immediately above, some of them let you see what happens when something goes wrong in TinCan. What if that standard error page is not to your liking, and you want to display something customized? TinCan lets you do that. Create a file called 500.pspx
:
#errors 500
<!DOCTYPE html>
<html>
<head>
<title>${error.status_line}</title>
</head>
<body>
<h1>${error.status_line}</h1>
<p>How embarrassing! It seems there was an internal error serving
<kbd>${request.url}</kbd></p>
</body>
</html>
Now when you visit a page that doesn’t work due to server-side errors (such as a template referencing an undefined variable), you’ll see something like:
Error pages work a little differently from normal pages. They are based on subclasses of tincan.ErrorPage
. You get two standard template variables “for free:” error
, containing an instance of a bottle.HTTPError
object pertaining to the error, and request
, a bottle.HTTPRequest
object pertaining to the request that triggered the error.
It is not as common for error pages to have code-behind as it is for normal, non-error pages, but it is possible and allowed. As alluded to above, the class to subclass is tincan.ErrorPage
; such classes have a handle()
method which may define instance variables which get exported to the associated template using the same basic rules as for normal pages.
If an error page itself causes an error, TinCan (the underlying Bottle framework, actually) will ignore it and display a fallback error page. This is to avoid triggering an infinite loop.
Index Pages and Server-Side Forwards¶
Remove any #python
header directives you added to hello.pspx
and hello2.pspx
during your previous experiments and create index.pspx
:
#forward hello.pspx
That’s it, just one line! When you launch the modified webapp, you should notice a couple new routes being created:
$ launch demo
adding route: /hello.pspx (GET)
adding route: /hello2.pspx (GET)
adding route: /index.pspx (GET)
adding route: / (GET)
Bottle v0.12.16 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
And if you navigate to http://localhost:8080/
, you should see our old friend the first page we created in this tutorial.
Just like how a web server recognizes index.html
as special, and routes requests for its containing directory to it, TinCan recognizes index.pspx
as special.
#forward
creates what is known as a server-side forward. Unlike with an HTTP forward, all the action happens on the server side. The client never sees any intermediate 3xx response and never has to make a second HTTP request to resolve the URL. Any TinCan template file containing a #forward
header directive will have everything else in it ignored, and otherwise act as if it were an exact copy of the route referenced in the #forward
header.
It is permitted for a page to #forward
to another #forward
page (but you probably should think twice before doing this). #forward
loops are not permitted and attempts to create them will be rejected and cause an error message at launch time.
Form Data and Requests Other Than GET¶
As a final example, here’s a sample page, name.pspx
, that processes form data via a POST request, and which uses some Chameleon TAL to render and style the response page, call it:
#rem A page that accepts both GET and POST requests
#methods GET POST
<!DOCTYPE html>
<html>
<head>
<title>Form Test</title>
</head>
<body>
<h1>Form Test</h1>
<h2>Enter Your Name Below</h2>
<form method="post">
<input type="text" name="name"/>
<input type="submit" value="Submit"/>
</form>
<if tal:condition="message is not None" tal:omit-tag="True">
<h2 tal:content="message.subject"></h2>
<p tal:attributes="style message.style" tal:content="message.body"></p>
</if>
</body>
</html>
Here’s the code-behind for that page:
from tincan import Page
from jsdict import JSDict
class Name(Page):
def handle(self):
self._error_message = JSDict({"subject": "Error", "style": "color: red;"})
if "name" not in self.request.forms:
if self.request.method == "GET":
self.message = None
else:
self.message = self.error("This should not happen!")
else:
name = self.request.forms["name"]
if name.strip() == "":
self.message = self.error("Please enter your name above.")
else:
self.message = JSDict({
"subject": "Hello, {0}".format(name.split()[0]),
"style": None,
"body": "Pleased to meet you!"
})
def error(self, message):
self._error_message.body = message
return self._error_message
This example requires you to create a third file, in WEB-INF/lib/jsdict.py
:
class JSDict(dict):
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
self[name] = value
def __delattr__(self, name):
del self[name]
WEB-INF/lib
is the directory where webapp-specific library routines live. It gets added to sys.path
automatically when your webapp is launched.
This example introduces two new header directives, #rem
and #methods
. The former introduces a remark (comment); the latter specifies the request methods that this page will respond to. Not specifying a #methods
line is equivalent to specifying #methods GET
. When you launch the webapp after adding this new page, you shound notice TinCan announce that it is creating a route that supports both GET and POST requests for it:
$ launch demo
adding route: /hello.pspx (GET)
adding route: /hello2.pspx (GET)
adding route: /index.pspx (GET)
adding route: / (GET)
adding route: /name.pspx (GET,POST)
Bottle v0.12.16 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
Configuration¶
Basically, there isn’t very much configuration to contend with. It’s all in the files you create in your webapp’s directory tree. TinCan figures it out from them and takes it from there:
A route is created for each
.pspx
file.By default, a
.pspx
file’s code-behind is find in a.py
file with the same base name.Header directives in the
.pspx
files can be used to alter the default behavior.The
tincan.launch
call accepts optional arguments which can be used to tailor how a webapp is launched and run.
Case-Sensitive and Case-Preserving File Systems¶
A word here is necessary about case-sensitive versus case-preserving files systems. Unix and Linux systems have case-sensitive file systems; file.pspx
, File.Pspx
, and FILE.PSPX
are three completely different files. It is possible for all three files to coexist in the same directory. Windows and MacOS are case-preserving file systems; only one of the three is allowed to exist at any time, and a request for any one of those names will match the existing one.
TinCan knows about both kinds of file systems, and acts slightly differently depending on which kind the system it is being run under has. When run on a case-preserving system, FILE.PSPX
, File.Pspx
, file.pspx
, and even fIlE.pSpX
will be recognized as having the correct sort of extension to define a route and template. On a case-sensitive system, only file.pspx
will be so recognized.
It is important to note, however, that while a file system might not be case sensitive, routes are always case sensitive. A TinCan webapp under Windows that contains FILE.PSPX
will get a route created that matches only the capitalized version of the name that appears on the Windows file system. It is for this reason that it is probably best to stick to one capitalization strategy, even on non-case-sensitive systems.
Deployment¶
Bottle’s Built-In Server¶
The launch
command will simply use the WSGI server built into Bottle (built into the Python standard library, actually) to serve the routes defined by the webapp. This is a multithreaded server (see the The Multithreading Problem), which is primarily intended for debugging and light-duty use.
Static and Dynamic Routes¶
If you’re using the built-in server, one thing to be aware of is that by default, only dynamic content is served. That means, if you create pages that reference style sheet, image, or font files, all these resources will cause 404 errors when a user agent attempts to request them from the server, because no routes exist to serve such content.
If you specify the --static
option to launch
, then for every file that is not related to dynamic content being generated by TinCan, a route will be created to serve that file’s content. The HTTP Content-Type
header for such routes will be set based on the file’s extension, using the Python mimetype
library.
WSGI¶
Bottle, and by implication TinCan, support WSGI, so any web server that supports WSGI can be used to serve a TinCan webapp. This is generally to be preferred for production use.
How exactly to do this is beyond the scope of this document, but here’s an example of serving a webapp using mod_wsgi
under Apache. Note how rewrites are used to ensure that only dynamic content is served by TinCan, leaving static content to be served by Apache itself.
In the configuration file for the site in question:
WSGIScriptAlias /ti_webapp /home/davidb/webapp.wsgi
RewriteEngine on
RewriteRule ^/ti/(.*\.pspx)$ /ti_webapp/$1 [PT]
<LocationMatch "^/ti/WEB-INF/">
Order deny,allow
Deny from all
</LocationMatch>
<LocationMatch "^/ti/.*\.(py|pyc|pt)$">
Order deny,allow
Deny from all
</LocationMatch>
<Directory "/var/www/html/ti">
DirectoryIndex index.html index.pspx
</Directory>
<Directory "/home/davidb">
<Files "webapp.wsgi">
Order deny,allow
Allow from all
Require all granted
</Files>
</Directory>
The webapp.wsgi
script:
#!/usr/bin/env python3
# C o n s t a n t s
# Set the following appropriately.
TINCAN_HOME = "/home/davidb/src/tincan"
WEBAPP_HOME = "/var/www/html/ti"
LOG_HOME = "/var/log/wsgi"
LOG_NAME = "webapp"
# I m p o r t s
# First, fix up the path
import os, sys
if sys.path[0] == "":
sys.path[0] = TINCAN_HOME
else:
sys.path.insert(0, TINCAN_HOME)
# Then do the importing
import bottle
import logging
from logging.handlers import RotatingFileHandler
import tincan
# M a i n P r o g r a m
# Set up logging
logger = logging.getLogger(LOG_NAME)
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(os.path.join(LOG_HOME, LOG_NAME+".log"),
maxBytes=1048576, backupCount=5)
handler.setFormatter(
logging.Formatter(fmt="%(asctime)s - %(levelname)s: %(message)s"))
logger.addHandler(handler)
# And away we go
application, errors = tincan.launch(fsroot=WEBAPP_HOME, logger=logger,
multithread=False)
Note the bit about setting up logging. The WSGI standard provides no standard means by which to log errors. It does mention that WSGI code is strictly forbidden to write to either the standard output or the standard error stream. What this all means is that unless you explicitly tell TinCan what to do with its error messages, it has no place to log them when running via WSGI. Thus, by default, TinCan is silent under WSGI. So you almost certainly will want to create a logger and pass it to tincan.launch
as in the example above.
TinCan is not particularly chatty with its logging, preferring to only log what routes it creates when starting up and then only logging something when a truly exceptional condition happens. Logging of requests is left to the main server. Therefore, running with a logging level of INFO
will not produce an undesirably large amount of output to the logs.
The Multithreading Problem¶
Due to fundamantal aspects of the language’s design and implementation, Python isn’t that great at running multithreaded code. The global interpreter lock (GIL) lets only a single thread execute Python bytecode at any given time. Thus, a busy multithreaded WSGI server is likely to spend much of its time unable to service new, incoming requests.
The simplest workaround is to avoid using threads with Python. This is what the default prefork
execution model of mod_wsgi
does, and it is recommended that you run mod_wsgi
this way. If you do, you should set multithread=False
when calling tincan.launch
, as it will optimize memory use somewhat by maximizing the degree to which templates are shared.
That said, using threads could conceivably be useful for webapps that spend a large amount of their time doing I/O (e.g. database queries), as when a Python app is waiting on I/O, it is in the operating system kernel, and thus not actively executing Python bytecode, allowing another thread to hold the GIL.
The simple server created by the launch
command runs in a single process and thus does always use multithreading.
So far as being thread safe goes, you don’t have to much worry about it. TinCan gives each request a completely separate copy of a page’s code-behind, request, and response objects. Although templates are shared to some degree, locking is used to ensure only a single thread has access to shared template objects at any one time. In short, multithreading might cause performance issues, but I have taken pains to ensure it should not cause code to execute incorrectly.
Installing¶
There’s basically two strategies for installing a webapp to a production server: the single-directory strategy, in which one runs a server against a single directory exactly like the one you created to develop the webapp, and the two-directory strategy, in which the static and dynamic parts of a webapp are separated into separate directory trees.
The Single-Directory Strategy¶
This is arguably the simplest, since it doesn’t involve breaking a webapp into two separate directory trees at install time. If you’re serving a webapp for test purposes or limited-duty internal usage with the launch
command, this is the only strategy supported.
The rub comes in production use. It’s unwise to have a web server run with the permission to create files in the directory trees it serves. However, TinCan needs to do just that if it needs to compile a .py
file into a .pyc
file.
The solution is to run launch --compile
each time you update the webapp, before serving it. This will cause all referenced Python code that needs it to be recompiled, generating fresh .pyc
files. The production server will then see that the byte-compiled files are all newer than the source code they are based upon, and not attempt to compile anything.
The single-directory strategy is what I typically use myself, and what is used in the example Apache configuration in the WSGI section above.
The Two-Directory Strategy¶
This gets rid of the need to recompile things, at the expense of having to install the webapp into two separate directories. What you must do is:
Create an empty directory for serving static content, and point your web server at it. This directory should readable but not writeable by the server.
Create a directory for serving the dynamic content. This will be the directory that
tincan.launch
gets passed, and it must be both readable and writeable by the server.Place all the webapp’s files in the second directory, ensuring all subdirectories created are both readable and writeable by the server.
Use the
install-static
command to copy or move the static content into the first directory.
API Reference¶
The Launch Function¶
-
launch
(fsroot=None, urlroot='/', multithread=True, logger=None, encoding='utf-8', static=False)¶
Launch and return a TinCan webapp object. Returns a tuple containing two items: an instance of tincan.TinCan
and an error count. This function does not run or serve the webapp; it is the caller’s responsibility to perform one of the latter operations. This function accepts the following keyword arguments:
- fsroot
Path to the root directory of this webapp on the filesystem. If no path is specified, the current working directory will be used.
- urlroot
This defines a directory prefix that is added to all routes. It is mostly for use by the
launch
shell command; when running under WSGI this should almost always be left to default to/
.- multithread
By default, TinCan assumes that multiple threads per process are being used. Setting this parameter to
False
will result in code that fails to multithread properly, but which runs more efficiently while not multithreading. It is recommended to set this toFalse
if you are not going to be using multithreading.- logger
If set, the name of the Python
logging.Logger
object to use for logging. If not set, no messages will be logged.- encoding
Character set used by all text files in the webapp. Note that this is different from the character set that TinCan will use to serve responses; the latter is inevitably UTF-8.
- static
Whether or not to create routes to serve static content in the webapp. If you’re using the built-in server, this should probably be
True
. If you’re running via WSGI, it should probably beFalse
.
The TinCan Object¶
This is a subclass of bottle.Bottle
. It is not intended for end users to create instances of this object themselves; use the launch
function above to do that.
This class contains one extra method above and beyond the methods bottle.Bottle
objects have:
-
forward
(target)¶
Perform a programmatic, request-time, server-side redirect to the route specified by target, which may be either relative to the route issuing the redirect or an absolute route. This differs from the #forward
header directive in that this method causes a server-side redirect to be set up the time a request is processed, not the when a route is created. That makes this method both more flexible and less efficient than #forward
.
One may only forward from a normal page to another normal page; neither the source nor the target of a forward may be an error page. Attempts to create forward loops will also cause an error.
TinCan Configuration Variables¶
The config
dictionary of a TinCan
object contains tincan.fsroot
, tincan.urlroot
, tincan.logger
, and tincan.encoding
keys (in addition to the standard ones defined by WSGI and Bottle); these contain the values of the corresponding parameters passed to the launch
function that created this webapp. In general, any key starting with tincan.
is reserved for use by TinCan.
Page, BasePage, and ErrorPage¶
These classes are typically subclassed in the code-behind for a page. Any page without code-behind will get a bare Page
or ErrorPage
object associated with it, as appropriate.
-
class
tincan.
BasePage
¶
This is the parent class of all code-behind classes. All such classes contain two standard methods:
-
handle
()¶
The handle
method is called by TinCan to handle a request. No arguments are passed to it (other than the implied self
argument). By default, this is a no-op method.
-
export
()¶
This is called by TinCan to export template variables; it must return a dict
or dict
-like object.
The default implementation exports all non-hidden instance variables; moreover, it does not export attributes that define callable objects (e.g. methods). A “hidden” instance variable means any one whose name does not start with an underscore; the request
and response
instance variables of class tincan.Page
are also considered hidden. Finally, self
is exported as the page
variable.
Note that the exporting happens after header processing; thus, if there is a conflict between a template variable defined by the #load
header directive and this exporting logic, the value exported by this method will always overwrite the earlier one.
If the above exporting behavior is for some reason unsuitable, it is permitted to override this method.
-
class
tincan.
Page
¶
The parent class of all normal (non-error) pages.
-
class
tincan.
ErrorPage
¶
The parent class of all error pages.
Exceptions¶
-
class
tincan.
TinCanException
¶
The parent class of all exceptions TinCan raises.
-
class
tincan.
TemplateHeaderError
¶
Raised upon encountering a syntax error in the template header directives.
-
class
tincan.
LoadError
¶
Raised when we run into problems #load
’ing something, usually because it doesn’t exist.
-
class
tincan.
TinCanError
¶
General-purpose exception raised by TinCan when things go wrong, either when attempting to launch webapps, or attempting to service requests. Often caused by errors in user-created files.
Request and Response Objects¶
The request
and response
instance variables of tincan.Page
are standard bottle.Request
and bottle.Response
objects. In the environ
attribute of the request
object, any key beginning with tincan.
is reserved.
Header Directives¶
A .pspx
file is a standard Chameleon template, plus a set of optional header directives that may be present to override the default behavior of the created page or its associated route.
#end
Marks the last line of the headers. This is not currently necessary, as headers are implicitly ended by the first line which does not start with a leading “#”. This directive is mainly here to facilitate future support of alternate templating engines.
#errors
This is an error page which handles the specified error codes; any code-behind associated with this page must be based on the
tincan.ErrorPage
class.#forward
Ignore everything else in this template (and any code-behind associated with it), using the specified route to serve it instead. The route specified with
#forward
may itself contain a#forward
, but attempts to create a#forward
loop are not allowed and will cause aTinCanError
to be raised at initialization time.#hidden
This is a hidden page; do not create a route for it. The page can only be displayed by a server-side forward.
#load
Load the specified Chameleon template file and make the loaded template available as a template variable. Useful for importing and invoking macros. See Loading Templates below for more information.
#methods
A list of HTTP request methods, separated by whitespace, follows. The route will allow all specified methods. Not specifying this line is equivalent to specifying
#methods GET
.#python
What follows is the name of the Python file containing the code-behind for this route; the file name must end in
.py
.#template
Ignore the body of this file and instead use the template in the body of the specified file, which must end in
.pspx
. Any headers in the referred template file are ignored.
Error Pages¶
Error pages supersede the standard Bottle error handling, and are created by using the #errors
page header. The #hidden
and #method
header directives are ignored in error pages (error pages are effectively hidden anyhow, by virtue of never having normal routes created for them).
The #errors
directive takes a list of numeric error codes (values from 400 to 599 are allowed), separated by spaces; the page is created to handle the specified errors. If no error codes are specified, the page will handle all errors. The behavior of specifying multiple error pages for the same error code is undefined; doing so is best avoided.
Templates with No Explicit Code-Behind¶
Code-behind is optional for both normal and error page templates. If code-behind is not provided, TinCan will use the Page
or ErrorPage
class as appropriate.
Loading Templates¶
The #load
directive may be used to load additional templates, e.g. ones containing macro definitions. Note that the loaded files are standard Chameleon templates and not TinCan .pspx
files (i.e. they cannot contain any header directives); as such, loaded files must have the standard .pt
extension for Chameleon template files.
In the normal case, #load foo.pt
will load a file relative to the same directory as the page containing the #load
directive itself. The loaded template object will be made available as a template variable matching the file name sans extension (e.g. foo
). One can change the name of the variable created by prefixing the file specification with a variable name followed by an equals sign, e.g. #load t=foo.pt
. If one places the specification inside angle brackets (e.g. #load <t=foo.pt>
), files to be loaded are searched for in WEB-INF/tlib
instead.
Finally, as is allowed for all arguments to header directives, one may enclose the argument to #load
inside single or double quotes and use the normal Python string syntax.
Using Loaded Macros¶
Once a template has been loaded, it will be available as a sub-attribute of the macros
attribute of the associated template object. E.g.:
#load foo.pt
<!DOCTYPE html>
<html>
<head>
<title>Macro Example</title>
</head><body>
<p metal:use-macro="foo.macros.bar"></p>
</body>
</html>
Chameleon Templates¶
TinCan templates are Chameleon templates, as documented here. Note that the templates in .pspx
files are processed as strings, so load:
expressions will not work in them (hence the #load
header directive). Templates loaded via #load
are processed as files, so the load:
expression will work from within them.
TinCan provides chameleon_template
and chameleon_view
callables, which follow the standard Bottle conventions for templating. Thus, if it is desired to define routes the “Bottle way”, the following import
line will make Chameleon the default templating engine:
from tincan import chameleon_view as view, chameleon_template as template
Commands¶
launch¶
Name¶
launch — launch a simple server
Synopsis¶
launch [ -baddress | --bind=address ] [ -c | --compile ] [ -d | --debug ][ -eencoding | --encoding=encoding ] [ -f | --force ] [ -h | --help ]
[ -pport | --port=port ] [ -s | --static ] directory path
Description¶
This command launches a simple WSGI server (or, with launch --compile
, pre-compiles all Python source code to byte code) for the webapp in the specified directory on the specified URL path. Run launch --help
for an explanation of what each option does.
install-static¶
Name¶
install-static — install static files from webapp
Synopsis¶
install-static
[ -h
| --help
]
[ -m
| --move
]
[ -n
| --no-preserve
]
source target
Description¶
This command copies (or moves, if so specified) the static files found in the source directory to the target one. Run install-static --help
for an explanation of what each option does.