Repository: ipython/ipython

Issue:
Fail to send class definitions from interactive session to engines namespaces
Here is a [sample notebook](http://nbviewer.ipython.org/4471180/) to demonstrate the issue.

Please note that I don't get any unpickling error message when calling:

```
rc[:]['AClass'] = AClass
```

While @minrk is reporting a `DummyModule` `AttributeError` in this [SO question](http://stackoverflow.com/questions/14184621/how-to-work-with-interactively-defined-classes-in-ipython-parallel).

I will see if I can come up with a pull request for a `can_class` / `uncan_class` along the lines of [this gist](https://gist.github.com/4470212).



Discussion:

Resolving patch:
diff --git a/IPython/utils/pickleutil.py b/IPython/utils/pickleutil.py
--- a/IPython/utils/pickleutil.py
+++ b/IPython/utils/pickleutil.py
@@ -38,6 +38,10 @@
 
 if py3compat.PY3:
     buffer = memoryview
+    class_type = type
+else:
+    from types import ClassType
+    class_type = (type, ClassType)
 
 #-------------------------------------------------------------------------------
 # Classes
@@ -110,6 +114,30 @@ def get_object(self, g=None):
         newFunc = FunctionType(self.code, g, self.__name__, defaults)
         return newFunc
 
+class CannedClass(CannedObject):
+
+    def __init__(self, cls):
+        self._check_type(cls)
+        self.name = cls.__name__
+        self.old_style = not isinstance(cls, type)
+        self._canned_dict = {}
+        for k,v in cls.__dict__.items():
+            if k not in ('__weakref__', '__dict__'):
+                self._canned_dict[k] = can(v)
+        if self.old_style:
+            mro = []
+        else:
+            mro = cls.mro()
+        
+        self.parents = [ can(c) for c in mro[1:] ]
+        self.buffers = []
+
+    def _check_type(self, obj):
+        assert isinstance(obj, class_type), "Not a class type"
+
+    def get_object(self, g=None):
+        parents = tuple(uncan(p, g) for p in self.parents)
+        return type(self.name, parents, uncan_dict(self._canned_dict, g=g))
 
 class CannedArray(CannedObject):
     def __init__(self, obj):
@@ -200,6 +228,12 @@ def can(obj):
     
     return obj
 
+def can_class(obj):
+    if isinstance(obj, class_type) and obj.__module__ == '__main__':
+        return CannedClass(obj)
+    else:
+        return obj
+
 def can_dict(obj):
     """can the *values* of a dict"""
     if isinstance(obj, dict):
@@ -266,6 +300,7 @@ def uncan_sequence(obj, g=None):
     FunctionType : CannedFunction,
     bytes : CannedBytes,
     buffer : CannedBuffer,
+    class_type : can_class,
 }
 
 uncan_map = {


Test patch:
diff --git a/IPython/zmq/tests/test_serialize.py b/IPython/zmq/tests/test_serialize.py
--- a/IPython/zmq/tests/test_serialize.py
+++ b/IPython/zmq/tests/test_serialize.py
@@ -18,7 +18,8 @@
 # from unittest import TestCaes
 from IPython.zmq.serialize import serialize_object, unserialize_object
 from IPython.testing import decorators as dec
-from IPython.utils.pickleutil import CannedArray
+from IPython.utils.pickleutil import CannedArray, CannedClass
+from IPython.parallel import interactive
 
 #-------------------------------------------------------------------------------
 # Globals and Utilities
@@ -164,5 +165,46 @@ def test_numpy_in_dict():
             yield nt.assert_equals(A.shape, B.shape)
             yield nt.assert_equals(A.dtype, B.dtype)
             yield assert_array_equal(A,B)
+
+@dec.parametric
+def test_class():
+    @interactive
+    class C(object):
+        a=5
+    bufs = serialize_object(dict(C=C))
+    canned = pickle.loads(bufs[0])
+    yield nt.assert_true(canned['C'], CannedClass)
+    d, r = unserialize_object(bufs)
+    C2 = d['C']
+    yield nt.assert_equal(C2.a, C.a)
+
+@dec.parametric
+def test_class_oldstyle():
+    @interactive
+    class C:
+        a=5
     
+    bufs = serialize_object(dict(C=C))
+    canned = pickle.loads(bufs[0])
+    yield nt.assert_true(canned['C'], CannedClass)
+    d, r = unserialize_object(bufs)
+    C2 = d['C']
+    yield nt.assert_equal(C2.a, C.a)
 
+@dec.parametric
+def test_class_inheritance():
+    @interactive
+    class C(object):
+        a=5
+
+    @interactive
+    class D(C):
+        b=10
+    
+    bufs = serialize_object(dict(D=D))
+    canned = pickle.loads(bufs[0])
+    yield nt.assert_true(canned['D'], CannedClass)
+    d, r = unserialize_object(bufs)
+    D2 = d['D']
+    yield nt.assert_equal(D2.a, D.a)
+    yield nt.assert_equal(D2.b, D.b)