Open tseaver opened 9 years ago
For reference, demo.py
(updated for Python 3) is:
from zope.configuration.config import GroupingContextDecorator
from zope.configuration.interfaces import IGroupingContext
from zope.configuration.xmlconfig import string
from zope.interface import Interface, implementer
from zope.schema import TextLine
_CONFIG = """
<configure xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/simple">
<meta:groupingDirective
handler="__main__.SimpleParentDirective"
name="parent"
schema="__main__.ISimpleParentDirective"
/>
<meta:directive
handler="__main__.SimpleChildDirective"
name="child"
schema="__main__.ISimpleChildDirective"
usedIn="__main__.ISimpleParentDirective"
/>
</meta:directives>
<configure xmlns:simple="http://namespaces.zope.org/simple">
<simple:parent factory="">
<simple:child />
</simple:parent>
</configure>
</configure>
"""
class DemoString(str):
def __call__(self, *args, **kwArgs):
raise Exception("You rang?")
class ISimpleChildDirective(Interface):
pass
class ISimpleParentDirective(Interface):
factory = TextLine(required=False)
@implementer(ISimpleChildDirective)
class SimpleChildDirective(object):
pass
@implementer(ISimpleParentDirective, IGroupingContext)
class SimpleParentDirective(GroupingContextDecorator):
def factory():
def getFactory(self):
return self.__factory
def setFactory(self, s):
self.__factory = DemoString(s)
return property(getFactory, setFactory)
factory = factory()
def main():
string(_CONFIG)
if __name__ == "__main__":
main()
And it still produces the exception:
Traceback (most recent call last):
File "//src/zope/configuration/xmlconfig.py", line 204, in startElementNS
self.context.begin(name, data, info)
File "//src/zope/configuration/config.py", line 349, in begin
self.stack.append(self.stack[-1].contained(__name, __data, __info))
File "//src/zope/configuration/config.py", line 516, in contained
return RootStackItem.contained(self, name, data, info)
File "//src/zope/configuration/config.py", line 480, in contained
factory = self.context.factory(self.context, name)
File "/tmp/demo.py", line 37, in __call__
raise Exception("You rang?")
Exception: You rang?
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/demo.py", line 65, in <module>
main()
File "/tmp/demo.py", line 62, in main
string(_CONFIG)
File "//src/zope/configuration/xmlconfig.py", line 513, in string
processxmlfile(f, context)
File "//src/zope/configuration/xmlconfig.py", line 295, in processxmlfile
parser.parse(src)
File "//3.7/lib/python3.7/xml/sax/expatreader.py", line 111, in parse
xmlreader.IncrementalParser.parse(self, source)
File "//3.7/lib/python3.7/xml/sax/xmlreader.py", line 125, in parse
self.feed(buffer)
File "//3.7/lib/python3.7/xml/sax/expatreader.py", line 217, in feed
self._parser.Parse(data, isFinal)
File "//Python-3.7.0/Modules/pyexpat.c", line 412, in StartElement
File "//3.7/lib/python3.7/xml/sax/expatreader.py", line 370, in start_element_ns
AttributesNSImpl(newattrs, qnames))
File "//src/zope/configuration/xmlconfig.py", line 213, in startElementNS
None, sys.exc_info()[2])
File "//src/zope/configuration/_compat.py", line 38, in reraise
raise value.with_traceback(tb)
File "//src/zope/configuration/xmlconfig.py", line 204, in startElementNS
self.context.begin(name, data, info)
File "//src/zope/configuration/config.py", line 349, in begin
self.stack.append(self.stack[-1].contained(__name, __data, __info))
File "//src/zope/configuration/config.py", line 516, in contained
return RootStackItem.contained(self, name, data, info)
File "//src/zope/configuration/config.py", line 480, in contained
factory = self.context.factory(self.context, name)
File "/tmp/demo.py", line 37, in __call__
raise Exception("You rang?")
zope.configuration.xmlconfig.ZopeXMLConfigurationError: File "<string>", line 20.6
Exception: You rang?
The suggested fix (looking up the context stack for IConfigurationContext
) still fails with the same error, because SimpleParentDirective
extends GroupingContextDecorator
which is a IConfigurationContext
. But that interface doesn't define factory
(in fact, no interface defines factory
). The mixin class ConfigurationAdapterRegistry
is what defines factory; ConfigurationMachine
extends that class.
So in RootStackItem
we can define
def __getConfigurationContext(self):
context = self.context
while not isinstance(context, ConfigurationAdapterRegistry) and hasattr(context, 'context'):
context = context.context
return context
to fix this direct problem (all the tests pass with that change).
Now we run into problems with SimpleChildDirective
: it's the handler
for the SimpleStackItem
, and it can't be called with arguments (it wants to get passed a GroupingContextDecorator
object); if we adjust that, then it fails because it's not iterable (handler
is supposed to return a list of actions). I'd have to refresh my knowledge about how grouping directives are supposed to work to understand if these are legitimate issues in the demo code.
As noted, ConfigurationContext.action
is another attribute expected to be found on the context
(by SimpleStackItem
), it would need similar treatment if this is indeed a correct fix.
It's not clear to me if this is a bug or a feature. On the one hand, the attributes the StackItems are looking for are undocumented, internal implementation details, meaning that a custom grouping context that accidentally overrides them (as in the example) is not "doing anything wrong" and still gets in trouble.
On the other hand, a custom grouping context could potentially deliberately override them to do something custom and interesting. In fact, just the other day, I saw a generic grouping directive that overrides __getattr__
to watch for factory
and wraps that callable in another callable that invokes a debugger. Such code may be relying on implementation details, but they've been solid implementation details for more than a decade, and at least one of them ('action') is partially documented (though its interaction here isn't).
Maybe the best we can do here is document these details? Or prohibit grouping directives from declaring factory
and action
in their schema?
In https://bugs.launchpad.net/zope.configuration/+bug/274655, Devin (devin@charityfinders.com) reported:
Devin later followed up:
@do3cc asked:
Devin replied:
Devin attached a file: https://bugs.launchpad.net/zope.configuration/+bug/274655/+attachment/714017/+files/demo.py