diff --git a/pnc/dep/attrdict.py b/pnc/dep/attrdict.py index 08a349e..2761808 100644 --- a/pnc/dep/attrdict.py +++ b/pnc/dep/attrdict.py @@ -1,67 +1,133 @@ -# Modified version of the code featured at the given link +# Heavily modified version of the code featured at the given link ## {{{ http://code.activestate.com/recipes/473786/ (r1) class AttrDict(dict): """A dictionary with attribute-style access. It maps attribute access to - the real dictionary.""" - def __init__(self, init={}): super(AttrDict, self).__init__(init) - def __getstate__(self): return self.__dict__.items() + the real dictionary. + + Note that accesses to preexisting (e.g. class inherited) or reserved + attributes are handled as they would be normally, and will not be + overwritten. + Overload _is_reserved if you want to change this.""" + def __init__(self, init={}): + super(AttrDict, self).__init__(init) + + def __getstate__(self): + return self.__dict__.items() def __setstate__(self, items): for key, val in items: self.__dict__[key] = val + def __repr__(self): - return "{}({})".format( + return "{0}({1})".format( type(self).__name__, super(AttrDict, self).__repr__() ) - def __setitem__(self, key, value): - return super(AttrDict, self).__setitem__(key, value) + + def __setitem__(self, name, value): + return super(AttrDict, self).__setitem__(name, value) def __getitem__(self, name): return super(AttrDict, self).__getitem__(name) def __delitem__(self, name): return super(AttrDict, self).__delitem__(name) + def __getattr__(self, name): - # Basically, fall back on __getitem__ first - # Try to access ourselves as a dict. Failing that, check for attributes - # with the same name. Failing *that*, throw AttributeError to keep - # other code happy. - try: retval = self[name] - except KeyError as err: - try: retval = self.__dict__[name] - except KeyError: - # Raising KeyError here will confuse __deepcopy__, so don't do - # that. - raise AttributeError("No key/attr {!r}".format(name)) - return retval - __setattr__ = __setitem__ + # NOTE: __getattr__ is called if the code has already failed to access + # an attribute on this object. The rest of this code reflects this. + # We could override __getattribute__ to bypass this, but that's not + # worthwhile. + + # We don't do any special handling for reserved names. + if self._is_reserved(name): + # Fall back to normal handling, by force. + return object.__getattribute__(self, name) + + try: + # Try to __getitem__. + result = self[name] + except (KeyError, AttributeError): + # Raising KeyError here will confuse __deepcopy__, so don't do + # that. + # Throw a custom error. + raise AttributeError("No key/attr {0!r}".format(name)) + return result + + def __setattr__(self, name, value): + # Set to the attribute first if it's defined. + # (NOTE: This isn't subject to the same checking as __getattr__.) + # Using dir() also checks non-instance definitions, so things defined + # on a class can be easily set on an instance this way. + + # Detect special/reserved names. + if name in dir(self) or self._is_reserved(name): + # Set it if it's 'special' because we aren't supposed to touch any + # of that - too many potential implementation issues. + # + # Apparently we're also not supposed to set our own dict directly + # in this particular function?... + return object.__setattr__(self, name, value) + else: + return super(AttrDict, self).__setitem__(name, value) + def __delattr__(self, name): - try: del self[name] - except KeyError as err: - try: del self.__dict__[name] - except KeyError: + # We very *specifically* use self.__dict__ here, because we couldn't + # possibly delete a value that doesn't yet *exist* in our instance's + # namespace yet! + # This shouldn't be a problem, since this type should never have + # __slots__. + if name in self.__dict__: + # See __setattr__. + return object.__delattr__(self, name) + else: + try: del self[name] + except KeyError as err: raise AttributeError(str(err)) + + @staticmethod + def _is_reserved(name): + """Check if an attribute name is reserved for system use.""" + # A very simple method. + result = name[:2] == name[-2:] == '__' + return result + def copy(self): return type(self)(self) __copy__ = copy ## end of http://code.activestate.com/recipes/473786/ }}} class DefAttrDict(AttrDict): + default_factory = None + def __init__(self, default_factory=None, *args, **kwargs): - self.__dict__["default_factory"] = default_factory + self.default_factory = default_factory super(DefAttrDict, self).__init__(*args, **kwargs) + def __repr__(self): - return "%s(%r, %s)" % ( + return "{0}({1!r}, {2})".format( type(self).__name__, self.default_factory, - super(AttrDict, self).__repr__() - ) + # We skip normal processing here, since AttrDict provides basic + # repr for classes in general, which we don't want. + dict.__repr__(self) + ) + def __getitem__(self, name): try: - return super(DefAttrDict, self).__getitem__(name) + result = super(DefAttrDict, self).__getitem__(name) except KeyError: - ##if self.default_factory is None: return None - ##return self.default_factory() result = None if self.default_factory is not None: result = self.default_factory() self[name] = result - return result - __getattr__ = __getitem__ + return result + + def __getattr__(self, name): + try: + result = super(DefAttrDict, self).__getattr__(name) + except AttributeError: + # Detect special/reserved names. + if self._is_reserved(name): + # We shouldn't automatically fill these in. + # Pass this along. + raise + return result + def copy(self): return type(self)(self.default_factory, self) + __copy__ = copy