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!