serialization.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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. try:
  10. import cPickle as pickle
  11. except ImportError:
  12. import pickle # noqa
  13. from .encoding import safe_repr
  14. #: List of base classes we probably don't want to reduce to.
  15. try:
  16. unwanted_base_classes = (StandardError, Exception, BaseException, object)
  17. except NameError:
  18. unwanted_base_classes = (Exception, BaseException, object) # py3k
  19. def subclass_exception(name, parent, module): # noqa
  20. return type(name, (parent,), {'__module__': module})
  21. def find_nearest_pickleable_exception(exc):
  22. """With an exception instance, iterate over its super classes (by mro)
  23. and find the first super exception that is pickleable. It does
  24. not go below :exc:`Exception` (i.e. it skips :exc:`Exception`,
  25. :class:`BaseException` and :class:`object`). If that happens
  26. you should use :exc:`UnpickleableException` instead.
  27. :param exc: An exception instance.
  28. :returns: the nearest exception if it's not :exc:`Exception` or below,
  29. if it is it returns :const:`None`.
  30. :rtype :exc:`Exception`:
  31. """
  32. cls = exc.__class__
  33. getmro_ = getattr(cls, 'mro', None)
  34. # old-style classes doesn't have mro()
  35. if not getmro_: # pragma: no cover
  36. # all Py2.4 exceptions has a baseclass.
  37. if not getattr(cls, '__bases__', ()):
  38. return
  39. # Use inspect.getmro() to traverse bases instead.
  40. getmro_ = lambda: inspect.getmro(cls)
  41. for supercls in getmro_():
  42. if supercls in unwanted_base_classes:
  43. # only BaseException and object, from here on down,
  44. # we don't care about these.
  45. return
  46. try:
  47. exc_args = getattr(exc, 'args', [])
  48. superexc = supercls(*exc_args)
  49. pickle.loads(pickle.dumps(superexc))
  50. except:
  51. pass
  52. else:
  53. return superexc
  54. def create_exception_cls(name, module, parent=None):
  55. """Dynamically create an exception class."""
  56. if not parent:
  57. parent = Exception
  58. return subclass_exception(name, parent, module)
  59. class UnpickleableExceptionWrapper(Exception):
  60. """Wraps unpickleable exceptions.
  61. :param exc_module: see :attr:`exc_module`.
  62. :param exc_cls_name: see :attr:`exc_cls_name`.
  63. :param exc_args: see :attr:`exc_args`
  64. **Example**
  65. .. code-block:: python
  66. >>> try:
  67. ... something_raising_unpickleable_exc()
  68. >>> except Exception as e:
  69. ... exc = UnpickleableException(e.__class__.__module__,
  70. ... e.__class__.__name__,
  71. ... e.args)
  72. ... pickle.dumps(exc) # Works fine.
  73. """
  74. #: The module of the original exception.
  75. exc_module = None
  76. #: The name of the original exception class.
  77. exc_cls_name = None
  78. #: The arguments for the original exception.
  79. exc_args = None
  80. def __init__(self, exc_module, exc_cls_name, exc_args, text=None):
  81. safe_exc_args = []
  82. for arg in exc_args:
  83. try:
  84. pickle.dumps(arg)
  85. safe_exc_args.append(arg)
  86. except Exception:
  87. safe_exc_args.append(safe_repr(arg))
  88. self.exc_module = exc_module
  89. self.exc_cls_name = exc_cls_name
  90. self.exc_args = safe_exc_args
  91. self.text = text
  92. Exception.__init__(self, exc_module, exc_cls_name, safe_exc_args, text)
  93. def restore(self):
  94. return create_exception_cls(self.exc_cls_name,
  95. self.exc_module)(*self.exc_args)
  96. def __str__(self):
  97. return self.text
  98. @classmethod
  99. def from_exception(cls, exc):
  100. return cls(exc.__class__.__module__,
  101. exc.__class__.__name__,
  102. getattr(exc, 'args', []),
  103. safe_repr(exc))
  104. def get_pickleable_exception(exc):
  105. """Make sure exception is pickleable."""
  106. try:
  107. pickle.loads(pickle.dumps(exc))
  108. except Exception:
  109. pass
  110. else:
  111. return exc
  112. nearest = find_nearest_pickleable_exception(exc)
  113. if nearest:
  114. return nearest
  115. return UnpickleableExceptionWrapper.from_exception(exc)
  116. def get_pickled_exception(exc):
  117. """Get original exception from exception pickled using
  118. :meth:`get_pickleable_exception`."""
  119. if isinstance(exc, UnpickleableExceptionWrapper):
  120. return exc.restore()
  121. return exc