Activity log for bug #819815

Date Who What changed Old value New value Message
2011-08-02 13:31:31 LeoRochael bug added bug
2011-08-02 13:51:47 LeoRochael description This is important for chameleon itself, which clones exceptions while formatting them. Here is an example traceback. Down below you'll find ipython session with demonstration appropriate for turning into (part of) an unit test, and a discussion on alternative fixes: /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/Zope2-2.12.19-py2.6-linux-x86_64.egg/Products/PageTemplates/ZopePageTemplate.py in pt_render(self, source, extra_context) 430 431 def pt_render(self, source=False, extra_context={}): --> 432 result = PageTemplate.pt_render(self, source, extra_context) 433 assert isinstance(result, unicode) 434 return result /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/Zope2-2.12.19-py2.6-linux-x86_64.egg/Products/PageTemplates/PageTemplate.py in pt_render(self, source, extra_context) 78 showtal = True 79 return super(PageTemplate, self).pt_render(c, source=source, sourceAnnotations=sourceAnnotations, ---> 80 showtal=showtal) 81 82 /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/zope.pagetemplate-3.5.2-py2.6.egg/zope/pagetemplate/pagetemplate.py in pt_render(self, namespace, source, sourceAnnotations, showtal) 111 TALInterpreter(self._v_program, self._v_macros, 112 context, output, tal=not source, showtal=showtal, --> 113 strictinsert=0, sourceAnnotations=sourceAnnotations)() 114 return output.getvalue() 115 /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/five.pt-2.1.4-py2.6.egg/five/pt/patches.py in __call__(self) 125 context['repeat'] = RepeatDict(self.repeat) 126 --> 127 result = self.template.render(**context) 128 129 self.stream.write(result) /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/z3c.pt-2.1-py2.6.egg/z3c/pt/pagetemplate.py in render(self, target_language, **context) 123 124 base_renderer = super(BaseTemplate, self).render --> 125 return base_renderer(**context) 126 127 def __call__(self, *args, **kwargs): /opt/slapgrid/c980eff74363615a0916a57227138da5/eggs/Chameleon-2.2-py2.6.egg/chameleon/zpt/template.py in render(self, encoding, translate, target_language, **k) 115 if 'repeat' not in k: k['repeat'] = RepeatDict({}) 116 --> 117 return super(PageTemplate, self).render(**k) 118 119 def include(self, stream, econtext, rcontext): ------------------------- Here is a specific demonstration of the bug: In [1]: import chameleon.exc, copy, cPickle In [2]: template_error = chameleon.exc.TemplateError(msg='a test', token=chameleon.exc.Token('a token')) In [3]: copy.copy(template_error) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /srv/slapgrid/slappart0/<ipython console> in <module>() /opt/slapgrid/c980eff74363615a0916a57227138da5/parts/python2.6/lib/python2.6/copy.pyc in copy(x) 93 raise Error("un(shallow)copyable object of type %s" % cls) 94 ---> 95 return _reconstruct(x, rv, 0) 96 97 /opt/slapgrid/c980eff74363615a0916a57227138da5/parts/python2.6/lib/python2.6/copy.pyc in _reconstruct(x, info, deep, memo) 321 if deep: 322 args = deepcopy(args, memo) --> 323 y = callable(*args) 324 memo[id(x)] = y 325 if listiter is not None: TypeError: __init__() takes exactly 3 arguments (1 given) In [4]: cPickle.dumps(template_error) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /srv/slapgrid/slappart0/<ipython console> in <module>() /opt/slapgrid/c980eff74363615a0916a57227138da5/parts/python2.6/lib/python2.6/copy_reg.pyc in _reduce_ex(self, proto) 75 except AttributeError: 76 if getattr(self, "__slots__", None): ---> 77 raise TypeError("a class that defines __slots__ without " 78 "defining __getstate__ cannot be pickled") 79 try: TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled -------------------------------- At least for copying problem, the issue seems to be that the signature required by TemplateError.__init__() is not compatible with what is returned by TemplateError.__reduce_ex__(), and it has not .__copy__() method. There are many possible simple fixes: * Make all __init__() parameters optional. This is the simplest one. The copy() infrastructure will simply call __setstate__() with the __dict__ of the original exception after an empty. * Provide a .__copy__() method. * Provide .__reduce_ex__() or .__reduce__() method with a result compatible with the existing .__init__() signature. For the second problem, the fix might be harder, and maybe unnecessary, if we cannot find good use cases for pickling such an exception. This is important for chameleon itself, which clones exceptions while formatting them. Here is an example traceback. Down below you'll find ipython session with demonstration appropriate for turning into (part of) an unit test, and a discussion on alternative fixes: .../eggs/Zope2-2.12.19-py2.6-linux-x86_64.egg/Products/PageTemplates/ZopePageTemplate.py in pt_render(self, source, extra_context)     430     431 def pt_render(self, source=False, extra_context={}): --> 432 result = PageTemplate.pt_render(self, source, extra_context)     433 assert isinstance(result, unicode)     434 return result .../eggs/Zope2-2.12.19-py2.6-linux-x86_64.egg/Products/PageTemplates/PageTemplate.py in pt_render(self, source, extra_context)      78 showtal = True      79 return super(PageTemplate, self).pt_render(c, source=source, sourceAnnotations=sourceAnnotations, ---> 80 showtal=showtal)      81      82 .../eggs/zope.pagetemplate-3.5.2-py2.6.egg/zope/pagetemplate/pagetemplate.py in pt_render(self, namespace, source, sourceAnnotations, showtal)     111 TALInterpreter(self._v_program, self._v_macros,     112 context, output, tal=not source, showtal=showtal, --> 113 strictinsert=0, sourceAnnotations=sourceAnnotations)()     114 return output.getvalue()     115 .../eggs/five.pt-2.1.4-py2.6.egg/five/pt/patches.py in __call__(self)     125 context['repeat'] = RepeatDict(self.repeat)     126 --> 127 result = self.template.render(**context)     128     129 self.stream.write(result) .../eggs/z3c.pt-2.1-py2.6.egg/z3c/pt/pagetemplate.py in render(self, target_language, **context)     123     124 base_renderer = super(BaseTemplate, self).render --> 125 return base_renderer(**context)     126     127 def __call__(self, *args, **kwargs): .../eggs/Chameleon-2.2-py2.6.egg/chameleon/zpt/template.py in render(self, encoding, translate, target_language, **k)     115 if 'repeat' not in k: k['repeat'] = RepeatDict({})     116 --> 117 return super(PageTemplate, self).render(**k)     118     119 def include(self, stream, econtext, rcontext): ------------------------- Here is a specific demonstration of the bug: In [1]: import chameleon.exc, copy, cPickle In [2]: template_error = chameleon.exc.TemplateError(msg='a test', token=chameleon.exc.Token('a token')) In [3]: copy.copy(template_error) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /.../<ipython console> in <module>() /.../lib/python2.6/copy.pyc in copy(x)      93 raise Error("un(shallow)copyable object of type %s" % cls)      94 ---> 95 return _reconstruct(x, rv, 0)      96      97 /.../lib/python2.6/copy.pyc in _reconstruct(x, info, deep, memo)     321 if deep:     322 args = deepcopy(args, memo) --> 323 y = callable(*args)     324 memo[id(x)] = y     325 if listiter is not None: TypeError: __init__() takes exactly 3 arguments (1 given) In [4]: cPickle.dumps(template_error) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /.../<ipython console> in <module>() /.../lib/python2.6/copy_reg.pyc in _reduce_ex(self, proto)      75 except AttributeError:      76 if getattr(self, "__slots__", None): ---> 77 raise TypeError("a class that defines __slots__ without "      78 "defining __getstate__ cannot be pickled")      79 try: TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled -------------------------------- At least for copying problem, the issue seems to be that the signature required by TemplateError.__init__() is not compatible with what is returned by TemplateError.__reduce_ex__(), and it has no .__copy__() method. There are many possible simple fixes:  * Make all __init__() parameters optional. This is the simplest one. The copy() infrastructure will simply call __setstate__() with the __dict__ of the original exception after an empty.  * Provide a .__copy__() method.  * Provide .__reduce_ex__() or .__reduce__() method with a result compatible with the existing .__init__() signature. For the second problem, the fix might be harder, and maybe unnecessary, if we cannot find good use cases for pickling such an exception.
2011-09-12 19:17:37 LeoRochael chameleon.core: status New Fix Released