Phillip Eby and Python

Some random remarks of no consequence concerning Python

Posted by 12/19/2006

I can't decide if Phillip Eby is the best thing, or the worst thing, to happen to Python. PyProtocols, RulesDispatch, Easy Install, WSGI they all sound like great ideas - sort of. And the amount of work that has gone into them is astounding. the whole PEAK stack is ... is ... great. Right? Really, I swear. So what is the problem?

Problems

1) I can't read the documentation. I can't stand to read it. It makes me want to claw the skin off my face. I don't know what the deal is. 2) I can't tell how I'm supposed to use anything. 3) Things don't work like I except them to and I get weird error messages. Setuptools is supposed to be like Ruby's gems but so far about every 3 times I run 'easy_install [package]' - I get some kind of message about not having the correct version of setuptools, and even though it knows it has the wrong version - it says it can't possibly install the new version. This does not make sense to me. So I've reverted to always downloading ez_setup.py every time. Which is close to defeating the purpose.

More and More and More Python Frameworks

Why am I harping on Phillip Eby? Just because, being a Web programmer by trade, I've been looking at various Python packages lately - and most of them are using easy_install. And the problems I've experienced with that package reminded me of all the other problems I've had with all the other packages he is responsible for. But anyway, I was looking at the latest Python web frameworks, Clever Harold and Pylons. Despite the promising beginning, both these frameworks are lacking a fundamental thing: A good html form input handling strategy. To me that's the wheat of the chaff of the Ruby on Rails and Django frameworks. Django is problematic when you don't use their generic Add/ChangeManipulator objects, (they are working on that) but being able to do {{ form.contact }} - in my html code and have it take care of every aspect of editing that object is worth money to me. The code {{ form.contact }} in Django accomplishes the following:

  1. produces a select box of all the contacts
  2. converts the value selected to the foreign_key value and back again
  3. validates the input
  4. converts to, from python - without having to enumerate through every field

Ruby on rails has a different approach. But with very little work I accomplish this:

  <%= select_contact 'project' 'contact' %>
   ...

in the view and

   ...
   @project.update_attributes(params[:project])
   ...

in the controller and I've taken care of nearly everything. I don't have to do something like this:

  try:
     project = first_find_the_project(request.POST['project_id'])
     project.contact_id = convert_to_python(request.POST['contact_id'], int)
     project.start_date = convert_to_python(request.POST['start_date'], datetime)
     (field after field) etc ...
     project.save()
  except KeyError, ConvertToPythonError, SaveToDatabaseError:
     bluh, bluh

This is not actually code I'm using, but you get the idea. It's ugly. It's way too involved with the internal workings of the web server, and I have to do the same thing over and over again. But with Django and Ruby on Rails, there is a world of things I no longer have to think about. Which is nice, because I don't like thinking about those things. I don't like murking in the world of converting string to types and back again. Which is the exact same disconnect as exists between the keyboard and the computer itself. It's a problem that's been solved thousands of times and it continues to be solved every day. Like a lot of things. Recursively copying the files in a directory, for instance. That operation should have been part of every language at least 10 years ago.

Ian Bicking

Another programmer I can't decide about, but whom I generally think of favorably is Ian Bicking. His package FormEncode looks kind of helpful for html forms and type conversions. But just like SQLObject it's a little wan on documentation. The classic example being the line "This is deprecated, as it's not that helpful." found here - when it actually does seem helpful, and it's not obvious at all how to do the same thing some other way. I'm not the only one that finds that is having problems with this. So, Ian - please explain further. Most of us are not as smart as you. Nothing personal. I'm just saying. At the same time, just like SQLObject, it's __mostly__ possible to get __most__ of what you want done with this library. And the elusive Paste is much the same.

Pylons? - Okay, I guess

Pylons seems to be using FormEncode, and Paste so it is, like those packages, sufficient. The approach is similiar to Rails, which, despite what some people say, I think is worth emulating in Python. So I mostly like Pylons. But the mapping from to_python to the domain object is not obvious - so the form handling strategy is still a grey zone. At one point the Pylons documentation suggests that they will be moving to a Widget system, like Turbogears. This is a little disheartening. To start working with a web framework that tells you a major part of it has not been fully thought through and will probably be replaced. I'm just saying.

Colubrid to the rescue

I've been using Colubrid combined with SQLAlchemy lately. Just experimenting. I can't decide whether I like SQLAlchemy. Sometimes I do. And sometimes I think the crude one-to-one, table-to-object approach that ActiveRecord uses - mixed with the occassional foray into pure SQL - is just fine. Because I usually only need simple objects when I'm getting information into the database. And when I'm getting data out of the database, and I'm trying to be efficient, nothing really beats SQL. But SQLAlchemy is good at accomodating both approaches. Here's an example where I needed straight SQL - and what I wrote using SQLAlchemy:

def weekly_report(user_id, upper, lower):
    
    project_table = mm_project
 
    monday_when = [("DAYNAME(date) = 'Monday'", "g_billing.time")]
    tuesday_when = [("DAYNAME(date) = 'Tuesday'", "g_billing.time")]
    wednesday_when = [("DAYNAME(date) = 'Wednesday'", "g_billing.time")]
    thursday_when = [("DAYNAME(date) = 'Thursday'", "g_billing.time")]
    friday_when = [("DAYNAME(date) = 'Friday'", "g_billing.time")]
    saturday_when = [("DAYNAME(date) = 'Saturday'", "g_billing.time")]
    sunday_when = [("DAYNAME(date) = 'Sunday'", "g_billing.time")]

    report = select([g_billing.c.id, g_billing.c.user_id, g_billing.c.projectId, 
                    auth_user.c.username, project_table.c.label,
                    func.sum(case(monday_when, else_="null")).label('monday'),
                    func.sum(case(tuesday_when, else_="null")).label('tuesday'),
                    func.sum(case(wednesday_when, else_="null")).label('wednesday'),
                    func.sum(case(thursday_when, else_="null")).label('thursday'),
                    func.sum(case(friday_when, else_="null")).label('friday'),
                    func.sum(case(saturday_when, else_="null")).label('saturday'),
                    func.sum(case(sunday_when, else_="null")).label('sunday')],
                    and_(g_billing.c.date.between(bindparam('lower'), bindparam('upper')), 
                        g_billing.c.user_id == bindparam('user_id'),
                        g_billing.c.projectId==project_table.c.id),
                    from_obj=[auth_user.join(g_billing, g_billing.c.user_id==auth_user.c.id)],
                    group_by=[g_billing.c.projectId],
                    engine=engine).compile()
    
    weekly_report = report.execute(user_id=user_id, lower=lower, upper=upper)

    for record in weekly_report:
        print record


And this is how I did it in SQL (created as a Django template):


SELECT g_billing.id as id,
    g_billing.user_id,
    projectId,
    auth_user.username,
    {{ project_table_name}}.label as label,
    SUM(case when DAYNAME(date) = 'Monday' then time else null end) as monday,
    SUM(case when DAYNAME(date) = 'Tuesday' then time else null end) as tuesday,
    SUM(case when DAYNAME(date) = 'Wednesday' then time else null end) as wednesday,
    SUM(case when DAYNAME(date) = 'Thursday' then time else null end) as thursday,
    SUM(case when DAYNAME(date) = 'Friday' then time else null end) as friday,
    SUM(case when DAYNAME(date) = 'Saturday' then time else null end) as saturday,
    SUM(case when DAYNAME(date) = 'Sunday' then time else null end) as sunday

    FROM g_billing

    INNER JOIN auth_user on (g_billing.user_id = auth_user.id)
    INNER JOIN {{ project_table_name }} on (g_billing.projectId = {{ project_table_name }}.id)
    
    WHERE g_billing.user_id={{ user_id }} AND (date <= '{{ lower }}' AND date >= '{{ upper }}')  
    GROUP BY projectId

Observation #1: The SQLAlchemy code is a little ugly - it obsfucates what's going on - it nests and nests into itself forever like a good Scheme function lost in it's own self-absorbed glory. And the SQL code is easier to read, but ... Observeration #2: It sure lets me do things that are completley impossible in Hibernate. And it lets me organize my sql code the same way I organize my Python code. I can put the daily summation code func.sum(case(monday_when, else_="null")) anywhere. I can factor out the 'monday_when', 'tuesday_when' parts into an iterative function for the days of the week. The possibilities are endless. And I can't really do that either in the database, or using your typical ORM. And I still could have just called the SQL code directly anyway. So my vote is +1 for SQLAlchemy.

But back to Phillip Eby. He's a great guy, he's doing great work - but the self-help books are a little scary, and everything on PEAK site seems written for expert Python programmers - not someone just trying to use a package quickly. And on examining the usage, it starts looking like Javafication of Python to me. Increased complexity, increased indirection. Not necessarily with any payoff. But at least he outlined some of the problems I myself felt but could not quite articulate with the decorator syntax here. It seems this problem was talked to death though. And anything I say will certainly have no effect on anything - but for the record I like this proposal:

def bar(low,high):
    .accepts(int,int)
    .returns(float)
    """docstring"""
    pass

def longMethodNameForEffect(longArgumentOne=None,
                            longArgumentTwo=42):
    .staticmethod
    .funcattrs(grammar="'@' dotted_name [ '(' [arglist] ')' ]",
               status="experimental", author="BDFL")
    """
    docstring
    """
    raise NotYetImplemented

found here But that's just me. I think of a decorator as a specialization of a function, rather than a function itself - and the fact the the __doc__ string, for whatever historical reasons, ended up after the function declaration proves my point. The brain wants a heading, a summary - and then, possibly, more detailed information. The brain does not want to start with the detailed information.

What am I blathering on about. I don't know actually. There is no coherence to this post. I'm just saying.

Comments

Post a comment


Total: 1.74 Python: 1.38 DB: 0.36 Queries: 31