Frictionless Web Development with Flask Static Sites with a Lightweight Python Stack.

For a while I’ve been looking for a more fluid toolkit for rapidly developing and iterating static sites. I have three use cases in mind: client html mockups with rapid iterations or slight variations, maintaining the static client sites that I have in the stable, and hacking on my own site. In each of these cases, dealing with databases, CMSs, versioning, or directories of HTML files was either too heavy, too slow, or both. My goals are straightforward:

First, A Diversion

Web development can become a chore, with too little time actually creating things and too much time learning APIs, stitching them together, and trying to remember their syntax. when I’m making a Django site, I feel like I’m programming in Django instead of Python. When I using JQuery, I feel like I’m programming JQuery instead of Javascript. Of course, frameworks and higher level APIs have enormous value (and the situation improves dramatically with one’s familiarity and excellent documentation). But I want to use simpler, more expressive tools even if this means reinventing a few wheels. Something light that allows me to structure my apps the way I want. Why? Because it’s more fun to make things than stitch APIs together.

Flask, As Simple As You Want To Be

Flask is a relatively new project that is part of the nice growing collection of packages under the Pocoo banner. It is a lightweight wrapper of two of their other projects, Werkzeug (a WSGI toolkit), and the excellent Jinja 2 template library.

Flask shares a quality with Python in general and the best of the core and third party python packages: it is as simple as you want it to be. The learning curve is very small. If you are just using the core functionality, the API is simple and intuitive. It grows with you as your needs grow. As your project grows, you can break it out from a single file to a whole package.

The quickstart guide is a great overview (and the rest of this post assumes a basic familiarity). I love its flexibility. You can start with all the content in your HTML files and break out reusable bits as needed. Or you can put the content in yaml files or a database and break things apart into an MVC structure. Whatever works for you.

Flask assumes very little about your file structure. By default, it looks for templates in the templates/ directory. And when you are using the dev server, it will automatically serve static content (images, css, etc) from static/ directory. Other than that, you basically put things where you want them.

For most static projects and mockups, the templates are where the action is. When I started making Django sites after working with Wordpress and Expression Engine, the Django templating system was a revelation. The template inheritance, block structures, and filters were exactly what I had been hoping for. Jinja was inspired by the Django template system and contains a host of improvements. The template syntax is straightforward and discoverable.

YAML == Easy content

I find myself using YAML more and more these days. My projects will often have configuration inputs or outputs that naturally lend themselves to plain text files. And since the excellent PyYAML package automatically translates them into base python objects, YAML is often the most expressive and lightweight way to deal a wide range of data. Consider a YAML file for a blog post

:::yaml
title: "The Gettysburg Address"
author: "Abe Lincoln"
date: 2011-01-08
banner: "/path/to/photo.jpg"
body: |
    Four score and seven years ago our fathers brought forth 
    on this continent, a new nation, conceived in Liberty, and 
    dedicated to the proposition that _all men_ are created equal.

    Now we are engaged in a great civil war, testing whether 
    that nation, or any nation so conceived and so dedicated, 
    can long endure. We are met on a great battle-field of that 
    war. We have come to dedicate a portion of that field, as a 
    final resting place for those who here gave their lives that 
    that nation might live. It is altogether fitting and proper 
    that we should do this.

tags:
    - presidents
    - speech
    - civil war

gallery:
    - pic: '/path/to/pic'
      caption: 'A very descriptive thingy'
    - pic: '/path/to/another/photo'
      caption: 'An even better caption'

In this example, pyyaml would return a dictionary of string objects (title, author, banner), a datetime object (date), a list (tags), and a list of dictionaries (gallery). The API is very straightforward.

:::python
import yaml
mypost = yaml.load(file('path/to/file', 'r'))
print(mypost['title'])  # 'The Gettysburg Address'

Even better with Markdown and Syntax Highlighting

YAML is more nutritious with Markdown. Markdown enables you to write big blocks of content and easily output valid HMTL. There is a robust Python implementation:

:::python
import markdown
foo = "# hello there, **Mr Bond**."
foo_html = markdown.markdown(foo)
print(foo_html)     
# <h1>hello there, <strong>Mr Bond</strong>.</h1>

Need code highlighting? Searching around, there is a host of outdated information on how to plumb everything together. But these days, Python-Markdown comes bundled with an extension for Pygments called codehilite which enables code highlighting for a huge list of languages. You use it like this:

:::python
import markdown
md = markdown.Markdown(extensions=['codehilite'])
foo = """
Beware the dreaded __trailing comma__.
Internet explorer will choke.

    :::javascript
    var foo = ['andrew', 'birds', 'bowl of', 'fire',];
    console.log(foo);
"""
foo_html = md.convert(foo)
print(foo_html)

In this example, foo_html will be html with the source code wrapped with <div class="codehilite"><pre>. The syntax coloring is powered by span elements and css classes; you just need to link to a css file. Pygments has about 20 built-in styles (check out their demo page for examples). You can generate the css file for the friendly style with this command:

:::bash
$ pygmentize -f html -S friendly -a .codehilite > mycssfile.css

With Flask/Jinja, using template filters is the most elegant way to integrate Markdown. Dan Colish as written a small jinja extension to enable it. Assuming his extension is the module flaskext, then a simple Flask app with Markdown and code highlighting would be:

:::python
from flask import Flask, render_template
from flaskext.markdown import Markdown
import yaml

app = Flask(__name__)
md = Markdown(app, extensions = ['codehilite'] )

@app.route('/')
def index():
    post = yaml.load(file('content/mypost.yaml', 'r'))
    return render_template('mytemplate.html', post=post )

if __name__ == '__main__':
    app.run()

If mypost.yaml is:

:::yaml
title: 'A Dojo Object'
date: 2010-11-01
body: |
    Let's do something in dojo

        :::javascript
        var b = dojo.byId('slideshow');
        var mydiv = dojo.create('div', {'class':'foo'},b,'after'};
        console.log("and that's about it");            

Then you could render it in the template like so:

:::jinja
<html><body>
<h1>{{post.title}}</h1>
<p class='date'>{{post.date}}</p>
{{ post.body|markdown|safe }}
</body></html>

Flattening

My site is running on a basic apache+php setup. To publish the static site, I need to save each of the urls has a index.html file. This is a straightforward programming exercise. They key is that flask has a test_client() that takes a url as an argument and returns the html of the rendered page.

:::python
import shutil, os

# the output directory
FLATDIR = '/path/to/flatten/directory/'

# a list of all URLs on the site
routes = ['/','/about/','/blog/']

def flatten():    
    """
    Loops through urls in routes and creates an automatic
    directory structure with index.htm files. When
    uploaded to a standard apache file server, paths will resolve 
    with the same urls as in the local dev environment.            
    """ 
    # delete the whole flatten directory and recreate it
    if os.path.exists(FLATDIR):     
        shutil.rmtree(FLATDIR)
    os.makedirs(FLATDIR)

    for p in routes:
        # the output filesystem path cannot start with a /
        # or os.path.join will assume it is the root of the volume
        outdir = os.path.join(FLATDIR, p[1:] )

        if not os.path.exists(outdir): 
            os.makedirs(outdir)

        with app.test_client() as client:
            resp = client.get(p)
            if resp.status_code==200:
                f = open(os.path.join(outdir, 'index.htm'), 'w')
                f.write(resp.data)            
                f.close()
            else:
                msg = "Error {0} while pressing path {1}"
                print(msg.format(resp.status_code, p))

Pushing out to the server

The final step is to push everything out to the server with Fabric. I’ll save that post for another day, but pushing it out to the server simply involves rsyncing the directory of flattened files and the directory of static content. The end result is a three step creation and publishing process.

:::bash
$ bbedit my/new/blog/post.yaml
$ hg -Am "added my new blog post"
$ fab press push