serialization.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import inspect
  2. import sys
  3. import types
  4. from copy import deepcopy
  5. import pickle as pypickle
  6. try:
  7. import cPickle as cpickle
  8. except ImportError:
  9. cpickle = None
  10. if sys.version_info < (2, 6):
  11. # cPickle is broken in Python <= 2.5.
  12. # It unsafely and incorrectly uses relative instead of absolute imports,
  13. # so e.g.:
  14. # exceptions.KeyError
  15. # becomes:
  16. # celery.exceptions.KeyError
  17. #
  18. # Your best choice is to upgrade to Python 2.6,
  19. # as while the pure pickle version has worse performance,
  20. # it is the only safe option for older Python versions.
  21. pickle = pypickle
  22. else:
  23. pickle = cpickle or pypickle
  24. # BaseException was introduced in Python 2.5.
  25. try:
  26. _error_bases = (BaseException, )
  27. except NameError:
  28. _error_bases = (SystemExit, KeyboardInterrupt)
  29. #: List of base classes we probably don't want to reduce to.
  30. unwanted_base_classes = (StandardError, Exception) + _error_bases + (object, )
  31. if sys.version_info < (2, 5):
  32. # Prior to Python 2.5, Exception was an old-style class
  33. def subclass_exception(name, parent, unused):
  34. return types.ClassType(name, (parent,), {})
  35. else:
  36. def subclass_exception(name, parent, module):
  37. return type(name, (parent,), {'__module__': module})
  38. def find_nearest_pickleable_exception(exc):
  39. """With an exception instance, iterate over its super classes (by mro)
  40. and find the first super exception that is pickleable. It does
  41. not go below :exc:`Exception` (i.e. it skips :exc:`Exception`,
  42. :class:`BaseException` and :class:`object`). If that happens
  43. you should use :exc:`UnpickleableException` instead.
  44. :param exc: An exception instance.
  45. :returns: the nearest exception if it's not :exc:`Exception` or below,
  46. if it is it returns :const:`None`.
  47. :rtype :exc:`Exception`:
  48. """
  49. cls = exc.__class__
  50. getmro_ = getattr(cls, "mro", None)
  51. # old-style classes doesn't have mro()
  52. if not getmro_:
  53. # all Py2.4 exceptions has a baseclass.
  54. if not getattr(cls, "__bases__", ()):
  55. return
  56. # Use inspect.getmro() to traverse bases instead.
  57. getmro_ = lambda: inspect.getmro(cls)
  58. for supercls in getmro_():
  59. if supercls in unwanted_base_classes:
  60. # only BaseException and object, from here on down,
  61. # we don't care about these.
  62. return
  63. try:
  64. exc_args = getattr(exc, "args", [])
  65. superexc = supercls(*exc_args)
  66. pickle.dumps(superexc)
  67. except:
  68. pass
  69. else:
  70. return superexc
  71. return
  72. def create_exception_cls(name, module, parent=None):
  73. """Dynamically create an exception class."""
  74. if not parent:
  75. parent = Exception
  76. return subclass_exception(name, parent, module)
  77. class UnpickleableExceptionWrapper(Exception):
  78. """Wraps unpickleable exceptions.
  79. :param exc_module: see :attr:`exc_module`.
  80. :param exc_cls_name: see :attr:`exc_cls_name`.
  81. :param exc_args: see :attr:`exc_args`
  82. **Example**
  83. .. code-block:: python
  84. >>> try:
  85. ... something_raising_unpickleable_exc()
  86. >>> except Exception, e:
  87. ... exc = UnpickleableException(e.__class__.__module__,
  88. ... e.__class__.__name__,
  89. ... e.args)
  90. ... pickle.dumps(exc) # Works fine.
  91. """
  92. #: The module of the original exception.
  93. exc_module = None
  94. #: The name of the original exception class.
  95. exc_cls_name = None
  96. #: The arguments for the original exception.
  97. exc_args = None
  98. def __init__(self, exc_module, exc_cls_name, exc_args):
  99. self.exc_module = exc_module
  100. self.exc_cls_name = exc_cls_name
  101. self.exc_args = exc_args
  102. Exception.__init__(self, exc_module, exc_cls_name, exc_args)
  103. @classmethod
  104. def from_exception(cls, exc):
  105. return cls(exc.__class__.__module__,
  106. exc.__class__.__name__,
  107. getattr(exc, "args", []))
  108. def restore(self):
  109. return create_exception_cls(self.exc_cls_name,
  110. self.exc_module)(*self.exc_args)
  111. def get_pickleable_exception(exc):
  112. """Make sure exception is pickleable."""
  113. nearest = find_nearest_pickleable_exception(exc)
  114. if nearest:
  115. return nearest
  116. try:
  117. pickle.dumps(deepcopy(exc))
  118. except Exception:
  119. return UnpickleableExceptionWrapper.from_exception(exc)
  120. return exc
  121. def get_pickled_exception(exc):
  122. """Get original exception from exception pickled using
  123. :meth:`get_pickleable_exception`."""
  124. if isinstance(exc, UnpickleableExceptionWrapper):
  125. return exc.restore()
  126. return exc