After a conversation with my friend and co-worker Jesse Davis last week about Python web frameworks, I had an idea: what if there were a Python web framework that combined some of the simplicity of workflow and deployment of PHP with the readability and embodiment of best practices of Python? This should be a framework targeted towards folks who want to (or need to) learn web development, but don't have the background, interest, or time to learn one of the heavy-weight frameworks (like Pyramid or Django) or middle-weight frameworks (like Flask or CherryPy). This framework should let you get started immediately, and let you move smoothly from static sites to dynamic sites, let you learn best practices of both Python and web programming, and should not stand in your way when it comes time to go live or scale up.

Well, it turns out something almost like this already exists, Aspen. Aspen has a number of very nice features: view code and template code combined in one file, but without being fully mixed in like PHP; leveraging the filesystem for URL mapping; and a relatively simple set-it-up-and-try-it-out story. It's got a few things I really don't like, too: the use of ASCII page breaks (^L characters) to separate Python from templates; lots of configuration; and about 5,300 lines of code.

So, inspired by what's great about Aspen, and determined not to duplicate what's not, I give you Keystone!

Hang on, another Python web framework?

Yes. It's OK. There are a lot of them. Choice is good. Let's move on.

Keystone's Main Idea

Keystone's views combine Python and (Jinja) template code into the same file, which must end with extension .ks. To avoid the tag-and-code soup that most people start out with in PHP, Keystone separates the code and template with four hyphens. No Python is allowed below the line, and no literal template code above it:

import random
name = random.choice(['Pal', 'Friend', 'Buddy'])
----
<!doctype html>
<html>
  <head>
    <title>Greetings from Keystone</title>
  </head>
  <body>
    <h1>Hello, {{name}}</h1>
  </body>
</html>

This does pretty much exactly what you'd expect: on each request, the Python code is executed and randomly assigns one of "Pal", "Friend", or "Buddy" to the name variable; that variable is then injected into the template's rendering context, and the template renders the HTML to be returned to the user.

You can use all of Python in Keystone views, import standard, third party, or custom modules, etc. All in-scope variables at the end of execution are made available to the template for rendering (i.e. there's something like an implicit return locals() at the end of the view code).

Keystone also injects a number of variables into your Python code: request which contains method, cookies, headers, and other helpful attributes; (response) headers for controlling the HTTP response; set_cookie and delete_cookie; and a few others.

Mapping URLs

The URL of a Keystone view is its path, relative to the application root. In other words, GET / will execute $APP/index.ks; GET /foo will execute $APP/foo.ks; GET /foo/bar/ will execute $APP/foo/bar/index.ks; etc.

Keystone also allows for parameterized paths, by prefixing the name of any directory or .ks file within the application root with "%". So GET /account/dcrosta would execute $APP/account/%username.ks, and within that view, the variable username would have the value "dcrosta".

Running Keystone

Keystone comes with a keystone command-line script which runs a development web server, including in-browser traceback display and inspection.

When it comes time to deploy a Keystone application, the keystone script can generate boilerplate code for you automatically in several of the popular Platform-as-a-Service providers (currently Heroku, DotCloud, or ep.io, and possibly more in the future) or a stock WSGI script.

Ready to Begin?

Check out Keystone's documentation for beginners and experts alike or source repository, and dive in. I'd love to hear feedback on all of the above, so please leave a comment.