SQLObject actually has, in its history, a great deal of similarity to
other Python ORMs. Not just the whole wraps-a-database-thing (which
it obviously should have in common), but little implementation
details. For instance, like PyDO and Django, it used to have a list
of columns (instead of using attribute assignment). All of those
projects have changed since then... and probably in a similar way you
can lingering artifacts of that past implementation detail.
One of the ways is how the class is actually constructed. This history
often reflects a past when a class was a mostly-dumb holder of a data
definition. Then some outside code (the ORM itself) looks at the
class definition for special attributes, and constructs a bunch of
stuff. So, for instance, though you do name = StringCol() in
SQLObject, StringCol is just a description of the column. It
doesn't actually do anything, and if you later fetch
MyClass.name you won't get back anything related to StringCol.
Because what is actually happening is that those descriptions are
collected, then the class is built.
This is something I'm trying to move away from in SQLObject, and I
think 0.8 will have some significant progress here. One of the goals
of that progress is to make a distinction between SQLObject and
ActiveRecord, (and, less direction,
from Django's ORM). Because -- admitting that this is a
judgemental and subjective term -- I want SQLObject to be the most
Pythonic of these options. Where Pythonic doesn't just mean
fits-the-language (can't expect a Ruby library to want to fit into Python)
but is a more generic term for Everything That Is Good In Programming.
In this case, there's a specific feature of Python I want to maintain:
backtracking. Python's namespaces and tendency towards functions and imperative
code means that it's fairly easy, given a local bit of code, to
figure out what that code is doing in terms of the larger system. You
can read code inside out, instead of having to figure everything out up front.
Metaprogramming on the whole tends to break
that, because you don't even understand the dialect of code you are
reading, not to mention where methods are implemented and what side
effects they might have. So SQLObject already breaks backtracking; my goal
is to mitigate that.
One instance is joins, which have some annoying surprises in SQLObject
exactly because of the legacy of treatment of classes as declaration. I
have a refactoring of joins (not yet checked in) that will hopefully
clear things up and generally simplify things. Over on the TurboGears
list people wanted syntax similar to Django's for adding instances,
and it would have meant:
class Person(SQLObject):
addresses = MultipleJoin('Address')
p = Person.get(1)
p.addAddress(street='123 W 12th', ...)
I.e., the presence of that join would cause the addAddress method
to be created. Seeing addAdress in code, how would you figure out
what that did? Well, you'd just have to be familiar with how the code
works, because addAddress simply won't exist in any other
fashion. But that's what I want to get away from; what will actually
go into SQLObject will be:
class Person(SQLObject):
addresses = OneToMany('Address')
p = Person.get(1)
p.addresses.create(street='123 W 12th', ...)
If you want to know what's going on, you look up
OneToMany.create(). Well... sadly it won't be that easy, as
OneToMany is a descriptor, and it
actually returns an object that delegates to SelectResults.
There's still a lot to keep track of, and the challenge I'll have is
to figure out a way to present a wider set of core concepts than is
currently in the documentation (for instance, SelectResults is a
really important class, but it's not documented and it's always
instantiated for you). I'm thinking SQLObject docs should move towards a casual
overview, with deep links into generated documentation that is more
complete.