I've been trying to find a way to do this since Martin Blais demonstrated the namedtuple he uses in his antiorm. I tried to directly use namedtuple, but it's a bit unusual in how it works and I couldn't adapt it to my existing code. Tonight I took another stab at getting this functionality, and ended up with something that seems to be working.
I haven't yet pushed this out to pypi, because I'm hoping to get some comments on my approach here. I'm almost certain that the way I'm doing this is cheating and may lead to problems, but my tests all succeed! Code is currently available as HEAD from github.
Read on for more details.
namedtuple does this thing where it creates a new class dynamically with properties for each of the attributes you need in it, so you're dynamically creating new classes as you make them.
In my case, I want my namedtuple to also work as a dictionary, which is how psycopgwrap has historically worked (you can access the first element either by it's name like a dictionary, or positionally like a tuple).
In the end I created a class based on the "list" class, passing the database row to the initializer. Then I override "getitem" and access the passed index on the original row object (which works for access as a tuple or dictionary). I also override "getattr" and if the requested attribute is in the original row object, I return that, otherwise I look it up in the original row as a key. This means that methods like "keys()" will override a database column of the same name.
If the attribute starts with a leading underscore, it will look up the attribute as a column name as well. My thinking is that this would be a good way to reduce the opportunity for collisions, for example if you have a column named "items".
Here's a sample of the code that implements this:
class RowHelper(list): def __init__(self, row): super(RowHelper, self).__init__(row) self._original_row_object = row def __getattr__(self, attr): if hasattr(self._original_row_object, attr): return getattr(self._original_row_object, attr) if attr.startswith('_') and self._original_row_object.has_key(attr[1:]): return(self._original_row_object[attr[1:]]) if self._original_row_object.has_key(attr): return(self._original_row_object[attr]) getattr(self._original_row_object, attr) def __getitem__(self, index): return self._original_row_object[index]
Again, if you have any comments on this approach, please let me know. The code is available as HEAD on github where I host this project.comments powered by Disqus