Python: constrained containers

As it happens, I was playing with the del.icio.us API today because of reasons which I shall hopefully elaborate upon in the coming few days. While cooking up some code to interact with the API, I saw the need to model a basic del.icio.us post entity. For my needs, this entity wouldn’t have to be much more than a dumb dictionary with keys like url, description, etc. So I started out that way but pretty soon I thought, wouldn’t it be nice to be able to write post.url instead of post['url']? Further, wouldn’t it be nice to constrain the keys in the dictionary to those required and prevent typo errors such as post['descrption'] instead of post['description']?

This seemed like the perfect opportunity to put into practice some of the stuff I’d learned about “new” style classes not too long ago. So I wrote the following class which implements the requirements laid out in the preceding paragraph. Since I’m just getting the hang of this stuff, please critique the code!

class Post(object):
    """Class to model a del.icio.us Post. It works like a struct/dict 
    like object which limits keys to a retricted subset, i.e. those
    used in a del.icio.us post."""

    __slots__ = ['description', 'url', 'extended', 'tags']

    def __getitem__(self, key):
        if key in self.__slots__:
            return self.__getattribute__(key)
        else:
            raise KeyError

    def __setitem__(self, key, value):
        if key in self.__slots__:
            self.__setattr__(key, value)
        else:
            raise KeyError('Given key is not allowed in class %s'\
                                % self.__class__.__name__)

    def __contains__(self, key):
        try:
            self.__getitem__(key)
        except:
            return False
        return True
    
    # urllib.urlencode() just needs this beyond the basic stuff above
    def items(self):
        return [(k, self[k]) for k in self.__slots__ if k in self]

And here’s the usage of the class.

>>> p = Post()
>>> p.url
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: url
>>> p.url = 'http://google.com'
>>> p.url
'http://google.com'
>>> p['url']
'http://google.com'
>>> p['desc']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "yummy.py", line 17, in __getitem__
    raise KeyError
KeyError
>>> p['description'] = 'Google homepage'
>>> p['description']
'Google homepage'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com')]
>>> p.tags = 'search'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com'), ('tags', 'search')]
>>> 'extended' in p
False
>>> p.extended = 'homepage of the world'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com'), ('extended', 'homepage of the world'), ('tags', 'search')]
>>>

So, what am I doing wrong? :-)