forked from celery/celery
-
Notifications
You must be signed in to change notification settings - Fork 40
/
serialization.py
165 lines (124 loc) · 4.61 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import inspect
import sys
import types
from copy import deepcopy
import pickle as pypickle
try:
import cPickle as cpickle
except ImportError:
cpickle = None
if sys.version_info < (2, 6):
# cPickle is broken in Python <= 2.5.
# It unsafely and incorrectly uses relative instead of absolute imports,
# so e.g.:
# exceptions.KeyError
# becomes:
# celery.exceptions.KeyError
#
# Your best choice is to upgrade to Python 2.6,
# as while the pure pickle version has worse performance,
# it is the only safe option for older Python versions.
pickle = pypickle
else:
pickle = cpickle or pypickle
# BaseException was introduced in Python 2.5.
try:
_error_bases = (BaseException, )
except NameError:
_error_bases = (SystemExit, KeyboardInterrupt)
unwanted_base_classes = (StandardError, Exception) + _error_bases + (object, )
if sys.version_info < (2, 5):
# Prior to Python 2.5, Exception was an old-style class
def subclass_exception(name, parent, unused):
return types.ClassType(name, (parent,), {})
else:
def subclass_exception(name, parent, module):
return type(name, (parent,), {'__module__': module})
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 :const:`None`.
:rtype :exc:`Exception`:
"""
cls = exc.__class__
getmro_ = getattr(cls, "mro", None)
# old-style classes doesn't have mro()
if not getmro_:
# all Py2.4 exceptions has a baseclass.
if not getattr(cls, "__bases__", ()):
return
# Use inspect.getmro() to traverse bases instead.
getmro_ = lambda: inspect.getmro(cls)
for supercls in getmro_():
if supercls in unwanted_base_classes:
# only BaseException and object, from here on down,
# we don't care about these.
return
try:
exc_args = getattr(exc, "args", [])
superexc = supercls(*exc_args)
pickle.dumps(superexc)
except:
pass
else:
return superexc
return
def create_exception_cls(name, module, parent=None):
"""Dynamically create an exception class."""
if not parent:
parent = Exception
return subclass_exception(name, parent, 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
Exception.__init__(self, exc_module, exc_cls_name, exc_args)
@classmethod
def from_exception(cls, exc):
return cls(exc.__class__.__module__,
exc.__class__.__name__,
getattr(exc, "args", []))
def restore(self):
return create_exception_cls(self.exc_cls_name,
self.exc_module)(*self.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(deepcopy(exc))
except Exception:
return UnpickleableExceptionWrapper.from_exception(exc)
return exc
def get_pickled_exception(exc):
"""Get original exception from exception pickled using
:meth:`get_pickleable_exception`."""
if isinstance(exc, UnpickleableExceptionWrapper):
return exc.restore()
return exc