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/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:
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:
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/eggs/ Zope2-2. 12.19-py2. 6-linux- x86_64. egg/Products/ PageTemplates/ ZopePageTemplat e.py in pt_render(self, source, extra_context) pt_render( self, source, extra_context)
430
431 def pt_render(self, source=False, extra_context={}):
--> 432 result = PageTemplate.
433 assert isinstance(result, unicode)
434 return result
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/eggs/ Zope2-2. 12.19-py2. 6-linux- x86_64. egg/Products/ PageTemplates/ PageTemplate. py in pt_render(self, source, extra_context) ns=sourceAnnota tions,
78 showtal = True
79 return super(PageTemplate, self).pt_render(c, source=source, sourceAnnotatio
---> 80 showtal=showtal)
81
82
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/eggs/ zope.pagetempla te-3.5. 2-py2.6. egg/zope/ pagetemplate/ pagetemplate. py in pt_render(self, namespace, source, sourceAnnotations, showtal) self._v_ program, self._v_macros, ns=sourceAnnota tions)( )
111 TALInterpreter(
112 context, output, tal=not source, showtal=showtal,
--> 113 strictinsert=0, sourceAnnotatio
114 return output.getvalue()
115
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/eggs/ five.pt- 2.1.4-py2. 6.egg/five/ pt/patches. py in __call__(self) self.repeat) render( **context) write(result)
125 context['repeat'] = RepeatDict(
126
--> 127 result = self.template.
128
129 self.stream.
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/eggs/ z3c.pt- 2.1-py2. 6.egg/z3c/ pt/pagetemplate .py in render(self, target_language, **context) **context)
123
124 base_renderer = super(BaseTemplate, self).render
--> 125 return base_renderer(
126
127 def __call__(self, *args, **kwargs):
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/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.TemplateErr or(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/ c980eff74363615 a0916a57227138d a5/parts/ python2. 6/lib/python2. 6/copy. pyc in copy(x) un(shallow) copyable object of type %s" % cls)
93 raise Error("
94
---> 95 return _reconstruct(x, rv, 0)
96
97
/opt/slapgrid/ c980eff74363615 a0916a57227138d a5/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/ c980eff74363615 a0916a57227138d a5/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.