The Artima Developer Community
Sponsored Link

Computing Thoughts
The Web Frameworks Jam and Turbogears
by Bruce Eckel
July 27, 2006
Summary
I think I'm really starting to get this "event" thing down. I can tell because, at the end of the "Web Frameworks Jam," half the people were saying "when are we going to do this again?"

Advertisement

Admittedly it was a small group—eight plus me—but I had as much fun and learned as much as I think I possibly could have, so it didn't really matter. And the small group was actually so nice that I even thought of limiting the size in the future. The manageability alone was great: we could all go to lunch together, hikes were much easier to coordinate, and everyone fit easily into the house and kitchen during barbueques.

It's very probable that the next event, which I hope to hold before the end of the year, will be a TurboGears Jam, because a few of us learned enough about it to jumpstart everyone else in a group, and because it seems worthwhile to focus on one framework (plus this is the one that my team liked the most!).

Blogs and Podcast

Here are some blogs and photos about the event:

You can hear a podcast that we recorded at the end of the workshop . From this, you'll find out that we broke up into three groups. One worked with the Google Web Toolkit (with the help of Jim White, who had experience with it), one worked with Spring and Hibernate (although mostly Spring), and Barry Hawkins, Dianne Marsh and myself worked with TurboGears.

Installation

easy_install rocks, and that's what TurboGears uses. Python finally has a system to rival Perl's CPAN, and it's about time. Philip Eby really put a lot of thought into easy_install, and it should be part of the standard Python library.

Something I'd like to see is all the optional libraries installed as part of the TurboGears easy_install, because those libraries are often used in the tutorials. Tutorials should be created with the attitude that any little setback is going to cause some percentage of people to give up, or postpone it and maybe not come back.

I've been learning a bit more about Python deployment. For example, I started using a GoDaddy account for downloads, and then I began programming on it; it supports Python 2.4. Naturally, GoDaddy controls the Python installation so you cannot just install the necessary TurboGears packages in the site-packages directory. However, the only thing you must actually do is put those files someplace and then provide the proper path information so that Python can import the packages. This appears to be feasible on GoDaddy. I could of course install TurboGears on my own server, but I have a fascination with trying to see how far a cheap service like GoDaddy can be pushed.

What TurboGears Does

The only web programming I've done is with CGI (C++, Python and a little PHP), Java (Servlets, JSPs and JDBC, but only in an educational context and never "in anger," and EJBs just from a theoretical and generally puzzled perspective), a bit of PHP, and a fair amount of Zope. So I, like many at the workshop (and I suspect many people in the business), still don't have that much of a sense of web frameworks in general. I think one would need to work with more of these before the general sense of them would begin to dawn.

I did find, however, that TurboGears gave me a nice perspective on the problem it is intended to solve. I think this is the first time that a web framework's functionality has been so obvious (admittedly, I may have been struggling with the "web problem" long enough that it's time for an epiphany).

I think that the TurboGears philosophy of cherry-picking and assembling the "best of breed" tools is a good one. Of course, what constitutes "best of breed" is determined by Kevin Dangoor and his team, but from what I've seen so far I'm pretty happy to let him do that research and make those decisions.

The experience I often have with libraries and frameworks consists of the "point of disconnect." That is, things make sense for awhile and then at some point an idea is introduced that feels slightly wonky. That's usually the point where the designer's model got stretched too thin, or their domain knowledge was lacking.

So far, everything about TurboGears has fit nicely into my head in a very straightforward fashion, elegantly and what we often refer to as "pythonic." In particular, this is to me the clearest example of where the division into model-view-controller makes sense. The tg-admin quickstart command even produces files called model.py and controllers.py. In fact, the only thing I would call a hiccup in all this is the naming of the @expose decorator. It took us a fair amount of time to figure out what it meant, whereas if it had been named @view instead, it would have been much more obvious. (I'm guessing that expose came from CherryPy, but there's no reason that the TurboGears decorator couldn't be renamed).

You can start every project using tg-admin quickstart. This provides enough of a framework to actually run, so you can get "hello, world" for you application right away, and then begin adding to it. There's even support for unit testing, which Barry looked at and found to be adequate (we often relied on him for opinions, because he's built enterprise systems using other frameworks, notably Java).

The Model, aka Database

The "model" support provided by TurboGears is about easy connection to the database. Of course, the actual model for an application usually comprises more than this, notably business logic. However, in TurboGears that part is considered to vary too significantly between projects and so you write that code yourself (my impression is that EJB was about trying to reuse business objects, and that didn't seem to work out so well).

Once you run tg-admin quickstart, you go into the dev.cfg file and tell it how to connect to your database; this is a single-line URI similar to what you do for JDBC. The tutorial suggests sqlite, but we found MySQL to be more straightforward—although you do need to create the database and establish permissions, this is what you normally have to do with a database anyway.

Although I do not have broad experience with object-relational mapping (ORM) techniques, and Alex Martelli has said that every one that he's seen makes him ill, the one used by TurboGears, called SQLObjects, seems to me to be quite elegant and reasonable. It performs the most common operations without requiring you to write SQL, but if you need to do something special it's easy to drop into SQL. This seems like a good compromise to me.

You create a database model component by writing a class containing static fields and inheriting from SQLObject. Here's an example for a comment collector:


class Comment(SQLObject):
    name = UnicodeCol()
    email = UnicodeCol()
    body = UnicodeCol()

When you run tg-admin sql create, you'll automatically create a table in your database called comment, containing 4 fields: the three you see above, plus an autoincrement id field to uniquely identify each object. You can access id programmatically, and you can create "alternate ID" fields which can also be used to look up objects.

When you create a new Comment object, SQLObject will create a new row in your database. When you assign to one of the fields, SQLObject will update that row (you can turn on batch mode for more efficiency). You can also do things like joins and foreign keys when you define the fields in an object.

SQLObject is not limited to creating its own databases. You can work with an existing database by setting a metadata tag in the class.

It appears that, by the time version 1.0 of TurboGears comes out, they will be using SQLAlchemy because it is significantly faster. However, you will still be able to continue using the syntax of SQLObject, which will be automatically adapted to SQLAlchemy.

The Controller

The structure of the controllers defined in the controllers.py file that tg-admin quickstart generates for you comes from the CherryPy server that TurboGears has selected for the controller system.

For each page in the system, you create controller code as a method in a class, then you indicate to CherryPy (via TurboGears) that you want this method to be called whenever the page is visited. For example, here is the controllers.py file that tg-admin quickstart creates for you:

import turbogears
from turbogears import controllers

class Root(controllers.Root):
    @turbogears.expose("test.templates.welcome")
    def index(self):
        import time
        return dict(now=time.ctime())

You can name your controller class anything you'd like, but it must inherit from controllers.Root. The @expose decorator (which, as I previously mentioned, I'd like to be @view instead) tells the method where it should hand off its data and control when it's finished; in this case it's the kid file test/templates/welcome.kid. The name of the method becomes the last part of the URL used to invoke that method.

If you are submitting a form to one of these methods, the form variables appear as named arguments. So in my comment-collector, if the form contains the fields name, email and body, the controller could look like this:

import turbogears
from turbogears import controllers

class CommentCollector(controllers.Root):
    @turbogears.expose("commentcollector.templates.store")
    def store(self, name, email, body):
        Comment(name=name, email=email, body=body)
        return dict()

The @expose decorator indicates that "store" is a legal page to call, and that the resulting page will be displayed using commentcollector/templates/store.kid.

When you create an SQLObject class, it automatically makes a constructor for you that allows you to pass in the fields for your class. By creating a Comment object, I automatically insert a new row into the database.

When you return a value from the method, you return it as a dictionary (a Python dict, aka map or associative array). Ordinarily the results will be displayed via a Kid template, in which case the dictionary is transparently available inside that template. You can also redirect to another page. It's even possible to display the results as JSON (a kind of second-generation version of AJAX) by simply annotating with @expose("json"). MochiKit is also integrated into TurboGears.

You can create or import multiple classes in your controllers.py file, which allows you to partition functionality into classes that might be reusable.

The View

You display pages using the Kid templating language. This is a simple syntax added on top of HTML, so learning to use it is not onerous. As mentioned earlier, the dictionary that you return from your controller method is automatically passed in and available inside the Kid template. So if you wanted to create a page displaying the comment, after writing a controller method that returns a dictionary with the appropriate data, you could insert that data like this:

<h3 py:content="name">This is replaced.</h3>
<h3 py:content="email">This is replaced.</h3>
<h3 py:content="body">This is replaced.</h3>

Notice how the Kid syntax is relatively succinct, and makes use of the existing html. You can even view a Kid template directly with your browser, which is often quite helpful.

Kid has the usual set of constructs for doing things like looping to display a list, but these are very Python-like so they tend to be easy to read and remember. For example, a for loop:

<ul>
  <li py:for="fruit in fruits">
    I like ${fruit}s
  </li>
</ul>

Notice how it uses the closing html tag to delimit the loop, and compare this with other systems; JSPs for example.

Kid doesn't limit you—you can embed python scripts within your Kid templates if you really want, although you then run the risk of mixing your MVC components together.

One thing I like a lot about Kid and the way TurboGears uses it is the "inheritance" model, so that you can establish the basic look and feel of all your pages in master.kid and then each new page inherits that look and feel (unless you tell it otherwise). This is almost always what you want to do on a site, and I think they've solved the problem nicely.

The Tutorial

During the workshop, we initially had the ambition that we were going to jump in and start building something new right away, but then we got reasonable and decided to just work through the existing tutorial first (the Spring/Hibernate group also decided this). This was the "20-minute Wiki," which ended up taking us about 2 days to get through (admittedly interrupted by hikes and dinners and the like).

It took so long because we kept hitting snags, which we attributed primarily to the fact that TurboGears is pre-1.0 and moving quite quickly—there were three version changes during the workshop, which fixed a number of our issues (thus, the list below may indicate more errors than there actually are). We could certainly understand how it would be difficult to be maintaining the tutorial at the same time, but it made for a hard out-of-the-box experience. In fact, we all agreed that if we hadn't had the other people in our team to keep us going, we would have given up. Despite that, our general experience with TurboGears was that everything was very polished, so we expect that by the time version 1.0 comes out the tutorial should be polished as well.

Dianne created this list with the idea that, if you want to go through the tutorial before 1.0 comes out, it may help you get over some of the trouble spots that we ran into.

When you finish the "20-minute Wiki" tutorial, here's another, more sophisticated tutorial by Brian Beck.

  1. Although the documentation for TurboGears suggests that you run the tutorials before reading the Getting Started guide, we think that it might actually be helpful to do the reading first, mainly because the tutorials aren’t rock solid just yet.
  2. An offline install would be particularly useful in a situation like we were in, where we didn’t have internet connectivity. Availability of the entire documentation tree would be convenient (essential?) as well. This also helps when you have connectivity but not necessarily good response (or when the site is under heavy use).
  3. If you don’t have docutils, install it (separate download). It doesn’t come with the TurboGears easy install.
  4. Tutorial refers to “six” readable lines (in page.kid). Looks like: “5. Deals with Unicode properly (always a good habit, on line 5).” seems to have been omitted. Another typo in same section “in other workds”.
  5. While the tutorial implies that using sqlite is the easiest path to getting a working app, it’s probably easier to use mysql or postgres if you’re comfortable with those. You will need to edit the dev.cfg file, but the skeletons are there.
  6. If you want to use sqlite, download it. SQLite3 is compatible with pysqlite2. You will need both. Same problem if you’re going to use MySQL. Grab the MySQLDB installer (we used 1.2.1). Assuming a similar issue if you want to use postgres (?).
  7. Changes to kid files do not seem to require restarting turbogears. Changes to python files (model.py, etc.) may require restarting turbogears. Getting Started guide claims that server restarts if it sees any changes to any files, but the tutorials instructs us to restart in certain situations, so it’s unclear when you need to restart and when you don’t.
  8. While the tutorial doc seems to imply that the TurboGears tools will create databases for you (by naming the header “Creating a Database”), that’s only partly true (depends on your choice of databases). tg-admin sql create will create databases and tables for sqlite or postgres (given a user with appropriate permissions). The database must already exist for MySQL (and proper permissions must be set), and sql create will then create the appropriate tables. The tutorial doesn’t give indications regarding these distinctions.
  9. It appears that tg-admin sql create must be run from the directory where dev.cfg and prod.cfg live (otherwise, you will get an error message, politely asking you if you want to fix things, but the autofix will fail).
  10. We had a problem with catwalk, when following the directions specified in the wiki20 tutorial. The tg-admin toolbox version of catwalk will work fine if the page already exists, but catwalk generated an error (from CherryPy) rather than offering an option to “Add Page” when run from the toolbox. Instead, we imported catwalk and instantiated it with our model (as directed in the turbotunes tutorial), we were able to add a page from catwalk. Essentially, the wiki20 tutorial will not work as directed (which purposefully leads you down a path of not having a page in order to demonstrate this functionality of catwalk). Instead, either add the info to the database directly or use the following for the controllers.py content:
    import logging
    import cherrypy
    import turbogears
    from turbogears import controllers, expose, redirect
    from wiki20 import json
    from wiki20.model import page
    from docutils.core import publish_parts
    from turbogears.toolbox.catwalk import CatWalk;
    import model
    
    log = logging.getLogger("wiki20.controllers")
    
    class Root(controllers.RootController):
        catwalk = CatWalk(model)
        @expose(template="wiki20.templates.page")
        def index(self, pagename="FrontPage"):
            p=page.byPagename(pagename)
            content=publish_parts(p.data,writer_name="html")["html_body"]
            return dict(page=p, data=content )
    
  11. Some of the variable names in the tutorial (and in the quickstart-generated template) make for unnecessary confusion. Examples follow.
    In the controllers.py file (as directed by the tutorial):
    class Root(controllers.RootController):
        catwalk = CatWalk(model)
        @expose(template="wiki20.templates.page")
        def index(self, pagename="FrontPage"):
            page=page.byPagename(pagename)
            content=publish_parts(page.data, writer_name="html")["html_body"]
            return dict(page=page, data=content )
    
  12. Trying to do this tutorial using 0.8.9 hit the wall when we got to the CatWalk step (adding pages to database), so we decided to abandon 0.8.9 and upgrade to 0.9 for the one team member.
  13. Trying to install SQLAlchemy (e.g., from Debian) is a bad thing to do. TurboGears tries to take advantage of this and you will be unable to run the quickstart-generated code out of the box.
  14. Method for save in controller doesn’t have enough arguments. CherryPy gives an error:
    TypeError: save() got an unexpected keyword argument 'submit'
    
    Bug report review of turbogears revealed the magic fix:
    def save(self, pagename, data, **kwds) or
    def save(self, pagename, data, submit)
    
  15. If you follow the instruction to change the HTTPRedirect to turbogears.url(“/%s” %pagename), you get the following error
    Page handler: <bound method Root.save of <wiki20.controllers.Root object at 0x015D9A50>>
    Traceback (most recent call last):
      File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 105, in _run
        self.main()
      File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 254, in main
        body = page_handler(*virtual_path, **self.params)
      File "<string>", line 3, in save
      File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 210, in expose
        output = database.run_with_transaction(func._expose,func, accept, allow_json, allow_json_from_config,*args, **kw)
      File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\database.py", line 216, in run_with_transaction
        retval = func(*args, **kw)
    File "<string>", line 5, in _expose
      File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 230, in <lambda>
        func._expose.when(rule)(lambda _func, accept, allow_json, allow_json_from_config,*args,**kw: _execute_func(
      File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 252, in _execute_func
        assert isinstance(output, basestring) or isinstance(output, dict) \
    AssertionError: Method Root.save() returned unexpected output. Output should be of type basestring, dict or generator.
    
    Changing to: raise turbogears.redirect(“/%s”%pagename) works fine.
  16. Inconsistency in presentation of source code in documentation is disconcerting. Sometimes, code is shown directly (e.g., wikiwords section). Most other times, only command line instructions are presented directly and code is shown using the “syntax highlighter” style. (Also note: “View plain” is really cool but left for the reader to discover independently).
  17. Using 0.9.a5 (but appears to be working on 0.9a7+), ran into problem with JSON section. Adding @expose(“json”) yielded an error message:
    The server encountered an unexpected condition which prevented it from fulfilling the request.
    @expose("wiki20.templates.pagelist")
    @expose("json")
    def pagelist(self):
      pages = [page.pagename for page in Page.select(orderBy=Page.q.pagename)]
      return dict(pages=pages)
    Page handler: <bound method Root.pagelist of <wiki20.controllers.Root object at 0x015F3D90>>
    Traceback (most recent call last):
    File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 105, in _run
    self.main()
    File "c:\python24\lib\site-packages\CherryPy-2.2.1-py2.4.egg\cherrypy\_cphttptools.py", line 254, in main
    body = page_handler(*virtual_path, **self.params)
    File "<string>", line 3, in pagelist
    File "<string>", line 3, in pagelist
    File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 210, in expose
    output = database.run_with_transaction(func._expose,func, accept, allow_json, allow_json_from_config,*args, **kw)
    File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\database.py", line 216, in run_with_transaction
    retval = func(*args, **kw)
    File "<string>", line 5, in _expose
    File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 230, in <lambda>
    func._expose.when(rule)(lambda _func, accept, allow_json, allow_json_from_config,*args,**kw: _execute_func(
    File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\controllers.py", line 251, in _execute_func
    output = errorhandling.try_call(func, *args, **kw)
    File "c:\python24\lib\site-packages\TurboGears-0.9a5-py2.4.egg\turbogears\errorhandling.py", line 71, in try_call
    return func(self, *args, **kw)
    TypeError: pagelist() takes exactly 1 argument (2 given)
    
    Fix follows, but then get JSON format WHENEVER pagelist is viewed (even without tg_format=json parameter). Tutorial pretty much broken from this point on. Verified by running a version that Bruce had working (same result).
    @expose("wiki20.templates.pagelist")
    @expose("json")
    def pagelist(self, *args, **kwds):
        pages = [page.pagename for page in Page.select(orderBy=Page.q.pagename)]
        return dict(pages=pages)
    
  18. For version 0.9a7, app.cfg has replaced
    tg.mochikit_all = True
    
    with
    tg.include_widgets = ['turbogears.mochikit']
    

Talk Back!

Have an opinion? Readers have already posted 7 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

This weblog entry is Copyright © 2006 Bruce Eckel. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use