Let’s do it! (aka Quickstart)

All the following examples are also available in the examples folder of pyroutes, as quickstart.py.

Application entry point

The application entry point is located directly in the pyroutes module. Just do

from pyroutes import application

in the file you want as a handler for mod_wsgi or your preferred deployment method. Let’s call it handler.py for now.

Adding routes

Routes are the way for defining which methods should handle requests to which paths.

This is the most basic example:

from pyroutes import route

@route('/')
def index(request):
    return 'Hello world!'

Here we define that our index method should handle all requests to /, and return the famous «Hello world!» to the user.

We can add more routes:

@route('/sayhello')
def sayhello(request, name='world'):
    return 'Hello %s!' % name

... Easy as pie! Save these at the end of our handler.py file.

Route handling gotchas

After adding the two example routes, we have a handler for / and /sayhello. If you try to access /foo you will get an 404 exception. However, accessing /sayhello/master does something quite different :)

Starting the development server

Pyroutes includes a development server to ease local development quite a bit. It’s located in the pyroutes.utils module.

Using it is as easy as adding this to handler.py from the previous examples.:

if __name__ == '__main__':
    from pyroutes.utils import devserver
    devserver(application)

Serving static media

Static media is normally served directly from the web server, but we need static files when developing locally as well. You can serve static files through the devserver using the fileserver route in pyroutes.utils. Change the previous code to something like this:

if __name__ == '__main__':
    from pyroutes import utils
    route('/media')(utils.fileserver)
    utils.devserver(application)

This will now serve anything you have in the folder called media in your working directory in the /media path. This behaviour can be modified in pyroutes_settings.

Firing it up

Let’s try what we have so far. Open up a terminal, go to the directory where you saved the handler.py file, and execute it:

$ python handler.py
Starting server on 0.0.0.0 port 8001...

Your application should now be running on port 8001. Let’s try it.:

$ echo `wget -q -O - http://localhost:8001/`
Hello world!
$ echo `wget -q -O - http://localhost:8001/sayhello/Pyroutes`
Hello Pyroutes!

Debugging

If something in the code was not correct, and an exception was thrown, you’ll get a error page with not much information. You can see more about what went wrong if you enable pyroutes’ debugging.

This is done by creating a file called pyroutes_settings.py in your PYTHONPATH. Create this file and add:

DEBUG=True

Now refresh the page with the error, and you’ll get a lot more information to work with.

Using URLs as data

As of Pyroutes >= 0.3.0 using URLs as data for your handler really simple. Let’s create an archive route as an example:

@route('/archive')
def archive(request, year, month=None, day=None):
    return 'Year: %s  Month: %s  Day: %s' % (year, month, day)

And let’s try it:

$ echo `wget -q -O - http://localhost:8001/archive`
(This returns Http404 because year is an obligatory parameter)
$ echo `wget -q -O - http://localhost:8001/archive/2010`
Year: 2010  Month: None  Day: None
$ echo `wget -q -O - http://localhost:8001/archive/2010/02`
Year: 2010  Month: 02  Day: None
$ echo `wget -q -O - http://localhost:8001/archive/2010/02/03`
Year: 2010  Month: 02  Day: 03
$ echo `wget -q -O - http://localhost:8001/archive/2010/02/03/foobar`
(This returns HTTP 404 because archive only accepts four parameters)

This example should make the URL matching logic clear. Note: If a method accepts a referenced argument list in the from *args, it will match any subpath of its route address.

An example:

@route('/pathprint')
def archive(request, *args):
    return 'User requested /%s under /pathprint' % '/'.join(args)

Accessing request data

One common operation in developing web applications is doing stuff with user data. Pyroutes gives you easy access to the POST, GET and FILES posted to your request handler.

@route('/newpost')
def new_post(request):
    if 'image' in request.FILES:
        # Do stuff with image
        filename, data = request.FILES['image']
        data = data.read()
    category = request.GET.get('category','default')
    title = request.POST.get('title', None)
    if not title:
        return 'No title!'
    return 'OK'

Note

If multiple fields have the same name, the value in the respective dicts are a list of the given values.

Sending responses to the user

Every route must return an instance of pyroutes.http.response.Response, or one of it’s subclasses. The former defaults to sending a text/html response with status code 200 OK. Any data returned that wasn’t wrapped in a Response object will also have these defaults applied (by the Responsify middleware)

We have the follow built-in responses:

Response(content=None, headers=None, status_code='200 OK',
        default_content_header=True)

Redirect(location)

content may be any string or iterable. This means you can do something like this:

@route('/pdf')
def pdf(request):
    return Response(open('mypdf.pdf'), [('Content-Type', 'application/pdf')])

Also available for convenience is the HttpException subclasses, also found under pyroutes.http.response. An example (assuming a method decrypt that can decrypt files by some algorithm):

@route('/decrypt_file')
def decrypt(request, filename, key):
    full_filename = os.path.join('secrets_folder', filename)
    if not os.path.exists(full_filename):
        raise Http404({'#details': 'No such file "%s"' % filename})
    try:
        return decrypt(full_filename, key)
    except KeyError:
        raise Http403({'#details': 'Key did not match file'})

Let’s go templates!

Pyroutes bundles XML-Template, a template system created by Steinar H. Gunderson, which might seem a bit «chunky», but it really fast, and guarantees it’s output to be valid XML (or in our case XHTML). The big difference between XML-template and most other template systems out there, is that XML-template is purely a representation layer. You don’t have any logic in your templates.

Now, pyroutes has a small wrapper around XML-Template for handling the most common template task; having a base-template, and a separate template for your current task.:

from pyroutes.template import TemplateRenderer

tmpl = TemplateRenderer('base.xml')

@route('/')
def index(request):
    return tmpl.render('index.xml', {})

For more information about XML-Template, see Introduction to XML::Template.