chameleon.exc exceptions can't be cloned, much less pickled

Bug #819815 reported by LeoRochael
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Chamelon Core
Fix Released
Undecided
Unassigned

Bug 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:

.../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.

LeoRochael (leorochael)
description: updated
Revision history for this message
LeoRochael (leorochael) wrote :

Exceptions can now be cloned.

Whoever wants exceptions raised by Chameleon to be pickleable should reopen this issue

Changed in chameleon.core:
status: New → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.