serialization.py 4.9 KB

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