Documentation

New documentation under construction.

What is Brevé?

Brevé is a template engine inspired heavily by Nevow Stan [1]. Unlike most Python template engines, Brevé is not an XML parser, rather Brevé templates are expressed as genuine Python code with a single limitation: only expressions are allowed.

A simple example:

html [
    head [
        title [ 'A simple example' ]
    ],

    body [
        h1 [ 'This is an example of Brevé' ], br,
        div ( style = 'text-align: center;' ) [
            span [ '''
                As you can see, Brevé markup maps very
                directly to the final HTML output.
            ''' ]
        ]
    ]
]

This template would output the following:

<html>
    <head>
        <title>A simple example</title>
    </head>
    <body>
        <h1>This is an example of Brevé</h1>
        <div style="text-align: center;">
            <span>
                As you can see, Brevé markup maps very
                directly to the final HTML output.
            </span>
        </div>
    </body>
</html>

You can quickly see how Brevé's syntax is far more terse than the generated HTML output. This is the first obvious advantage of Brevé. Templates require less typing and appear less cluttered.

Back to Table of Contents

Brevé compared to other template engines

Most Python template engines (with only a few notable exceptions) are XML derivatives. That is, they are either valid XML (e.g. Zope's ZPT, Kid, Genshi), or they are non-validating PSP-style (Python Server Page) with template directives interwoven within the XML/HTML (i.e. Django, Cheetah, Myghty).

These types of template engines have the apparent advantage of being familiar to designers who may not be familiar with programming languages but know XML/HTML. While most designers may be familiar with XML/HTML, they probably aren't familiar with the odd tags and directives these template engines insert into the template, so this perceived advantage is unsubstantiated. My experience has shown most template engines to be equally (in)comprehensible to the average designer, with Brevé having no more or less of a learning curve than the traditional forms.

One notable advantage to Brevé is that it consists of a single syntax. That is, there is not a separate syntax for markup and a separate syntax for template directives. There is only one syntax that is universal to the entire template.

Another advantage to Brevé is that because it is real Python code, rendering is fast.

Most XML-based template engines require something like the following process to render:

parse XML template -> generate DOM -> flatten DOM to HTML.

Brevé requires only:

parse Python code -> flatten DOM to HTML

This is because Brevé templates are the DOM. Further, because Brevé's parser is Python's parser, Brevé takes advantage of any optimizations available in the Python parser. Also, once a Brevé template has been parsed, the output (Python byte code) can be cached and reused for further performance gains.

Another advantage Brevé has over many other template engines is that it can be easily extended to generate other types of output besides HTML. For instance, Brevé has been used to generate XRC, the XML micro-language used by wxWidgets [2] for describing graphical user interface elements. It's also been used to generate RSS and Atom feeds. Because Brevé has a pluggable XML generation system (and defining new tags is remarkably simple) it's ideally suited for many applications outside the web.

Back to Table of Contents

Brevé Configuration Options

There are a few parameters that control Brevé's behaviour:

  • extension - The default extension for Brevé templates. Defaults to "b".
  • root - The base directory prepended to all template references (including inter-template references). Defaults to '.'
  • doctype - The HTML doctype. Defaults to the XHTML 1.0 Transitional doctype.
  • xml_encoding - The XML encoding of the output. Default is '<?xml version="1.0" encoding="UTF-8"?>'.
  • namespace - All variables passed into the template will be placed in a Namespace object with this name (e.g. a value of 'vars' would result in the variable foo being addressable as vars.foo or vars [ 'foo' ] within the template). Useful for avoiding name clashes with XML tags and template local variables or doing dynamic variable addressing. Default is None (disabled).
  • tidy - If the Python tidy library is installed, and this flag is True, then output is filtered through tidy prior to being returned. This can greatly assist in development since the output will be nicely formatted. Default is False (disabled).
  • format - This argument causes Brevé to attempt to import a Python module of the same name that contains custom tag definitions (see Creating New XML Tags for more details). Default is 'html'.

Usually these parameters are passed via the framework's configuration system. See the framework notes on the download page for details regarding particular frameworks.

Since 1.0.27, the Brevé Buffet adapter also supports passing url-style arguments as part of the template parameter. Both TurboGears and Pylons are badly broken with regard to passing template arguments, so this feature can help bypass these issues. For example, in TurboGears, you could use something like the following:

@expose ( template = 'breve:feed?root=/templates/feeds&format=rss' )

and in Pylons:

render_response ( 'feed?root=/templates/feeds&format=rss' )

For direct access to these values (i.e. when used outside a supported framework), they can usually be passed as parameters or set directly on the breve.Template object prior to rendering.

Back to Table of Contents

Basic HTML Generation

Brevé supports all the expected HTML tags. Translating a typical HTML document is usually no more complicated than the following:

  1. replace open tags with "tag ["
  2. replace the close tags with "]"
  3. put parentheses around attributes ( such as style="" and id="" )
  4. put commas between adjacent tags

Consider the example given earlier to get a good picture of what this entails.

Back to Table of Contents

Using Variables

Template engines aren't of much use if you can't dynamically generate content (although Brevé's clean syntax would be an advantage in any case). Using variables in Brevé is quite simple: you simply pass the variable (usually from your framework's controller) and then use it in your template. An example for TurboGears [3] might be as follows:

# controllers.py
from datetime import datetime
@view ( 'breve:index' )
def index ( self, *args, **kw ):
     today = datetime.today ( ).strftime ( '%Y/%m/%d' )
     return dict (
          message = 'Hello, world',
          today = today
     )

# index.b
html [
     body [
          span [ message ], br,
          span [ 'Today is ', today ]
     ]
]

Note that Brevé templates usually end with a ".b" extension (although this can be changed). The output of the above would be something like this:

<html>
    <body>
        <span>Hello, world</span><br />
        <span>Today is 2006/12/27</span>
    </body>
</html>

Back to Table of Contents

Special Directives

Variable substitution is useful, but Brevé supports much more powerful features that help organize your templates and assist with code reuse.

The include directive

The first and simplest of these is simple file includes. Templates may include fragments of Brevé from other files. For example:

# index.b
html [
    body [
         span ( class_ = 'title' ) [ 'Include directive' ],
         div ( class_ = 'para' ) [
             include ( 'fragment' )
         ]
    ]
]

# fragment.b
div [
     'This could have been any amount of Brevé'
]

You could also combine this with variable substitution to dynamically select fragments to include in your template, for example:

# index.b
html [
    body [
         span [ "We're going to include %.b" % page ],
         include ( page )
    ]
]

Assuming that the variable "page" was populated with the path to a file containing Brevé code, this would have included whatever file was stored in that variable.

However, there are better ways of doing this that we'll be covering next.

Back to Table of Contents

Template Inheritance

Inheritance is more complicated than simple includes, but more powerful as well. At some level it can be seen as the inverse of includes, since it's the fragments that specify what they'll be included in. This model is especially useful in frameworks (such as TurboGears and CherryPy) where a class or method (referred to a a "controller") directly represent a particular page on a website. You could solve this problem by dynamically selecting files to include as in the earlier section, but it's much better to use inheritance. It's much easier to demonstrate than explain so here's a short example:

# controllers.py
@view ( 'breve:contact' )
def contact ( self, *args, **kw ):
    return ( { } )

# contact.b
inherits ( 'index' ) [
    override ( 'content' ) [
        div ( id = 'contacts' ) [
            ul [
                li [ a ( href = 'mailto:cliff@domain.com' ) [ 'Cliff' ] ],
                li [ a ( href = 'mailto:laura@domain.com' ) [ 'Laura' ] ]
            ]
        ]
    ]
]

# index.b
html [
     body [
         slot ( 'content' )
     ]
]

This may seem complicated (and there are a few new directive that must be taken together), but it's really quite simple (especially if you are familiar with the concept of inheritance from Python and other programming languages).

Here's the basic flow of events: The controller specifies that it will render the template "contact.b". However this template gives notice that it is only part of a larger whole by using the "inherits" directive. The "inherits" directive tells Brevé that this template consists only of fragments that will fillin empty "slots" in some other template (whose name is given in the parentheses following the "inherits" directive).

Note: template paths are relative to a "root" which must be specified when instantiating the Template object. This can be done via a setting in your framework or by specifying it as the "root" argument to the Template object.

Now that Brevé knows what template to fill in with fragments from this template, it needs to know what slots to fill in (our example has only one, but there could be several). This is what the "override" directive does. It tells Brevé to replace the "slot" named "content" with the fragment specified. More than one slot may be given:

# fragment.b
inherits ( 'index' ) [
    override ( 'main-menu' ) [
        div ( class_ = 'menu' ) [
             ul [
                  li [ a ( href = '/' ) [ 'Home' ] ],
                  li [ a ( href = '/about' ) [ 'About' ] ]
             ]
        ]
    ],

    override ( 'content' ) [
        div ( class_ = 'body-text' ) [
            '''Welcome to our humble site.  Enjoy your stay.'''
        ]
    ]
]

# index.b
html [
     body [
          slot ( 'main-menu' ),
          slot ( 'content' )
     ]
]

The output of the above would look like this:

<html>
    <body>
        <div class="main-menu">
            <ul>
                <li><a href="/"></a>Home</li>
                <li><a href="/about">About</a></li>
            </ul>
        </div>
        <div class="body-text">
            Welcome to our humble site.  Enjoy your stay.
        </div>
    </body>
</html>

Inheritance can go many layers deep and the deepest layer (i.e. the one farthest from the root template) has the final say. For example:

# frag2.b
inherits ( 'frag1' ) [
    override ( 'slot-1' ) [
        span [ 'Hello from frag2' ]
    ],
    override ( 'slot-2' ) [
        span [ 'Hello from frag2' ]
    ]
]

# frag1.b
inherits ( 'index' ) [
    override ( 'slot-1' ) [
        span [ 'Hello from frag1' ]
    ],
    override ( 'slot-3' ) [
        span [ 'Hello from frag1' ]
    ]
]

# index.b
html [
    body [
         slot ( 'slot-1' ),
         slot ( 'slot-2' ),
         slot ( 'slot-3' )
    ]
]

Then if we told our controller to render frag2.b, we would get the following:

<html>
    <body>
        <span>Hello from frag2</span>
        <span>Hello from frag2</span>
        <span>Hello from frag1</span>
    </body>
</html>

Notice that even though frag1.b specified that it would override slot-1, because frag2.b also specified that it would override slot-1 and, being further from the "root" document (in this case index.b), it has the last say and so does the override.

As an aside, something that's been used several times now but not mentioned is appending an underscore ( _ ) to certain attributes. This is side-effect of using Python as a template language. Some words that are used as HTML attributes are also reserved Python keywords ("class" being the most commonly used one). Because it would be a Python syntax error to use the word "class" outside an actual class definition, we are forced to append an underscore to prevent this name clash. Helpfully, we don't need to actually remember which attributes conflict with Python keywords. If you don't know which might cause problems, simply append an underscore to all attributes. Brevé will accept it either way.

Back to Table of Contents

Conditional Expressions

For the most part, it's best to not put too much logic in your templates. It tends to make them cluttered and difficult to read. Also, debugging templates is usually more difficult than debugging actual program code so the simpler they are the better. Regardless, it's sometimes useful to be able to embed bits of simple logic in templates. To that end, Brevé provides a few simple constructs for making rudimentary decisions directly within the template. If you find yourself tempted to use these constructs, pause and consider if perhaps this could be better done within the controller. Usually the controller is the proper place for logic but on rare occassions it makes sense to do it in the template. Where to draw the line is something that comes with experience. If you find your templates getting difficult to understand then you've probably gone too much the wrong direction.

BIG FAT WARNING

Because Brevé templates are expressions, there's no short-circuit behaviour. The conditional expressions below are evaluated inside-out. This means that the body of the condition is evaluated before the condition itself. The truth of the condition does not prevent the code inside the body of the clause from being executed, it only prevents it from being displayed. If you, for example, reference a non-existent variable inside the body of the clause, you will get an error. To some degree I consider the conditional expressions described below to be a mistake and they are being considered for deprecation for Brevé 2.0. Please see the Using Python Constructs in Templates for better examples of how to achieve proper logic in your templates.

The when conditional

The simplest conditional expression is the when conditional. It's semantically equivalent to Python's if statement except that it doesn't support an else clause.

html [
    body [
        when ( x == 1 ) [ span [ 'x is 1' ] ],
        when ( x != 1 ) [ span [ 'x is not 1' ] ]
    ]
]

The switch/case conditional

The next conditional expression is the switch/case conditional. If you are familiar with languages such as C, then this is a familiar construct (albeit with somewhat different syntax).

An example:

html [
    body [
        switch ( username ) [
            case ( None ) [ span [ "Please login" ] ],
            default [ "Welcome back, %s" % username ]
        ]
    ]
]

In the above example, the switch/case conditional is used to test the value of the "username" variable. If the variable contains None (which we assume means no one is logged in), then we display a message directing the user to login. If it's anything else, then we assume it contains a valid username and we display a welcome message. There are a few things to note about this construct:

  1. There can be as many "case" directives as you need
  2. "case" directives are evaluated from top to bottom. This means you should probably put the most common cases near the top for performance reasons (although if your conditional is long enough to make much difference then probably you should have put this logic in the controller).
  3. The "default" directive is optional and, if present, must be last. Evaluation of the conditional stops whenever a "case" directive meets the criteria or the "default" directive is encountered, so any further "case" directives after the default will never be evaluated.
  4. If no "case" directive satisfies the test and there is no "default" directive, the expression as a whole evaluates to nothing (i.e. it won't appear in the final HTML output).

Back to Table of Contents

Custom Flatteners

So far, most of the features of Brevé are pretty typical of most template engines. In fact, Brevé offers far fewer features than most (we consider this a good thing). However, Brevé tries to offer the fewest but most powerful and expressive features. Quality over quantity. One of the simplest yet most powerful features of Brevé is the concept of flatteners. As I mentioned earlier, there's only one process to turn a Brevé template into its final HTML form and that process is called "flattening". All the keywords such as "div" and "span" that you use in Brevé templates have a default flattener. In fact, anything that can be printed (i.e. using the "print" statement) in Python can be flattened automatically by Brevé.

However, sometimes we want certain things to be flattened in a particular way. Quite often the string representation of Python objects isn't at all what we'd want in our HTML output. Consider the first example back in Chapter 1 that used the datetime object. In that example, we formatted the output in the controller prior to passing it to the template.

Let's try it again using a custom flattener:

# controllers.py
from datetime import datetime
from breve.flatten import register_flattener
from breve.tags import escape

def flatten_date ( o ):
    return escape ( o.strftime ( '%Y/%m/%d' ) )
register_flattener ( datetime, flatten_date )

@view ( 'breve:index' )
def index ( self ):
    return dict ( today = datetime.today ( ) )

# index.b
html [
    body [
        span [ 'Today is', today ]
    ]
]

The function "register_flattener" takes two arguments: a type and a function. Whenever Brevé encounters an object that matches the registered type, it will pass that object to the specified function to be flattened. The output of the function is what will appear in the final HTML output:

<html>
    <body>
        <span>Today is 2006/12/27</span>
    </body>
</html>

This is an extremely simple example, but it doesn't take much to imagine the ways this concept can be applied. Database records could be flattened to forms, Python lists could be flattened to HMTL tables or lists. The beautiful thing is that no special formatting need appear anywhere but in the flattener. Again this fits with the Brevé philosophy of keeping templates as simple and clean as possible.

Important: Notice that we called escape ( ) before returning the flattened object. All output in Brevé templates is escaped by default except for custom flatteners. Custom flatteners are assumed to know what they are doing and their output is passed untouched to the browser. Passing unescaped output might open security flaws or at least cause rendering issues. Be very certain you know what you are doing if you return unescaped data.

Back to Table of Contents

Custom Renderers

Much as flatteners allow you to generate custom HTML when a particular Python object is encountered, renderers allow you to generate custom template code for particular template tags. While flatteners generate the final HTML, renderers generate template fragments.

For example:

# controllers.py
from breve.tags.html import tags as T

@view ( template = 'breve:index' )
def index ( self ):
    def hello ( tag, data = None ):
        return tag [
            T.span ( style = 'font-weight: bold;' ) [ 'Hello, world' ]
        ]
    return ( dict (
        hello_renderer = hello
    ) )

# index.b
html [
    body [
        div ( render = hello_renderer )
    ]
]

This would output:

<html>
    <body>
        <div>
            <span style='font-weight: bold;'>Hello, world</span>
        </div>
    </body>
</html>

You'll notice, however, that the renderer can accept a second argument "data". You can pass any value or variable in here, for example:

# controllers.py
from breve.tags.html import tags as T

@view ( template = 'breve:index' )
def index ( self ):
    def hello ( tag, data = None ):
        return tag [
            T.span ( style = 'font-weight: bold;' ) [ 'Hello, %s' % data ]
        ]
    return ( dict (
        hello_renderer = hello
    ) )

# index.b
html [
    body [
        div ( render = hello_renderer, data = 'Joe' )
    ]
]

This would output:

<html>
    <body>
        <div>
            <span style='font-weight: bold;'>Hello, Joe</span>
        </div>
    </body>
</html>

Again, this is a very simple (and rather contrived) example, but these simple building blocks are quite powerful when combined in creative ways.

Custom Loaders

Since version 1.1.4, Brevé supports using custom loaders for finding and loading templates. A loader is a class that provides two methods, stat and load. stat accepts a template name and a root and returns a timestamp and a uid. In typical use, the timestamp reflects the last modification time of the template and the uid is a globally unique identifier for the template. In most cases the uid will simply be the full path to the template, but this is not required, it could just as well be the primary key from a database table or a URL. The only requirement is that it is globally unique for each template. The timestamp is also somewhat arbitrary in that it is only compared to the last timestamp returned by stat. This value is used for caching and determines whether the cached value will be returned or whether load will be called to fetch the template. If you wanted to completely bypass caching, you could simply return a sequence (1, 2, 3, etc) and the cache would be considered perpetually stale, forcing calls to load. The load method simply accepts the uid returned by the stat method and returns a string representing a Brevé template or fragment.

The default loader mimics the previous behaviour of Brevé, which is to simply prepend the root to the requested template and try to read it from the filesystem.

The following is a simple example that allows for templates to be searched for in multiple directories (i.e. a search path):

import os
from breve import Template
from breve.tags import html

class PathLoader ( object ):
    __slots__ = [ 'paths' ]

    def __init__ ( self, *paths ):
        self.paths = paths

    def stat ( self, template, root ):
        for p in self.paths:
            f = os.path.join ( root, p, template )
            if os.path.isfile ( f ):
                timestamp = long ( os.stat ( f ).st_mtime )
                uid = f
                return uid, timestamp
        raise OSError, 'No such file or directory %s' % template

    def load ( self, uid ):

        return file ( uid, 'U' ).read ( )

loader = PathLoader ( 'path1', 'path2', 'path3', '' )
t = Template ( tags = html.tags, root = root )
t.render ( 'index', loader = loader )

Since 1.1.5, the include directive also accepts a loader argument. This allows loaders to be mixed within the same template. Note that loaders are inherited. This means that if you include a template and specify a loader for that include directive, then the new loader will be in effect until the include directive finishes, at which point the prior loader will be restored. This means that if the included template also includes another template, the specified loader will be used to include this template as well. This is managed internally as a stack, so there is no limit to the number of loaders that can be called.

Back to Table of Contents

Using Python Constructs in Templates

Since Brevé templates are Python, you are free to use Python code within them with a single (but important) limitation: Brevé templates are expressions and since Python doesn't allow embedding statements within expressions, no Python statements are allowed.

Statements vs Expressions

Python is an imperative language. This means that it makes a distinction between statements and expressions. Logically, there is no real distinction between a statement and an expression (all statements could be expressions if the language allowed it, and many languages do, Lisp and Ruby being two notable ones). Ultimately, the distinction boils down to a single rule: expressions can be embedded in other expressions, statements cannot. Because of this, expressions return a value (although sometimes it is None), statements do not return a value. It's probably best to simply show some statements and expressions and let your intuition do the rest.

Examples of statements:

if ( x == 1 ):
    pass

for x in range ( 10 ):
    pass

x = 6

def f ( x ):
    pass

Examples of expressions:

3 + 2

x == 1

f ( x )

Here's a quick reference for turning Python conditional statements into equivalent Python expressions (borrowed from Crispin Wellington's article. Note also his caveats regarding this technique):

# Python Conditional      # Equivalent Python Expression
if A: B                   (A and B)

if not A: B               (A or B)

if A: B                   ((A and B) or C)
else: C

if A: B                   ((A and B) or (C and D) or E)
elif C: D
else: E

Python 2.5 has also introduced a nice ternary operator that can be used in Brevé templates:

a if b else c

So for example, you could do something like:

div [
    [ 'Welcome, %s' % user ] if ( logged_in ) else [ 'Please log in' ]
]

Back to Table of Contents

Some Concrete Examples

Here's some examples of Python code embedded in Brevé templates to accomplish common tasks. Just remember that, like the conditional expressions mentioned earlier, it's best to keep logic in the template to a minimum.

  1. Using a list as a conditional:
div [
    [ 'goodbye', 'hello' ][ bool ( logged_in ) ]
]
  1. List comprehensions as loops:
ul [
    [ li [ s ] for s in items ]
]
  1. Dictionaries as selectors:
div [
    { 1: span [ 'One' ],
      2: span [ 'Two' ],
      3: span [ 'Three' ] } [ var ]
]

And of course you are free to pass functions, objects and variables into the template and use them freely. You simply can't define them there. You must define them externally to the template (i.e. in your controller) and pass the object in.

As an aside, it is entirely possible to implement a goodly portion of Python's statements as expressions. This has been done at least once [4]. If you truly feel the need for rich logic constructs in your templates you could import a module such as this and pass it into the template's namespace (much the same way custom tags can be passed in, which we'll discuss in a later chapter).

Back to Table of Contents

Putting It Together

One of the biggest stumbling blocks when learning to use Brevé is changing how you think about template organization. Traditional templates with rich logic features built in make it quite easy to paste together a template without much thought. However they also make it quite easy to create templates that appear to have been put together without much thought. Templates are code and we feel they should be given as much care as any other piece of code, especially since debugging HTML and CSS accounts for so much time in website design. For many of the same reasons the more complicated approach of tableless CSS layout is preferable to the easier-to-write table-based layouts of yore, the slightly more difficult approach of Brevé pays off in the long run (by "difficult" I mean "requires forethought to get optimal results").

Despite requiring more forethought, there are several patterns that can be readily adopted to make your life with Brevé simpler.

Pattern 1: the multi-faceted site

A common architecture of sophisticated websites is to have two (or more) views of the same site depending on certain conditions (most often based upon the visitor's status: anonymous, registered, paying/premium, administrator, etc). Brevé's inheritance model is ideal for this architecture. Also custom flatteners and renderers can be a great boon. Let's consider a simple site that has two facets: anonymous ("guest") and registered ("user"). Depending on the user's status, many aspects of the site will differ.

We'll start with the following templates:

# index.b
html [
    body [
        slot ( 'greeting' ),
        slot ( 'sidebar' ),
        slot ( 'content' )
    ]
]

# guest.b
inherits ( 'index' ) [
    override ( 'greeting' ) [
        span [
            'Welcome, guest. ',
            'Please ', a ( href = '/login' ) [ 'login' ],
            ' if you have an account.'
        ]
    ],

    override ( 'sidebar' ) [
        ul [
            [ li [ m ] for m in guest_menus ]
        ],
        include ( 'advertisements' )
    ],

    override ( 'content' )
]

# user.b
inherits ( 'index' ) [
    override ( 'greeting' ) [
        'Welcome back, %s!' % user.name,
        'You have %d new messages' % len ( user.messages )
    ],

    override ( 'sidebar' ) [
        ul [
            [ li [ m ] for m in user_menus ]
        ]
    ],

    override ( 'content' )
]

Now, you can see we've managed to dynamically change aspects of the site by simple organization rather than by embedding logic within the templates. We could have had a single, longer template with conditionals for selecting output, but that would have gotten messy pretty fast. One thing to note is that if an "override" directive doesn't have a body, then the "slot" it overrides will effectively disappear from the final HTML output.

In all the examples so far, we've hard-coded URLs directly into the template. Often this isn't desirable since URLs may change over time (or depend upon which facet of the site the visitor is viewing). Many frameworks use a dispatching system to map URLs to to views (for example, Pylons [5] uses Routes [6]) and we want to be able to integrate this mapping down to the template level. This is a place where custom renderers can come in handy. We'll change the guest.b template from the example above to leverage a custom renderer for the login URL:

# controllers.py
def render_login_url ( tag, data = None ):
    return tag ( href = '/login' ) [ 'login' ]

@view ( template = 'breve:guest' )
def index ( self ):
    return ( dict (
        render_login_url = render_login_url
    ) )

# guest.b
inherits ( 'index' ) [
    override ( 'greeting' ) [
        span [
            'Welcome, guest. ',
            'Please ', a ( render = render_login_url ),
            ' if you have an account.'
        ]
    ],

    # ...
]

There's no hard-and-fast rule for when things should be coded directly into a template or handed off to the controller, but usually if you are finding it difficult to express something in the template then it probably belongs in the controller (either using a custom renderer or flattener).

For more information, see the examples directory that comes with Brevé.

Back to Table of Contents

Gotchas and Tricks

It's important to note that all plain strings in a Brevé template are automatically escaped. This means that if you want to output XML, you need to enclose it in an xml directive:

xml ( '''<b>this is bold</b>''' )

Custom flatteners, on the other hand, are assumed to output completely rendered XML. Therefore if you define a custom flattener, it is not automatically escaped and you should take care to escape any questionable content yourself using breve.tags.escape():

from breve.flatten import register_flattener
from breve.tags import escape

class Foo ( str ):
    content = '''<some unsafe content>'''

def flatten_foo ( o ):
    return escape ( o.content )
register_flattener ( Foo, flatten_foo )

Back to Table of Contents

Unicode-specific Issues

In general, Unicode should work as expected. However, there is at least one Python setting that can have an impact on what is "expected". Many systems are installed with us-ascii as the default encoding. This will cause Python to throw exceptions if you embed non-ASCII characters into a plain Python string. The solution is to either always use the Unicode form of Python strings (i.e. u"a string") or to properly setup your Python install with the correct encoding. For most people, the best encoding is going to be utf-8. This provides a great deal of backwards-compatibility (it includes ASCII as a subset) and allows Python to deal with non-ASCII characters in plain strings.

You can find out what your default encoding is with this command:

python -c "import sys; print sys.getdefaultencoding()"

To change the default encoding for Python, you can create a file named sitecustomize.py and put it somewhere on your PYTHONPATH:

import sys
sys.setdefaultencoding ( 'utf-8' )

Note that if you put this in your system-wide site-packages directory, this will affect all Python applications on the system (this may or may not be a good thing - I've had no issues with doing so). Alternatively, if you only want to affect individual applications, simply put this file in a directory that's on the application's PYTHONPATH.

Back to Table of Contents

Creating New XML Tags

You can create tag description files (which are Python modules) by hand or use the xsd2breve script from the tools/ directory of the source distribution.

Here's a simple example showing how to describe the Google sitemap [7] schema:

# sitemap.py
from breve.tags import Proto

xmlns = "http://www.google.com/schemas/sitemap/0.84/sitemap.xsd"
doctype = "" # required, but not used here
tag_names = [ "changefreq", "lastmod", "loc", "priority", "url", "urlset" ]
tags = { }
for t in tag_names:
    tags [ t ] = Proto ( t )

You could have also generated this exact file with using the xsd2breve script:

xsd2breve http://www.google.com/schemas/sitemap/0.84/sitemap.xsd > sitemap.py

Assuming you have now have sitemap.py somewhere on your Python path, you would use:

from breve import Template
import sitemap

vars = dict ( loc = 'http://www.example.com/',
              lastmod = '2005-01-01',
              changefreq = 'monthly',
              priority = 0.8 )
t = Template ( tags = sitemap.tags, xmlns = sitemap.xmlns )
t.namespace = 'v'
t.render ( 'mytemplate', vars = vars )

Note that we set the template variable namespace to "v" in order to avoid clashing with our custom tag names.

Next, we create our template:

# mytemplate.b
urlset ( xmlns = xmlns ) [
    url [
        loc [ v.loc ],
        lastmod [ v.lastmod ],
        changefreq [ v.changefreq ],
        priority [ v.priority ]
    ]
]

which would render as:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
    <url>
        <loc>http://www.example.com/</loc>
        <lastmod>2005-01-01</lastmod>
        <changefreq>monthly</changefreq>
        <priority>0.8</priority>
    </url>
</urlset>

The Buffet adapter also supports rendering custom tags. Simply pass the name of your tag definition module (which must be on the Python path) as the value of "format":

from breve.tags import Namespace

# turbogears example
@expose ( 'breve:sitemap', format = 'sitemap' )
def sitemap ( self ):
    return dict ( v = Namespace (
        loc = 'http://www.example.com/',
        lastmod = '2005-01-01',
        changefreq = 'monthly',
        priority = 0.8
    ) )

The Namespace is necessary as TurboGears provides no mechanism for passing extra arguments to template engines.

Back to Table of Contents

The Brevé API and Standalone Operation

Rendering a Brevé template is a two-part operation: instantiation and rendering. Each stage has a few options that control the final output:

t = Template ( tags, root = '.', doctype = '', xmlns = '' )
print t.render ( template, vars = None )

Arguments

  • tags: a dictionary of legal entities.
  • root: the base directory prepended to all template references.
  • doctype: the XML DTD
  • xmlns: the XML namespace
  • template: the filename (sans extension) of the template to be rendered
  • vars: a dictionary or Namespace of variable names to provide in the template

Tags are a mapping of strings to flattenable objects. "Flattenable" means that there is either a flattener defined for the object type (using register_flattener ( )) or the object has a meaningful representation when calling str ( ) on it.

Namespace is simply a convenience object that presents both mapping and attribute access ( i.e. an item key can can be referenced in Namespace ns as ns [ 'key' ] or ns.key ).

Using Brevé tags outside a template:

It's also possible to simply create fragments of XML outside of a template (e.g. in Python's interactive interpreter):

Python 2.4.4 (#1, Oct 23 2006, 13:58:00)
[GCC 4.1.1 20061011 (Red Hat 4.1.1-30)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from breve.tags.html import tags as T
>>> print T.html [ T.body [ T.div [ 'this is a test' ] ] ]
<html><body><div>this is a test</div></body></html>
>>>

If you wanted to embed Brevé code in a standalone Python app (or perhaps a CGI), you could do something like this:

from breve.flatten import flatten
from breve.tags.html import tags
globals ( ).update ( tags )

template = html [
    head [
        title [ 'Example' ]
    ],

    body [
        div [ "We're using Brevé in a Python app." ]
    ]
]

print flatten ( template )

Note that while it's convenient to mash the Brevé tags into the global namespace, it's probably better to just use T or some other short name as in the previous example. There's enough HTML tags that could get stepped on by local variable (e.g. a, b, p) to make this not recommended (but still shown here for consenting adults).

A few notes about register_flattener and flattening in general:

  • If there is no registered flattener for an object, the builtin str function is tried.
  • There are a few pre-registered flatteners for built-in Python objects: str, list.
  • Flatteners are registered based on the type of the object. There can be only one flattener per type. If you want to have variations in how a particular type is flattened, subclass the type and register a flattener for the subclass. If you want to override the default flattener, simply register a new one.
  • You can achieve the same functionality as register_flattener by simply defining a __str__ method for a class.
  • When defining custom flatteners, be wary of XML escaping and quoting issues. Custom flatteners are assumed to return the final representation of the object and no further escaping is done. You can use breve.tags.escape ( ) on the data before you return it from the renderer in order to guarantee the data is properly escaped.
  • If you want to turn off all escaping or define a different escaping mechanism, redefine the default str flattener or unregister it using unregister_flattener ( str ), but be certain to call breve.tags.escape ( ) for any untrusted data.

Back to Table of Contents

Function Reference

  • register_global ( name, variable|function ) This function takes a string name and registers it with the Brevé template as a global variable. This is a handy way to make sure that functions, renderers, etc are available application-wide without needing to explicictly pass them.
  • register_flattener ( type, function ) This function associates a Python type with a function that will render it as a string. There can only be one flattener per type.
  • util.escape ( string ) Escape &, <, and > in a string of data.

Back to Table of Contents

Experimental Features

The SVN version of Brevé will often contain experimental features. These are features that are being considered for a future version of Brevé but are not considered ready. You are encouraged to test them and provide feedback, but please do not use them in production code as they are subject to change or complete removal.

Tag Multiplication

This experimental feature lets you multiply a tag by a list of dictionaries. It's more or less an implicit loop.

For example:

from breve.tags.html import tags
from breve.flatten import flatten
globals ( ).update ( tags )

menu_items = [
    { 'href': '/home', 'data': 'Home', 'class': 'menu-item' },
    { 'href': '/about', 'data': 'About', 'class': 'menu-item' },
    { 'href': '/docs', 'data': 'Documentation', 'class': 'menu-item' },
]

template = html [
    body [
        ul [
            li ( class_ = '$class' ) [
                a ( href='$href', class_ = '${class}-link' ) [ '$data' ]
            ] * menu_items
        ]
    ]
]

print flatten ( template )

The above would output:

<html>
  <body>
    <ul>
      <li class="menu-item">
        <a class="menu-item-link" href="/home">Home</a>
      </li>
      <li class="menu-item">
        <a class="menu-item-link" href="/about">About</a>
      </li>
      <li class="menu-item">
        <a class="menu-item-link" href="/docs">Documentation</a>
      </li>
    </ul>
  </body>
</html>

Back to Table of Contents

edit page
Back to top
Rendered using Brevé 1.3.0Copyright © 2007, Cliff Wells