Browse Source

get_result() now handles UnpickleableExceptionWrapper, and the handling of unpickled exception is better. The find_nearest_exception function now skips Exception, BaseException and Object (or that is, everything below Exception in the mro)

Ask Solem 16 years ago
parent
commit
8054c38fa1
4 changed files with 54 additions and 9 deletions
  1. 39 6
      celery/backends/base.py
  2. 5 1
      celery/backends/cache.py
  3. 5 1
      celery/backends/database.py
  4. 5 1
      celery/backends/tyrant.py

+ 39 - 6
celery/backends/base.py

@@ -9,21 +9,32 @@ import sys
 
 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.
+    and find the first super exception that is pickleable. It does
+    not go below :exc:`Exception` (i.e. it skips :exc:`Exception`,
+    :class:`BaseExecption` 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`
 
     """
     for supercls in exc.__class__.mro():
+        if supercls is Exception:
+            # only BaseException and object, from here on down,
+            # we don't care about these.
+            return None
         try:
-            superexc = supercls(exc.args)
+            superexc = supercls(*exc.args)
             pickle.dumps(superexc)
         except:
             pass
         else:
             return superexc
-    return exc
+    return None
 
 
 class UnpickleableExceptionWrapper(Exception):
@@ -33,7 +44,7 @@ class UnpickleableExceptionWrapper(Exception):
 
     :param exc_cls_name: see :attr:`exc_cls_name`.
     
-    :param exc_args: The arguments for the original exception.
+    :param exc_args: see :attr:`exc_args`
 
     .. attribute:: exc_module
 
@@ -43,6 +54,10 @@ class UnpickleableExceptionWrapper(Exception):
 
         The name of the original exception class.
 
+    .. attribute:: exc_args
+
+        The arguments for the original exception.
+
     Example
 
         >>> try:
@@ -57,13 +72,16 @@ class UnpickleableExceptionWrapper(Exception):
 
     def __init__(self, exc_module, exc_cls_name, exc_args):
         self.exc_module = exc_module
-        self.exc_cls = exc_cls_name
+        self.exc_cls_name = exc_cls_name
+        self.exc_args = exc_args
         super(Exception, self).__init__(exc_module, exc_cls_name, exc_args)
 
 
 class BaseBackend(object):
     """The base backend class. All backends should inherit from this."""
 
+    UnpickleableExecptionWrapper = UnpickleableExceptionWrapper
+
     def store_result(self, task_id, result, status):
         """Store the result and status of a task."""
         raise NotImplementedError(
@@ -77,8 +95,16 @@ class BaseBackend(object):
         """Mark task as executed with failure. Stores the execption."""
         return self.store_result(task_id, exc, status="FAILURE")
 
+    def create_exception_cls(self, name, module, parent=None):
+        if not parent:
+            parent = Exception
+        return type(name, (parent, ), {"__module__": module})
+
     def prepare_exception(self, exc):
-        exc = find_nearest_pickleable_exception(exc)
+        nearest = find_nearest_pickleable_exception(exc)
+        if nearest:
+            return nearest
+
         try:
             pickle.dumps(exc)
         except pickle.PickleError:
@@ -89,6 +115,13 @@ class BaseBackend(object):
             return excwrapper
         else:
             return exc
+    
+    def exception_to_python(self, exc):
+        if isinstance(exc, UnpickleableExceptionWrapper):
+            exc_cls = self.create_exception_cls(exc.exc_cls_name,
+                                                exc.exc_module)
+            return exc_cls(*exc.exc_args)
+        return exc
 
     def mark_as_retry(self, task_id, exc):
         """Mark task for retry."""

+ 5 - 1
celery/backends/cache.py

@@ -32,7 +32,11 @@ class Backend(BaseBackend):
 
     def get_result(self, task_id):
         """Get the result of a task."""
-        return self._get_task_meta_for(self, task_id)["result"]
+        meta = self._get_task_meta_for(self, task_id)
+        if meta["status"] == "FAILURE":
+            return self.exception_to_python(meta["result"])
+        else:
+            return meta["result"]
 
     def is_done(self, task_id):
         """Returns ``True`` if the task has been executed successfully."""

+ 5 - 1
celery/backends/database.py

@@ -28,7 +28,11 @@ class Backend(BaseBackend):
 
     def get_result(self, task_id):
         """Get the result for a task."""
-        return self._get_task_meta_for(task_id).result
+        meta = self._get_task_meta_for(task_id)
+        if meta.status == "FAILURE":
+            return self.exception_to_python(meta.result)
+        else:
+            return result
 
     def _get_task_meta_for(self, task_id):
         if task_id in self._cache:

+ 5 - 1
celery/backends/tyrant.py

@@ -72,7 +72,11 @@ class Backend(BaseBackend):
 
     def get_result(self, task_id):
         """Get the result of a task."""
-        return self._get_task_meta_for(self, task_id)["result"]
+        meta = self._get_task_meta_for(self, task_id)
+        if meta["status"] == "FAILURE":
+            return self.exception_to_python(meta["result"])
+        else:
+            return meta["result"]
 
     def is_done(self, task_id):
         """Returns ``True`` if the task executed successfully."""