Python Under the Hood: A simple generic model, part 1

Tonight I was digging through the SQLAlchemy and SQLSoup source code to help figure out what I’m doing wrong with my object mapping for in-memory sqlite. While doing this, I figured out how to make a very simple but powerful “value” object [1]. If you are a beginner or intermediate Python programmer, you may have seen where Python seems to magically add named attributes and methods to instantiated classes (objects). SQLSoup and lxml’s Objectify do this, as well as many other packages and modules. SQLSoup does table and field auto-discovery, saving you the effort of defining the table classes and mappings. When you instantiate against an existing database, like this (from the SQLSoup tutorial)

>>> import sqlsoup
>>> db = sqlsoup.SQLSoup('postgresql://scott:tiger@localhost/test') 

Then you can access tables and fields in an intuitive way, as shown below

>>> user = db.users.filter(db.users.name='John Baldwin').one() 
>>> user.email 
johnbaldwin@example.com

You don’t have to go through the following nonsense to get the email address for a particular object (table row)

 >>> email = user.get_field('email') 

This is a very common pattern in Python, and one of its many great features. Python, as a dynamically types language, is very powerful, but can cause a lot of problems if you don’t treat this power with respect and pay attention to detail. One measure of respect is that you need to be thorough with your testing when writing production code.

So even though I have no practical need at this time, I wrote a generic “value” object that can create all of its attributes from the constructor:


from collections import OrderedDict

class GenericModel(object):

    def __init__(self, *args, **kwargs):

        # probably unnecessary, but guarantees order by the numeric key
        self.attrs = OrderedDict(
            sorted({ k : args[k] for k in range(0, len(args)) }.items()))

        for k,v in kwargs.iteritems():
            setattr(self, k, v)

    def __getitem__(self, key):
        return self.attrs.get(key)

    def __setitem__(self, key, value):
        self.attrs[key] = value

    def __iter__(self):
        return iter(self.attrs.values())

    def __len__(self):
        return len(self.attrs)

Here is an example using GenericModel:


 >>> a = GenericModel('foo', 'bar', 'baz', soup='No soup for you', help='You are on your own')
 >>> a.soup
 'No soup for you'
 >>> a.help
 'You are on your own'
 >>> len(a)
 3
 >>> a[0]
 'foo'
 >>> a[1]
 'bar'
 >>> a[2]
 'baz'
 >>> for i in a:
 ... print i
 ...
 foo
 bar
 baz
 

So what we have is a class which has named attributes for keyword arguments (**kwargs) and can be an iterable for the variable arguments (*args). I will explain what is going on in part 2.

[1] Technically, this is not a value object. A value object is immutable.

Advertisements
Tagged with:
Posted in Computing, Programming, Python