Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Quick & dirty Snakelets “blog”

Jerry Stratton, April 7, 2007

I wrote this quick and dirty blog to work on some techniques discussed at an ETech tutorial a week ago Monday. This is the first time I’ve taken a tutorial at ETech; normally I find the conference sessions much more compelling and don’t take the extra time away from work to do the tutorials. This year, however, there was a fascinating-sounding tutorial on creating addictive user experiences. And since tutorials come in twos, I also chose the interesting-sounding “Web Heresies”.

The heresy was treating HTML as code rather than design, jettisoning templates and instead creating very simple HTML appropriate for CSS. The corollary to the heresy was that the objects themselves would handle all of their HTML and form processing.

The user experience tutorial ended up cancelled. On coming to the Heresy tutorial, I discovered that I was unprepared. The description indicated I needed a favorite programming language; what I actually needed was:

  1. An object-oriented language
  2. A web application server
  3. Something else I don’t remember

Right. So, for object-oriented language I could use PHP, Perl, or Python, or fake through Ruby which I’ve only used once, and for a few minutes (trying and failing to get Ruby on Rails set up). The instructor was going to use Ruby-like pseudocode, but that didn’t seem like it was going to work well enough for me to learn Ruby in a few minutes.

The only web application server I’ve used (other than the abortive attempt to use Rails) is Django. This inclined me towards using Python as the OO language of choice, but installing Django, quickly, for non-templated, non-databased web development seemed both overkill and difficult. I don’t know how to set up a model without a database backend; I’d probably have to do all of the programming in the view.

So I did a quick web search for python web applications, and came upon Snakelets. It promised to be a quick install with each webapp being nothing more than a Python package. That turned into the most fascinating aspect of the tutorial for me.

Which was surprising, because the documentation for Snakelets (like much in the Python world) can be very cryptic.

The test

On Monday evening, I started thinking about what I could do with Snakelets, and what I could do to try out the heretical techniques we discussed in the tutorial.

What about a really quick and dirty blog for really quick and dirty blogging?

  1. Posts are created by requesting the URL for the post.
  2. Posts can’t be edited once they’re created.
  3. Posts contain only a few fields: a title and a description.
  4. There are no templates. Each object—posts, post lists, pages—know how to create their own HTML.
  5. All layout is handled by CSS.

The only interface for entering information about the blog is the post. The name of the blog, and the blurb about the blog? That’s the post for index.sn, the Snakelets index page name.

Set up Snakelets

  1. Download the Snakelets server. As I write this, the server is currently at 1.44.
  2. gunzip Snakelets-1.44.tar.gz
  3. tar -xvf Snakelets-1.44.tar
  4. cd Snakelets-1.44
  5. python serv.py

That should start the server. Read the text that comes up when you run it, and look for something like “Processing vhost 'George.local' root...”. That’s the hostname Snakelets picked up. You can browse your Snakelets install by going to “http://hostname:9080/”. In my case, “http://george.local:9080/”.

You can change the port by editing serv.py and changing the line that says “HTTPD_PORT = 9080”. Also, make sure that your firewall allows you to access port 9080 (or whatever port you choose). And if you need to change the hostname that Snakelets uses (for example, to localhost) you can change “bindname=None” in serv.py to “bindname=hostname”. You’ll need to restart the server (CTRL-C and then “python serv.py” again) for those changes to take effect.

Once you have the server running to your liking, you can also go to http://hostname:9080/manage/ to see the management screens for Snakelets. The default admin username and password is “test” and “test”. It’s in “webapps/manage/__init__.py”. You’ll want to change that immediately. Change the line:

[toggle code]

  • users = {
    • "test": LoginUser("test", "test", "Test Admin", ["admin"])   # XXX hardcoded test user
  • }

Change the instances of “test”, “test”, and “test” to “username”, “username”, and “password”.

Restart the server and your new username and password will take effect. You can see all of the sample webapps in the interface, as well as other server information.

Your own webapp

Inside the webapps folder, create a folder called “blog”. Create a “__init__.py” file in that folder:

[toggle code]

  • import blog
  • name = "Jerry's Blog"
  • docroot = "."
  • snakelets= {
    • "index.sn": blog.Blog,
    • "*": blog.Blog,
  • }
  • #doesn't seem to be able to specify that a path IS NOT a snakelet
  • #so the CSS files need to go in their own webapp
  • assetLocation = "/library"

Create a file called “blog.py” that contains:

[toggle code]

  • from snakeserver.snakelet import Snakelet
  • #The Blog mediates between Posts and Page
  • class Blog(Snakelet):
    • hitcount=0
    • #respond to the browser's request
    • def serve(self, request, response):
      • #handle this visit
      • out=response.getOutput()
      • out.write("<h2>Hello World</h2>")
      • self.hitcount+=1
      • out.write("<p>Served " + str(self.hitcount) + " request(s).</p>")

If you follow the “Show list & vhosts” link on the management page, you’ll see that it has found our new web app, but it isn’t deployed yet. Go back to the command line and restart the server.

Go to “http://hostname:9080/blog/” and you now have your own custom webapp that you can edit and modify as you see fit. When you make changes to blog.py, Snakelets will almost always automatically reload blog.py; when you make changes to other files, you’ll need to either restart the server or go to the management page for “blog” and reload.

URL patterns

The main part of __init__.py is the snakelets dict. It contains the valid URLs for the webapp. In this Hello World app we’ve set up to URLs: index.sn and *. Index.sn is the default page for Snakelets; * means everything (you can use question marks, bracketed character lists, and asterisks as wildcards, much as on the command line).

The biggest problem (other than RSS) that I ran into is that there is no way to say that a URL pattern does not match a snakelet. This was a problem because of the design constraint that entering a non-existent URL brings up the entry form for that URL. This meant that I need to match all URLs. But since the other design constraint is that all layout is handled by CSS, I needed to have a CSS file whose path is not passed to the snakelet.

One way around this would be to match “library” and send it to a snakelet that will download it, but that would turn this simple problem into a much less simple one. (Not too difficult: look for “ serveStaticFile()”.)

The easiest solution was to make a very simple webapp that doesn’t do anything except contain library files. It doesn’t contain any python files except for __init__.py, and the __init__.py file for it is very simple:

  • name = "Jerry's libraries"
  • docroot = "."
  • snakelets= {}

Inside the library webapp, I put the css file in a folder called “css”. Theoretically, I’ll be able to put other library items there as well, such as images and javascript files. This project won’t use any of that.

The Blog

If you want to look at the example, go ahead and download SnakeBlog.zip and unzip it. The blog folder and the library folder go into your webapps folder, and makeHTML.py goes into your userlibs folder.

There are four parts to the blog, each handled by an object: the Blog, the Page, the Posts, and the Post.

The Blog object is the interface between Snakelets and the web application. Requests get sent to Blog.serve, which sends them off to the methods that need to handle them. The response is also sent to Blog.serve, and the web page is written out to the response.

The Blog object knows what the special pages are (currently, an RSS feed and a special page for copying HTML of the feed); it knows whether or not a request is coming from someone authorized to add new posts.

The Page

If this were a true web heresy, each page would remain an object, keeping track of its visits, it’s current state, and anything else that we need to know about a page. I decided not to do that in this iteration, but rather to have each Page render itself, and then store the rendered page in a list in the Blog object. I hate re-rendering basically static pages for every visit. Even under Django, I use Django locally and then upload the rendered files to my site.

However, if this were a real heresy blog, there would probably be different kinds of Pages inheriting from some basic Page; the Blog instance would know which Page type to insert into the dict of urls and pages, and the Page when called would render itself (or use a cached render that it stored on itself).

Instead, the __init__ for Page checks against the list of special pages before creating a generic post entry.

For example, the Pages object treats the Post object corresponding to “index.sn” as the main page of the site. Not only does it also show a list of all other Post objects, its title and description are used as the title and description of the entire blog.

The Post

The egocentric post knows only about itself. It knows how to request a new post, how to display itself, and how to provide information about itself.

One of the neat things about a Post is that we can add features to it later. As you’ll see when you look at the Posts object, posts are saved as Python objects using the built-in shelve library. The only thing we have to worry about when adding new features to a post is that we can’t assume that the feature exists in all posts. This means extensive use of hasattr().

The Posts

The list of posts knows how to store itself (using Python’s shelve module), how to present a list (either in HTML or RSS), and how to return a specific post. It knows that the main post must also contain a list of all other posts.

It stores Posts using the shelve library. It loads them when the app starts, and saves them whenever a new Post is created. Otherwise it works completely from memory.

makeHTML.py

I’m using the makeHTML.py Python HTML library mainly because I wrote it, but also because, if we’re going to force designers to handle all presentation through CSS it helps to have easily readable HTML for the designers to look at.

Will I use it again?

It was a lot of fun to play around with during spare minutes at the convention. I used it to keep quick notes during presentations, and at the end of the convention wrote a quick method to return them not as a page but as appropriate HTML for my real blog.

I don’t know if I’ll use Snakelets again. But while I’m not going to switch from Django to Snakelets for my main web site, or for any site that I have lots of time to plan for, Snakelets looks like a great choice for sites that need to be created in a few minutes—as I had to do for that surprise tutorial.

I did this using Mac OS X 10.4.9 (on a PowerBook G4 at the conference, and an iMac G5 at home); the blog webapp may require Python 2.3.5 or greater, since that’s what I have installed. I believe that’s the default install on Mac OS X 10.4.

  1. <- Play Backwards
  2. Combine PDF files ->