bor.borygmus

A programming weblog by Hao Lian. • A long walk through an angry forest. • A series of memory leaks brought on by senility.

Let’s talk about SQLAlchemy, __init__(), and __repr__()!

It’s another post about Python. And SQLAlchemy. There’s got to be a way to monetize this.

Let’s say we have this, because we are young powerful virile men and women capable of great sexy things.

user_table = Table(
    'user', meta.metadata,
    Column('id', Integer, primary_key=True),
    Column('name', Unicode(100), unique=True),
    Column('strength', Integer))

class User(object): pass

mapper(User, user_table)

If you are lost, maps are on the table in the corner to your left. No, not the sparkly table, that’s Django’s. The solid wooden table. Yes, that one.

Now we journey out into the world, using our new User class.

>>> User(name=u'Gosh McMuffin', strength=10)
[snip]
TypeError: __init__() got an unexpected keyword argument 'name'

Ah. SQLAlchemy is not Magic School Bus ORM. You never defined an __init__() on User. Neither did our mapper() call. And this makes sense: We don’t want a function that monkeypatches an entire class. It gives into The Temptation of Globals; any monkeypatching we do is only available in that thread and we just shot down the chances of anybody reusing our code without growing a few more gray hairs. It’s rude. And, worst of all, it’s un-Pythonic. What to do, what to do.

All right. Fine. We’ll stick out our lower lip, put on that hat that Victorian-era street urchins wore, and plod on.

>>> u = User()
>>> u.name = u'Gosh McMuffin'
>>> u.strength = 10
>>> repr(u)
'<__main__.User object at 0x7ec8a3ec>'
>>> u
<__main__.User object at 0x7ec8a3ec>

Eww. Eww-tropy. A solid __repr__() does two things. One: It gives you valuable debugging information about an object o without having to do dir(o) and then manually printing out o.attr_this and o.attr_that. Two: It hides the pointer address. What’s a pointer? Am Why does it have that weird 0x in the front? Am I still writing Python? My head hurts. My body hurts. We need a solid __repr__().

“But, Hao, pointers are easy!”

So we do the noble thing. We sit our children down, tell them that mommy and daddy are coming home a little late today and that they still love their Jane’s and John’s and Sally’s and Murphy’s but not enough to come home on time. That is:

class User(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.iteritems():
            setattr(self, k, v)

    def __repr__(self):
        data = repr(self.name), repr(self.strength)
        return 'User(name=%s, strength=%s)' % data

And, to test that this works:

>>> u = User(name=u'Gosh McMuffin', strength=10)
>>> repr(u)
"User(name=u'Gosh McMuffin', strength=10)"
>>> u
User(name=u'Gosh McMuffin', strength=10)

You should notice two things. One: This was really easy to do in Python. Python is pretty fantastic; we are happy with the choices we have made in our lives. Two: Having to copy, paste, and manually modify this for every class is going to be a gigantic pain. And, if we squint, we see that it’s mostly automatable. __init__() doesn’t even have to know about User! And __repr__() would do just fine if it could access the user_table.c object.

Turns out, I did all the hard work for you. I wrote two mixin classes a while back that did just this and called it brownstone.sqlalchemy as part of a personal, undocumented Python web framework. (Official advertisement here: Brownstone: A labor of love. Try it today.) The code there for __repr__() is a little longer than you might expect because I added some additional cleverness to handle polymorphic inheritance, which roughly corresponds to a class inheritance hierarchy. For example, if we had class Superuser(User, KiteFlier) and class KiteFlier(object) as mapped classes somewhere.

Here’s what the final code would look like:

class User(InitMixin, ReprMixin):
    __table__ = user_table

>>> u = User(name=u'Gosh McMuffin', strength=10)
>>> repr(u)
"User(name=u'Gosh McMuffin', strength=10)"
>>> u
User(name=u'Gosh McMuffin', strength=10)

Huzzah!

[(December 28, 2009) .]

Abandon your ideas.

Use Markdown+, but not HTML. In code blocks, beware angle brackets.