Posts about turbogears

Impressions of Turbogears 4 months in

I've been a user of Turbogears for 4 months now, working on a client facing app. The app has not gone production yet, so I don't have much insight on deployment, but I have a lot of experience on the development side - I started from scratch - and here is what I learned so far.

Freedom of Choice
Turbogears as a framework is pretty agnostic of different components such as ORM, or template engine. Although there is a default choice, I found it wasn't hard to stray away from it.

Mako
I ended up choosing Mako as the template engine because, coming from rails, I felt kid and genshi were too heavyweight for my taste since they are based on XSLT and requires your markup to be valid XML before it can do anything, which obviously means there's an XML parsing step it has to do. Mako is more like erb in that it's "text-based", e.i. it's perfectly fine to render non-valid XML code. But Mako turned out to be much more than another erb. With Mako you can easily write helper template functions and reuse them everywhere. You can also write inversion-of-control template style functions which take in a partial template and calls it inside its body. It always puzzled me why you couldn't do that with erb or haml or most of the ruby template engines easily. With erb, you have to write a partial view as a separate file, but calling a partial with local parameters is inconvient, you have to write something like:

render :partial => 'my_control', :locals => {:control_id => 'con', :height=> '50px'}

and since this is so inconvient, i usually end up wrapping it with a helper method like:

def my_control(control_id, height)
    render :partial => 'my_control', :locals => {:control_id => control_id, :height=> height}
end


Of course this is partially due to the culture in rails that you normally write helpers in ruby rather than in a template language. In Mako you don't have to do this extra step, which makes me happy. Now if you want to do the inversion of control thing, in erb it's even worse! In mako you would just do this. So, in general, I am able to refactor my views a lot easier with mako and therefore I find myself doing it a lot more often.

SQLAlchemy
SQLAlchemy is a main stream ORM in the python community. It's direction is different from that of ActiveRecord and is a lot more similar to Hibernate of Java but also has similarities to Ambition of Ruby and Linq of .NET. It is similar to Hibernate in that it is very fully featured, has sessional transaction management, and can coupe with a large variety of schemas. It is similar to ambition and linq in that you can contruct queries in your host language in a very succint and elegant way(I know you can build queries in Hibernate's criteria API too, but it's not quite elegant). I like SQLAlchemy a lot! Here's a couple of sqlalchemy tricks I like. First one:

    fields = [
        User.user_name,
        User.display_name,
        User.email_address
    ]
    results = User.query.filter(or_(*[f.ilike('%' + q + '%') for f in fields]))


The above code does a wildcard partial string match of the string q against any of the three fields listed in the fields list. Second example:

    page = User.query[10:20]
           
This looks like array slicing, but no, it's slicing against the query results! It's smart enough to build the query using OFFSET and LIMIT or equivalent. You can easily do pagination with this technique.

Python's Named Parameters
Another thing I like when working with turbogears is python's named parameters. Whereas rubists use the hash as the poor man's name parameters, python has real named parameters, which is not only safer, but more elegant.

Python's Polluted Name Space
I run into this problem once in a while, but I hit on it 2 or 3 times in the last week! In python, list, dict, str, int, etc. are the names of fundamental types in the language, therefore you can't(actually you can, but don't want to) use them as variable names. More than once I've tried to use list as a variable name, which python doesn't complain about immediately but causes a cryptic error down the road.
I've also tried use from as a variable name, which turns out to be a keyword in the language, this causes a syntax error, which you don't see until you realize it's a keyword. Now, of course, more languages suffer from this problem, ruby isn't any different, but I think ruby has better error messages for these syntax errors: it will tell you the symbol that is unexpected and what symbols it was expecting.

Little Verbosity
Turbogears is more verbose than rails most prominently in 2 areas: import statements and method decorators. Rails files, usually have at most 2 requires(counterpart of import in python), most of the time none at all. My TG controller files and the model.py(the file with all the model objects) usually have about 10 to 15 lines of includes, my Mako template files usually 2 to 4 lines of includes. This has a lot to do with the design of the language. The python interpreter requires each .py file to act like a module, and as a module, it must identify all of its dependences, the ruby interpreter does not require this and so your controller code, for example, doesn't need to explicitly require anything before it has what it needs to do its work.
I think method decorators are cool, but they can also be overused and become cluttery. Some of my controller methods have more than 4 or 5 lines of decorators, consisting of the mandatory expose(), access restriction spec, and form parameter validators. That's a bit much. I also don't like the fact that you need an expose() decorator for every single controller method. Rails has no such thing and usually specifies such things at the top of the class, which has pros and cons vs TG's approach, but is at the end less cluttery.

Posted by airportyh 5 months ago about programming, python, ruby and turbogears (0 comments)

Turbogears and ReML

I followed the Turbogears 20 minute tutorial here. The first impressions of Turbogears are:
  1. It's a bit more verbose than rails: there's more plumbing - you have to explicitly define which view you want to use for each action, as supposed to doing everything based on convention.
  2. you explicitly pass the local variables to the view as a hash, as supposed to using class or global variables
  3. Turbogears uses a template engine called kid by default, which is very different from rails' erb in philosophy, there's more emphesis on designer friendliness and higher level support for template inheritence
  4. Python/Turbogears in general is safer than ruby/rails - see my last post, in that you usually get more informative errors, such as NameError: global name 'pag' is not defined rather than nil when you didn't expect it
  5. The development feedback is not quite as good as rails. Turbogears requires a restart everytime you make a change. The restart is automatically triggered everytime you save a file in the project, and it is very fast, but it still takes about 5 seconds to rails' 0(ruby has this luxury because of its open classes)
I am hip to Haml so I had to see if I could get it working with Turbogears. I found a couple of implementations: ReML and GHRML. Tried them both, ReML was simpler and more approachable, so I wrote a Turbogears plugin for it. The important bit of the plugin code is here:

from reml import TemplateLoader

class RemlTg(object):
 
  def __init__(self, extra_vars_func=None, options=None):
    pass

  def load_template(self, templatename):
    "Find a template specified in python 'dot' notation."
    parts = templatename.split('.')
    return TemplateLoader('/'.join(parts[0:len(parts)-1])).load(parts[len(parts)-1] + '.reml')

  def render(self, info, format="html", fragment=False, template=None):
    "Renders the template to a string using the provided info."
    return self.load_template(template).render(info)


After that, I converted the views in the tutorial into ReML. Let me do a wc on them for comparison, wait just a minute...

$ wc wiki20/templates/*.kid
  25   76 1068 wiki20/templates/edit.kid
  71  173 2802 wiki20/templates/master.kid
  21   56  773 wiki20/templates/page.kid
  21   50  705 wiki20/templates/pagelist.kid
 138  355 5348 total
airport@wedding-singer ~/documents/play/turbogears/Wiki-20
$ wc wiki20/templates/*.reml
  16   41  492 wiki20/templates/edit.reml
  38   86 1338 wiki20/templates/master.reml
  12   30  327 wiki20/templates/page.reml
   8   24  190 wiki20/templates/pagelist.reml
  74  181 2347 total


So that's about a 50% code reduction, not bad. Here's a Sample for comparison:

page.kid:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://purl.org/kid/ns#"
      py:extends="'master.kid'">
<head>
<title> ${page.pagename} - 20 Minute Wiki </title>
</head>
<body>
    <div class="main_content">
        <div style="float:right; width: 10em">
            Viewing <span py:replace="page.pagename">Page Name Goes Here</span>
            <br/>
            You can return to the <a href="/">FrontPage</a>.
        </div>

        <div py:replace="XML(data)">Page text goes here.</div>
        <p><a href="${tg.url('/edit', pagename=page.pagename)}">Edit this page</a></p>

    </div>
</body>
</html>


page.reml
- append('master.reml')
- def title():
  =page.pagename
- def content():
  %div: 'style':'float:right; width: 10em'
    Viewing
    %span=page.pagename
    You can return to the
    %a: 'href':tg.url('/')
      Frontpage
  %div=unescaped(data)
  %a: 'href':tg.url('/edit', pagename=page.pagename)
    Edit this page


Posted by airportyh 9 months ago about haml, programming, python, rails and turbogears (0 comments)