Phillip Eby and Python
Some random remarks of no consequence concerning Python
Posted by 12/19/2006
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:
- produces a select box of all the contacts
- converts the value selected to the foreign_key value and back again
- validates the input
- 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