forked from celery/celery
/
serialization.py
116 lines (86 loc) · 3.34 KB
/
serialization.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from functools import partial as curry
import operator
try:
import cPickle as pickle
except ImportError:
import pickle
def find_nearest_pickleable_exception(exc):
"""With an exception instance, iterate over its super classes (by mro)
and find the first super exception that is pickleable. It does
not go below :exc:`Exception` (i.e. it skips :exc:`Exception`,
:class:`BaseException` and :class:`object`). If that happens
you should use :exc:`UnpickleableException` instead.
:param exc: An exception instance.
:returns: the nearest exception if it's not :exc:`Exception` or below,
if it is it returns ``None``.
:rtype: :exc:`Exception`
"""
unwanted = (Exception, BaseException, object)
is_unwanted = lambda exc: any(map(curry(operator.is_, exc), unwanted))
mro_ = getattr(exc.__class__, "mro", lambda: [])
for supercls in mro_():
if is_unwanted(supercls):
# only BaseException and object, from here on down,
# we don't care about these.
return None
try:
exc_args = getattr(exc, "args", [])
superexc = supercls(*exc_args)
pickle.dumps(superexc)
except:
pass
else:
return superexc
return None
def create_exception_cls(name, module, parent=None):
"""Dynamically create an exception class."""
if not parent:
parent = Exception
return type(name, (parent, ), {"__module__": module})
class UnpickleableExceptionWrapper(Exception):
"""Wraps unpickleable exceptions.
:param exc_module: see :attr:`exc_module`.
:param exc_cls_name: see :attr:`exc_cls_name`.
:param exc_args: see :attr:`exc_args`
.. attribute:: exc_module
The module of the original exception.
.. attribute:: exc_cls_name
The name of the original exception class.
.. attribute:: exc_args
The arguments for the original exception.
Example
>>> try:
... something_raising_unpickleable_exc()
>>> except Exception, e:
... exc = UnpickleableException(e.__class__.__module__,
... e.__class__.__name__,
... e.args)
... pickle.dumps(exc) # Works fine.
"""
def __init__(self, exc_module, exc_cls_name, exc_args):
self.exc_module = exc_module
self.exc_cls_name = exc_cls_name
self.exc_args = exc_args
super(Exception, self).__init__(exc_module, exc_cls_name, exc_args)
def get_pickleable_exception(exc):
"""Make sure exception is pickleable."""
nearest = find_nearest_pickleable_exception(exc)
if nearest:
return nearest
try:
pickle.dumps(exc)
except pickle.PickleError:
excwrapper = UnpickleableExceptionWrapper(
exc.__class__.__module__,
exc.__class__.__name__,
getattr(exc, "args", []))
return excwrapper
return exc
def get_pickled_exception(exc):
"""Get original exception from exception pickled using
:meth:`get_pickleable_exception`."""
if isinstance(exc, UnpickleableExceptionWrapper):
exc_cls = create_exception_cls(exc.exc_cls_name,
exc.exc_module)
return exc_cls(*exc.exc_args)
return exc