Tag Archives: bottle

Jinja2 templates and Bottle

Although Bottle’s built-in mini-template language is remarkably useful, I nearly always prefer to use Jinja2 templates because the syntax is very close to Django’s template syntax (which I am more familiar with) and because the Bottle template syntax for filling in blocks from a parent template is a bit limiting (but that’s kind of the point).

Bottle provides a nice jinja2_view decorator that makes it easy to use Jinja2 templates, but it isn’t that obvious how to configure the template environment for auto-escaping and default context, etc.

(The rest of this relates to Bottle version 0.11 and Jinja2 version 2.7.)

Template paths

Bottle’s view decorator takes an optional template_lookup keyword argument. The default is to look in the current working directory and in a ‘views’ directory, i.e. template_lookup=('.', './views/').

You can override the template path like so:

from bottle import jinja2_view, route

@route('/', name='home')
@jinja2_view('home.html', template_lookup=['templates'])
def home():
    return {'title': 'Hello world'}

Which will load templates/home.html.

Most likely you will want to use the same template path for every view, which can be done by wrapping jinja2_view:

import functools
from bottle import jinja2_view, route

view = functools.partial(jinja2_view, template_lookup=['templates'])

@route('/', name='home')
@view('home.html')
def home():
    return {'title': 'Hello world'}

@route('/foo', name='foo')
@view('foo.html')
def foo():
    return {'title': 'Foo'}

That would have loaded templates/home.html and templates/foo.html.

Another way of setting a global template path for the view decorator is to fiddle with Bottle’s global default template path:

from bottle import TEMPLATE_PATH, jinja2_view, route

TEMPLATE_PATH[:] = ['templates']

@route('/', name='home')
@jinja2_view('home.html')
def home():
    return {'title': 'Hello world'}

N.B. I used TEMPLATES_PATH[:] to update the global template path directly rather than re-assigning it with TEMPLATE_PATH = ['templates'].

Template defaults

Bottle has a useful url() function to generate urls in your templates using named routes. But it isn’t in the template context by default. You can modify the default context on the Jinja2 template class provided by Bottle:

from bottle import Jinja2Template, url

Jinja2Template.defaults = {
    'url': url,
    'site_name': 'My blog',
}

Jinja2 version 2.7 by default does not escape variables. This surprises me, but it is easy to configure a template environment to auto-escape variables.

from bottle import Jinja2Template

Jinja2Template.settings = {
    'autoescape': True,
}

Any of the Jinja2 environment keyword arguments can go in this settings dictionary.

Using your own template environment

Bottle’s template wrappers make a new instance of a Jinja2 template environment for each template (although if two views use the same template then they will share the compiled template and its environment).

You can avoid this duplication of effort by creating the Jinja2 template environment yourself, however this approach means you also need to write your own view decorator to use the custom template environment. No biggie.

Setting up a global Jinja2 template environment to look for templates in a “templates” directory:

from bottle import url
import jinja2

env = jinja2.Environment(
    loader=jinja2.FileSystemLoader('templates'),
    autoescape=True,
)
env.globals.update({
    'url': url,
    'site_name': 'My blog',
})

You then need a replacement for Bottle’s view decorator that uses the previously configured template environment:

import functools

# Assuming env has already been defined in the module's scope.
def view(template_name):
    def decorator(view_func):
        @functools.wraps(view_func)
        def wrapper(*args, **kwargs):
            response = view_func(*args, **kwargs)

            if isinstance(response, dict):
                template = env.get_or_select_template(template_name)
                return template.render(**response)
            else:
                return response

        return wrapper

    return decorator

@route('/', name='home')
@view('home.html')
def home():
    return {'title': 'Hello world'}

Conclusion

It’s easy to customize the template environment for Jinja2 with Bottle and keep compatibility with Bottle’s own view decorator, but at some point you may decide it is more efficient to by-pass things and setup a custom Jinja2 environment.

Bottle is nice like that.

Inspecting your routes in Bottle

Marcel Hellkamp recently added a small feature to Bottle that makes it easy to inspect an application’s routes and determine if a particular route is actually for a mounted sub-application.

(Bottle is a small module written in Python for making websites.)

Route objects (items in the app.routes list) now have extra information when the route was created by mounting one app on another, in the form of a new key mountpoint in route.config.

Here’s a trivial app with another app mounted on it:

import bottle

app1 = bottle.Bottle()

@app1.route('/')
def app1_home(): return "Hello World from App1"

app2 = bottle.Bottle()
@app2.route('/')
def app2_home(): return "Hello World from App2"

app1.mount(prefix='/app2/', app=app2)

And a utility function that returns a generator of prefixes and routes:

def inspect_routes(app):
    for route in app.routes:
        if 'mountpoint' in route.config:
            prefix = route.config['mountpoint']['prefix']
            subapp = route.config['mountpoint']['target']

            for prefixes, route in inspect_routes(subapp):
                yield [prefix] + prefixes, route
        else:
            yield [], route

Finally, inspecting all the routes (including mounted sub-apps) for the root Bottle object:

for prefixes, route in inspect_routes(app1):
    abs_prefix = '/'.join(part for p in prefixes for part in p.split('/'))
    print abs_prefix, route.rule, route.method, route.callback

This new feature is sure to revolutionise everything.

Class-based views for Bottle

I’m not convinced this is actually a good idea, but I have an approach for using class-based views as handlers for a route with Bottle.

(If you were mad keen on Django’s shift to class-based views you might reckon life wouldn’t be complete with a Bottle-based application until you employ classes for views. However Bottle’s use of decorators for tying URLs to views means it is less a natural fit than the same thing in Django.)

The problem is that you can’t just decorate the method in your class using bottle.route because if you use that decorator on a method in a class you are telling Bottle to use the method before it has been bound to an instance of that class.

So although I wish it did, the following example will not work:

import bottle

class ViewClass(object):
    @bottle.route("/")
    def home_view(self):
        return "My home page."

obj = ViewClass()
bottle.run()

Running that will lead to errors about not enough arguments passed to the view method of your ViewClass instance.

Instead you need to register the route right after the object is created. This can be done in the class’s __new__ method:

import bottle

class ViewClass(object):
    def __new__(cls, *args, **kwargs):
        obj = super(ViewClass, cls).__new__(cls, *args, **kwargs)
        bottle.route("/")(obj.home_view)
        return obj

    def home_view(self):
        return "My home page."

obj = ViewClass()
bottle.run()

It works. It isn’t that pretty. You could achieve exactly the same thing by explicitly passing the obj.home_view method to bottle.route after the instance is created. The advantage to doing this in the __new__ method is it will happen automatically whenever ViewClass is instantiated.

And if you go down this path then you should be aware of threads. Hey! Nice threads! Also I have a cold.

Bottle’s view decorator and default variables

Bottle‘s @view decorator provides a simple way to designate a template to render an HTML page. Your view function just has to return a dictionary, and its contents can be accessed from the template using the '{{ name }}' syntax.

The @view decorator can also take keyword arguments. These are treated as default template variables – if the dictionary returned by your view function doesn’t have a key for one of the keyword arguments then the template will use the value passed into the decorator, like so:

from bottle import view

@view('default.html', author='David Buxton')
def home():
    return {'title': 'Home page'}

That would render any instance of '{{ author }}' as 'David Buxton'. And then you can have another view function that overrides the keywords by returning a different value in the dictionary:

from bottle import view

@view('default.html', author='David Buxton')
def music():
    return {'title': 'Thalassocracy', 'author': 'Frank Black'}

And at that point I wonder what is the advantage of using keyword arguments with @view: you have to decorate each function separately, and if you want to override a keyword in your return dictionary then it would be easier not to specify the keyword in the first place.

Thus the real point of using keywords with the @view function is only apparent if you curry the @view decorator with keywords first so that you can re-use the curried decorator and avoid repeating yourself.

Someday I will re-write the previous sentence. Until then, sorry.

Instead of passing a default author each time as in the examples above, let’s make a new @view decorator (using Python’s functools module) and then use that on each view function:

import functools
from bottle import view

view = functools.partial(view, author='David Buxton')

@view('default.html')
def home():
    return {'title': 'Home page'}

@view('default.html')
def music():
    return {'title': 'Thalassocracy', 'author': 'Frank Black'}

The new decorator means you get the default keyword arguments wherever you use @view while permitting any function to override those defaults in the dictionary it returns.

And if you wanted to get really lazy you could even pass in a template name when wrapping the decorator with functools.partial, however you would not be able to use your wrapped decorator to change the template name because it is a positional argument (like what it explains here in the functools documentation). You would also have to call the decorator with no arguments like '@defaultview()'. So forget I mentioned it.

I’m not saying you are lazy.

Django-style routing for Bottle

Bottle provides the @route decorator to associate URL paths with view functions. This is very convenient, but if you are a Django-reject like me then you may prefer having all your URLs defined in one place, the advantage being it is easy to see at a glance all the different URLs your application will match.

Updated: I have re-written this post and the example to make it simpler following Marcel Hellkamp’s comments (Marcel is the primary author of Bottle). My original example was needlessly complicated.

It is possible to have a Django-style urlpatterns stanza with a Bottle app. Here’s how it can work:

from bottle import route

# Assuming your *_page view functions are defined above somewhere
urlpatterns = (
    # (path, func, name)
    ('/', home_page, 'home'),
    ('/about', about_page, 'about'),
    ('/contact', contact_page, 'contact'),
)

for path, func, name in urlpatterns:
    route(path, name=name)(func)

Here we run through a list where each item is a triple of URL path, view function and a name for the route. For each we simply call the route method and then invoke it with the function object. Not as flexible as using the decorator on a function (because the @route decorator can take additional keyword arguments) but at least you can have all the routes in one place at the end of the module.

Then again if you have so many routes that you need to keep them in a pretty list you probably aren’t writing the simple application that Bottle was intended for.

(This was tested with Bottle’s 0.8 and 0.9-dev branches.)