# AttrDict Python module: a convenience wrapper to Python's dict. # Author: Steven Brown # Homepage: http://stevenbrown.ca # Copyright (C) 2008 Steven Brown # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Simple extension of the built-in dictionary so that dictionary keys are mirrored as object attributes. This is for convenience. You can do things like this: d = dict() #standard dict, for comparison a = AttrDict() # d['ok'] = 'this is ok' d.ok -> AttributeError raised d.ok = 'test' -> AttributeError raised You cannot assign attributes to standard Python dicts. a['ok'] = 'this is ok' a.ok -> 'this is ok' #attribute is automatically created a.ok = 'changed' a['ok'] -> 'changed' #and the dict value is automatically updated a.ok2 = 'new value' #adding new attribute, ok2 a['ok2'] -> 'new value' #dict key is automatically created This introduces a limitation on the dictionary keys such that they must be strings and provide valid Python syntax for accessing. For example: {'123':'valid'} #is a valid dictionary but mydict.123 #is not valid Python syntax. Attempting to create a key that cannot be accessed through an attribute name will raise an AttributeError Exception. This module has not been built for optmization. Some of the method documentation has been taken from Python's dict documentation. For more info on a particular method, refer to that. I've tried to mirror the effects of the standard dict as closely as possible. """ __version__ = "0.1" # Create a namespace with an object to use for testing syntax test_namespace = {} exec ("class O(object):pass",test_namespace) exec ("o = O()",test_namespace) #FIXME - hide this further within the module? class AttrDict(dict): def __init__(self, *args): """Takes an optional dict or AttrDict object to initialize with.""" super(AttrDict,self).__init__() if len(args) > 1: raise TypeError("__init__ takes at most 1 argument (%i given)" % len(args)) elif len(args) == 1: d = dict(args[0]) self.update(d) def update(self,*d,**F): """D.update(E, **F) -> None. Update D from E and F: for k in E: D[k] = E[k] (if E has keys else: for (k, v) in E: D[k] = v) then: for k in F: D[k] = F[k]""" if len(d) < 1: pass elif len(d) > 1: raise TypeError("update expected at most 1 arguments, got %i" % len(d)) else: d = d[0] if type(d) != dict: d = dict(d) for (k,v) in d.items(): self[k] = v #kwargs for (k,v) in F.items(): self[k] = v assert(self.__is_balanced()) def clear(self): "D.clear() -> None. Remove all items from D." for k in self.keys(): del self[k] assert(self.__is_balanced()) def __repr__(self): return "AttrDict(" + super(AttrDict,self).__repr__() + ")" def __getitem__(self, i): try: a = getattr(self,i) except AttributeError: raise KeyError(i) return a def __setitem__(self, i, val): "Sets the object's dictionary item i and attribute i to val. \ If i is not of type str, a TypeError is raised. \ If i yields invalid attribute syntax, an AttributeError is raised." # test i to make sure it's a string... if type(i) != str: raise TypeError("Item key must be a string, not '%s'" % type(i)) # ...which yields valid attribute syntax if not self.__yieldsValidSyntax(i): raise AttributeError( \ "Dictionary Key must valid attribute name. 'my_object.%s' is not valid syntax." % i) self.__setattr_helper(i, val) def __setattr__(self, a, val): if not self.__yieldsValidSyntax(a): raise AttributeError( \ "Attribute name not valid. 'my_object.%s' is not valid syntax." % a) self.__setattr_helper(a, val) def __yieldsValidSyntax(self, a_str): """Returns True if a_str will be acceptable to python through the dot operator: my_object. is possible. Python keywords,""" try: #Pass in a namespace with pre-created object 'o' test_code = "o."+ a_str +" = 0; del o."+ a_str exec(test_code, test_namespace) except SyntaxError, e: return False return True def __setattr_helper(self, a, val): super(AttrDict,self).__setattr__(a,val) super(AttrDict,self).__setitem__(a,val) assert(self.__is_balanced()) def __delattr__(self,a): super(AttrDict,self).__delattr__(a) super(AttrDict,self).__delitem__(a) assert(self.__is_balanced()) def __delitem__(self,i): self.__delattr__(i) def get(self,k,d=None): """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" return super(AttrDict,self).get(k,d) def setdefault(self,k,d=None): "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" if self.has_key(k): return self.get(k,d) self[k] = d return d def pop(self, k, d=KeyError): """ D.pop(k[,d]) -> v, remove specified key and return the corresponding valu If key is not found, d is returned if given, otherwise KeyError is raised """ if d != KeyError: v = super(AttrDict,self).pop(k,d) else: v = super(AttrDict,self).pop(k) if hasattr(self, k): super(AttrDict,self).__delattr__(k) assert(self.__is_balanced()) return v def popitem(self): """D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty""" if len(self.keys()) <= 0: raise KeyError("AttrDict is empty.") k = self.keys()[0] v = self.pop(k) assert(self.__is_balanced()) return (k, v) #TODO def fromkeys():pass #a.fromkeys(seq[, value]) Creates a new dictionary with keys from seq and values set to value #fromkeys() is a class method that returns a new dictionary. value defaults to None. New in version 2.3. #TODO make public? def __is_balanced(self): """Returns True if the dictionary items and attributes are equal. They should be at the end of all operations. Returns False otherwise.""" return vars(self) == dict(self) #FIXME - kill me, or make me beautiful @classmethod def is_synced(cls,attrdict,show=True): if type(attrdict) != AttrDict: raise TypeError("Expecting AttrDict type.") for key in attrdict.keys(): if show: print "d[%s] is d.%s ('%s') :: " % \ (key, key, getattr(attrdict,key)), res = attrdict[key] is getattr(attrdict,key) if show: print res if not res: return False return True