malthe/chameleon — pull request #152 by ossmkitty (2013-09-15 13:41:47+00:00)
Title: RepeatDict raises KeyError on getattr for missing attrs (which breaks zope.interface on PyPy)

The class `chameleon.tal.RepeatDict` copies `dict`s version of `__getitem__` directly into its `__getattr__` function. Doing so causes problems for missing attributes because, while `__getitem__` is supposed to raise `KeyError`, `__getattr__` is supposed to raise `AttributeError`. This commit makes `__getattr__` do the expected thing.

When Python's `getattr` function is called with three arguments to provide a default value for missing attributes, it catches `AttributeError` but allows `KeyError` to pass through. Before this commit this caused the default to be ignored and the caller to deal with an unexpected exception.

One practical place this was a problem was in using Chameleon under PyPy. We use Chameleon with the Zope traversal model, which uses Zope interfaces, and under PyPy `zope.interface` is implemented in Python code making heavy use of `getattr`. With `getattr` throwing `KeyError`, interface adaptation was broken:

File ".../chameleon/src/chameleon/tal.py", line 433, in chameleon.tal.RepeatDict
Failed example:

import interfaces
interfaces.ITALESIterator(repeat,None) is None

Exception raised:
Traceback (most recent call last):
File "", line 3, in
interfaces.ITALESIterator(repeat,None) is None
File ".../zope.interface-4.0.5-py2.7.egg/zope/interface/interface.py", line 129, in __call__
conform = getattr(obj, '__conform__', None)
KeyError: '__conform__'