Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Exception serialization module from billiard is bundled. I decided to…

… remove the dependency because exception serialization is not the main billiard purpose, it seems that billiard is no longer actively developed and the latest billiard is not on pypi.
  • Loading branch information...
commit b95eb684eb8d2131db4d42f218d2aa9f5e88a858 1 parent 4d4bb08
@kmike authored
Showing with 149 additions and 4 deletions.
  1. +8 −3 README.rst
  2. +1 −1  slacker/postpone.py
  3. +140 −0 slacker/serialization.py
View
11 README.rst
@@ -264,9 +264,12 @@ Inspiration:
* https://bitbucket.org/david/django-roa/
* http://tornadogists.org/654157/
-Third-party software: `adisp <https://code.launchpad.net/adisp>`_ (tornado
-adisp implementation is taken from
-`brukva <https://github.com/evilkost/brukva>`_).
+Third-party software:
+
+* `adisp <https://code.launchpad.net/adisp>`_ (tornado adisp implementation
+ is taken from `brukva <https://github.com/evilkost/brukva>`_);
+* exception serialization utils are from
+ `billiard <https://github.com/ask/billiard>` by Ask Solem.
License
=======
@@ -274,3 +277,5 @@ License
The license is MIT.
Bundled adisp library uses Simplified BSD License.
+
+slacker.serialization is under BSD License.
View
2  slacker/postpone.py
@@ -7,7 +7,7 @@
except ImportError:
import pickle
-from billiard.serialization import get_pickleable_exception
+from slacker.serialization import get_pickleable_exception
class SlackerException(Exception):
View
140 slacker/serialization.py
@@ -0,0 +1,140 @@
+# Exception serialization utils are borrowed from
+# https://github.com/ask/billiard
+
+import sys
+import types
+import operator
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+from copy import deepcopy
+
+try:
+ _error_bases = (BaseException, )
+except NameError:
+ _error_bases = (SystemExit, KeyboardInterrupt)
+
+
+def is_unwanted_exception_cls(exc_cls):
+ unwanted_classes = (Exception, ) + _error_bases + (object, )
+ for unwanted_cls in unwanted_classes:
+ if exc_cls is unwanted_cls:
+ return True
+ return False
+
+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 ``None``.
+
+ :rtype: :exc:`Exception`
+
+ """
+
+ mro_ = getattr(exc.__class__, "mro", lambda: [])
+ for supercls in mro_():
+ if is_unwanted_exception_cls(supercls):
+ # only BaseException and object, from here on down,
+ # we don't care about these.
+ return None
+ try:
+ exc_args = getattr(exc, "args", [])
+ superexc = supercls(*exc_args)
+ pickle.dumps(superexc)
+ except:
+ pass
+ else:
+ return superexc
+ return None
+
+
+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)
+
+
+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:
+ excwrapper = UnpickleableExceptionWrapper(
+ exc.__class__.__module__,
+ exc.__class__.__name__,
+ getattr(exc, "args", []))
+ return excwrapper
+ return exc
+
+
+def get_pickled_exception(exc):
+ """Get original exception from exception pickled using
+ :meth:`get_pickleable_exception`."""
+ if isinstance(exc, UnpickleableExceptionWrapper):
+ exc_cls = create_exception_cls(exc.exc_cls_name,
+ exc.exc_module)
+ return exc_cls(*exc.exc_args)
+ return exc
Please sign in to comment.
Something went wrong with that request. Please try again.