All the following examples are also available in the examples folder of pyroutes, as quickstart.py.
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.
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.
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 :)
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)
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.
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!
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.
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)
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.
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'})
Cookies are the de-facto way of storing data on the clients. Pyroutes uses secure cookies by default. This means that if a user edits his own cookies, pyroutes will not accept them. This is done by storing a HMAC-signature, based on the cookie its signing and the SECRET_KEY in your settings, along with the actual cookie.
Settings cookies:
@route('/cookie-set')
def set_cookies(request, message='Hi!'):
response = Response('Cookies set!')
response.cookies.add_cookie('logged_in', 'true')
# Insecure cookie setting
response.cookies.add_unsigned_cookie('message', message)
return response
Retrieving cookies:
@route('/cookie-get')
def get_cookies(request):
logged_in = request.COOKIES.get_cookie('logged_in')
message = request.COOKIES.get_unsigned_cookie('message')
if logged_in:
return message
raise Http403({'#details': 'Go away!'})
Deleting cookies:
@route('/cookie-del')
def get_cookies(request):
response = Response('Cookies deleted!')
response.cookies.del_cookie('logged_in')
response.cookies.del_cookie('message')
return response
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.