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:

_images/hello1.png

What just happened?

  1. TinCan found the hello.pspx file.

  2. In that file, it found no special header directives. It also found no hello.py file.

  3. 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 in hello.pspx with it.

  4. 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:

_images/hello2.png

What new happened this time?

  1. You created a code-behind file with the same name as its associated template (only the extensions differ).

  2. Because the file names match, TinCan deduced that the two files were related, one containing the code-behind for the associated template.

  3. TinCan looked in the code-behind file for a something subclassing tincan.Page, and created an instance of that class, complete with the standard request and response instance variables.

  4. It then called the handle(self) method of that class, which defined yet another instance variable.

  5. 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:

_images/500.png

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:

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

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

  3. Place all the webapp’s files in the second directory, ensuring all subdirectories created are both readable and writeable by the server.

  4. 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 to False 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 be False.

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 a TinCanError 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.