serialization.py 4.9 KB

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