Commits

Ronald Oussoren  committed 5ecd0b9

This adds support for the ``NSCoding`` protocol to
OC_PythonObject and friends. This basically means
that it is now possible to use an NSKeyedArchiver
to serialize any Python object that can be pickled.

Notes:
* This does not yet support NSArchiver, adding
that should be trivial (basicly fall back to
using "classic" NSCoding methods when the coder
doesn't support keyed coding)

* This support is experimental, the code passes
unittests but I'm not yet fully convinced that
the unittests are good enough.

  • Participants
  • Parent commits 6afd5c0
  • Branches pyobjc-ancient

Comments (0)

Files changed (30)

File pyobjc-core/Doc/20api-notes-macosx.txt

 Core Objective-C runtime
 ------------------------
 
+In Objective-C you can raise arbitrary values as exceptions using the ``@throw``
+syntax. PyObjC will only convert exception values that are Objective-C
+instances to Python exceptions, raising other values will cause a
+fatal error due to an uncaught exception.
+
+Apple's frameworks only raise instances of ``NSException``, and 
+other exception values are frowned upon (at best).
+
 Addressbook framework
 ---------------------
 

File pyobjc-core/Lib/objc/__init__.py

 
 # Import the namespace from the _objc extension
 def _update(g=globals()):
+
+    # Dummy import of copy_reg, needed 
+    # for py2app.
+    import copy_reg
+
     import _objc
     for k,v in _objc.__dict__.iteritems():
         g.setdefault(k,v)
 from _convenience import *
 from _bridgesupport import *
 
-# Add useful utility functions below
-if platform == 'MACOSX':
-    from _dyld import *
-else:
-    from _gnustep import *
-
+from _dyld import *
 from _protocols import *
 from _descriptors import *
 from _category import *
 from _functions import *
 from _locking import *
 
+import _pycoder
+
 # Make sure our global autorelease pool is
 # recycled when the interpreter shuts down.
 # This avoids issue1402 in the python

File pyobjc-core/Lib/objc/_convenience.py

     ('endswith', lambda self, pfx: self.hasSuffix_(pfx)),
 )
 
+
+CONVENIENCE_METHODS['copyWithZone:'] = (
+    ('__copy__', lambda self: self.copyWithZone_(None)),
+)
+
+NSKeyedArchiver = lookUpClass('NSKeyedArchiver')
+NSKeyedUnarchiver = lookUpClass('NSKeyedUnarchiver')
+def coder_deepcopy(self):
+    buf = NSKeyedArchiver.archivedDataWithRootObject_(self)
+    result = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+    return result
+
+CONVENIENCE_METHODS['encodeWithCoder:'] = (
+    ('__deepcopy__', coder_deepcopy ),
+)
+
 CLASS_METHODS['NSNull'] = (
     ('__nonzero__',  lambda self: False ),
 )

File pyobjc-core/Lib/objc/_pycoder.py

+"""
+Implementation of NSCoding for OC_PythonObject and friends
+
+NOTE: this only works with a keyed archiver, not with a plain archiver. It 
+should be easy enough to change this later on if needed.
+
+FIXME: encoding for lists and tuples is far from optimal
+FIXME: need versioning
+"""
+import objc
+from types import *
+import copy_reg
+import copy
+import sys
+
+from pickle import PicklingError, UnpicklingError, whichmodule
+
+
+
+def setupPythonObject():
+    OC_PythonObject = objc.lookUpClass("OC_PythonObject")
+
+    NSArray = objc.lookUpClass("NSArray")
+    NSDictionary = objc.lookUpClass("NSDictionary")
+    NSString = objc.lookUpClass("NSString")
+
+    kOP_REDUCE=0
+    kOP_INST=1
+    kOP_GLOBAL=2
+    kOP_NONE=3
+    kOP_BOOL=4
+    kOP_INT=5
+    kOP_LONG=6
+    kOP_FLOAT=7
+    kOP_UNICODE=8
+    kOP_STRING=9
+    kOP_TUPLE=10
+    kOP_LIST=11
+    kOP_DICT=12
+    kOP_GLOBAL_EXT=13
+
+    kKIND = NSString.stringWithString_(u"kind")
+    kFUNC = NSString.stringWithString_(u"func")
+    kARGS = NSString.stringWithString_(u"args")
+    kLIST = NSString.stringWithString_(u"list")
+    kDICT = NSString.stringWithString_(u"dict")
+    kSTATE = NSString.stringWithString_(u"state")
+    kCLASS = NSString.stringWithString_(u"class")
+    kVALUE = NSString.stringWithString_(u"value")
+    kNAME = NSString.stringWithString_(u"name")
+    kMODULE = NSString.stringWithString_(u"module")
+    kCODE = NSString.stringWithString_(u"code")
+
+    class _EmptyClass:
+        pass
+
+    encode_dispatch = {}
+
+    # Code below tries to mirror the implementation in pickle.py, with
+    # adaptations because we're not saving to a byte stream but to another
+    # serializer.
+
+    def save_reduce(coder, func, args, 
+            state=None, listitems=None, dictitems=None, obj=None):
+
+        if not isinstance(args, TupleType):
+            raise PicklingError("args from reduce() should be a tuple")
+
+        if not callable(func):
+            raise PicklingError("func from reduce should be callable")
+
+
+        coder.encodeInt_forKey_(kOP_REDUCE, kKIND)
+        coder.encodeObject_forKey_(func, kFUNC)
+        coder.encodeObject_forKey_(args, kARGS)
+        if listitems is None:
+            coder.encodeObject_forKey_(None, kLIST)
+        else:
+            coder.encodeObject_forKey_(list(listitems), kLIST)
+
+        if dictitems is None:
+            coder.encodeObject_forKey_(None, kDICT)
+        else:
+            coder.encodeObject_forKey_(dict(dictitems), kDICT)
+        coder.encodeObject_forKey_(state, kSTATE)
+
+    def save_inst(coder, obj):
+        if hasattr(obj, '__getinitargs__'):
+            args = obj.__getinitargs__()
+            len(args) # Assert it's a sequence
+        else:
+            args = ()
+
+        cls = obj.__class__
+
+        coder.encodeInt32_forKey_(kOP_INST, kKIND)
+        coder.encodeObject_forKey_(cls, kCLASS)
+        coder.encodeObject_forKey_(args, kARGS)
+
+        try:
+            getstate = obj.__getstate__
+        except AttributeError:
+            state = obj.__dict__
+
+        else:
+            state = getstate()
+
+        coder.encodeObject_forKey_(state, kSTATE)
+
+    encode_dispatch[InstanceType] = save_inst
+
+
+    def save_none(coder, obj):
+        coder.encodeInt_forKey_(kOP_NONE, kKIND)
+    encode_dispatch[NoneType] = save_none
+
+    def save_bool(coder, obj):
+        coder.encodeInt_forKey_(kOP_BOOL, kKIND)
+        coder.encodeBool_forKey_(bool(obj), kVALUE)
+    encode_dispatch[bool] = save_bool
+
+    def save_int(coder, obj):
+        coder.encodeInt_forKey_(kOP_INT, kKIND)
+        coder.encodeInt64_forKey_(obj, kVALUE)
+    encode_dispatch[int] = save_int
+
+    def save_long(coder, obj):
+        coder.encodeInt_forKey_(kOP_LONG, kKIND)
+        coder.encodeObject_forKey_(unicode(repr(obj)), kVALUE)
+    encode_dispatch[long] = save_long
+
+    def save_float(coder, obj):
+        coder.encodeInt_forKey_(kOP_FLOAT, kKIND)
+        coder.encodeDouble_forKey_(obj, kVALUE)
+    encode_dispatch[float] = save_float
+
+    def save_string(coder, obj):
+        coder.encodeInt_forKey_(kOP_STRING, kKIND)
+        coder.encodeBytes_length_forKey_(obj, len(obj), kVALUE)
+    encode_dispatch[str] = save_string
+
+    
+    def save_tuple(coder, obj):
+        coder.encodeInt_forKey_(kOP_TUPLE, kKIND)
+        coder.encodeObject_forKey_(NSArray.arrayWithArray_(obj), kVALUE)
+    encode_dispatch[tuple] = save_tuple
+
+    def save_list(coder, obj):
+        coder.encodeInt_forKey_(kOP_LIST, kKIND)
+        coder.encodeObject_forKey_(NSArray.arrayWithArray_(obj), kVALUE)
+    encode_dispatch[list] = save_list
+
+    def save_dict(coder, obj):
+        coder.encodeInt_forKey_(kOP_DICT, kKIND)
+
+        v = NSDictionary.dictionaryWithDictionary_(obj)
+        coder.encodeObject_forKey_(v, kVALUE)
+    encode_dispatch[dict] = save_dict
+
+    def save_global(coder, obj, name=None):
+
+        if name is None:
+            name = obj.__name__
+
+        module = getattr(obj, "__module__", None)
+        if module is None:
+            module = whichmodule(obj, name)
+
+        try:
+            __import__ (module)
+            mod = sys.modules[module]
+            klass= getattr(mod, name)
+
+        except (ImportError, KeyError, AttributeError):
+            raise PicklingError(
+                      "Can't pickle %r: it's not found as %s.%s" %
+                      (obj, module, name))
+        else:
+            if klass is not obj:
+                raise PicklingError(
+                    "Can't pickle %r: it's not the same object as %s.%s" %
+                    (obj, module, name))
+
+        code = copy_reg._extension_registry.get((module, name))
+        if code:
+            coder.encodeInt_forKey_(kOP_GLOBAL_EXT, kKIND)
+            coder.encodeInt_forKey_(code, kCODE)
+
+        else:
+            coder.encodeInt_forKey_(kOP_GLOBAL, kKIND)
+            coder.encodeObject_forKey_(unicode(module), kMODULE)
+            coder.encodeObject_forKey_(unicode(name), kNAME)
+
+    encode_dispatch[ClassType] = save_global
+    encode_dispatch[FunctionType] = save_global
+    encode_dispatch[BuiltinFunctionType] = save_global
+    encode_dispatch[TypeType] = save_global
+
+
+    decode_dispatch = {}
+
+    def load_none(coder, setValue):
+        return None
+    decode_dispatch[kOP_NONE] = load_none
+
+    def load_bool(coder, setValue):
+        return coder.decodeBoolForKey_(kVALUE)
+    decode_dispatch[kOP_BOOL] = load_bool
+
+    def load_int(coder, setValue):
+        return int(coder.decodeInt64ForKey_(kVALUE))
+    decode_dispatch[kOP_INT] = load_int
+
+    def load_long(coder, setValue):
+        return long(coder.decodeObjectForKey_(kVALUE))
+    decode_dispatch[kOP_LONG] = load_long
+
+    def load_float(coder, setValue):
+        return coder.decodeFloatForKey_(kVALUE)
+    decode_dispatch[kOP_FLOAT] = load_float
+
+    def load_tuple(coder, setValue):
+        return tuple(coder.decodeObjectForKey_(kVALUE))
+    decode_dispatch[kOP_TUPLE] = load_tuple
+
+    def load_list(coder, setValue):
+        return list(coder.decodeObjectForKey_(kVALUE))
+    decode_dispatch[kOP_LIST] = load_list
+
+    def load_dict(coder, setValue):
+        return dict(coder.decodeObjectForKey_(kVALUE))
+    decode_dispatch[kOP_DICT] = load_dict
+
+    def load_global_ext(coder, setValue):
+        code = coder.intForKey_(kCODE)
+        nil = []
+        obj = copy_reg._extension_cache.get(code, nil)
+        if obj is not nil:
+            return obj
+        key = copy_reg._inverted_registry.get(code)
+        if not key:
+            raise ValueError("unregistered extension code %d" % code)
+
+        module, name = key
+        __import__(module)
+        mod = sys.modules[module]
+        klass = getattr(mod, name)
+        return klass
+    decode_dispatch[kOP_GLOBAL_EXT] = load_global_ext
+
+    def load_global(coder, setValue):
+        module = coder.decodeObjectForKey_(kMODULE)
+        name = coder.decodeObjectForKey_(kNAME)
+        __import__(module)
+        mod = sys.modules[module]
+        klass = getattr(mod, name)
+        return klass
+
+    decode_dispatch[kOP_GLOBAL] = load_global
+
+
+    def load_inst(coder, setValue):
+        cls = coder.decodeObjectForKey_(kCLASS)
+        initargs = coder.decodeObjectForKey_(kARGS)
+
+        instantiated = 0
+        if (not initargs and 
+                type(cls) is ClassType and
+                not hasattr(cls, "__getinitargs__")):
+            try:
+                value = _EmptyClass()
+                value.__class__ = cls
+                instantiated = 1
+
+            except RuntimeError:
+                pass
+
+        if not instantiated:
+            try:
+                value = cls(*initargs)
+            except TypeError, err:
+                raise TypeError, "in constructor for %s: %s" % (
+                    cls.__name__, str(err)), sys.exc_info()[2]
+
+            
+        setValue(value)
+
+        state = coder.decodeObjectForKey_(kSTATE)
+        setstate = getattr(value, "__setstate__", None)
+        if setstate is not None:
+            setstate(state)
+            return value
+
+        slotstate = None
+        if isinstance(state, tuple) and len(state) == 2:
+            state, slotstate = state
+
+        if state:
+            try:
+                value.__dict__.update(state)
+            except RuntimeError:
+                for k, v in state.items():
+                    setattr(value, k, v)
+
+        if slotstate:
+            for k, v in slotstate.items():
+                setattr(value, k, v)
+
+        return value
+    decode_dispatch[kOP_INST] = load_inst
+        
+
+    def load_reduce(coder, setValue):
+        func = coder.decodeObjectForKey_(kFUNC)
+
+        # XXX: a problem: ``args`` might contain
+        # the object we want to recover (either
+        # directly or somewhere in the object graph)
+        args = coder.decodeObjectForKey_(kARGS)
+
+        value = func(*args)
+        setValue(value)
+
+        listitems = coder.decodeObjectForKey_(kLIST)
+        dictitems = coder.decodeObjectForKey_(kDICT)
+        state = coder.decodeObjectForKey_(kSTATE)
+        setstate = getattr(value, "__setstate__", None)
+        if setstate:
+            setstate(state)
+            return
+
+        slotstate = None
+        if isinstance(state, tuple) and len(state) == 2:
+            state, slotstate = state
+
+        if state:
+            try:
+                value.__dict__.update(state)
+
+            except RuntimeError:
+                for k, v in state.items():
+                    setattr(value, k, v)
+
+        if slotstate:
+            for k, v in slotstate.items():
+                setattr(value, k, v)
+
+        if listitems:
+            for a in listitems:
+                value.append(a)
+
+        if dictitems:
+            for k, v in dictitems.items():
+                value[k] = v
+
+        return value
+    decode_dispatch[kOP_REDUCE] = load_reduce
+
+
+    def pyobjectEncode(self, coder):
+        t = type(self)
+
+        # Find builtin support
+        f = encode_dispatch.get(t)
+        if f is not None:
+            f(coder, self)
+            return
+
+        # Check for a class with a custom metaclass
+        try:
+            issc = issubclass(t, TypeType)
+        except TypeError:
+            issc = 0
+
+        if issc:
+            save_global(coder, self)
+            return
+
+        # Check copy_reg.dispatch_table
+        reduce = copy_reg.dispatch_table.get(t)
+        if reduce is not None:
+            rv = reduce(self)
+
+        else:
+            reduce = getattr(self, "__reduce_ex__", None)
+            if reduce is not None:
+                rv = reduce(2)
+
+            else:
+                rv = getattr(self, "__reduce__", None)
+                if reduce is not None:
+                    rv = reduce()
+
+                else:
+                    raise PicklingError("Can't pickle %r object: %r" %
+                            (t.__name__, self))
+
+        if type(rv) is StringType:
+            save_global(coder, rv)
+            return
+
+        if type(rv) is not TupleType:
+            raise PicklingError("%s must return string or tuple" % reduce)
+
+        l = len(rv)
+        if not (2 <= l <= 5):
+            raise PicklingError("Tuple returned by %s must have two to "
+                    "five elements" % reduce)
+
+        save_reduce(coder, *rv)
+
+    def pyobjectDecode(coder, setValue):
+        tp = coder.decodeIntForKey_(kKIND)
+        f = decode_dispatch.get(tp)
+        if f is None:
+            raise UnpicklingError("Unknown object kind: %s"%(tp,))
+
+        return f(coder, setValue)
+
+    # An finally register the coder/decoder
+    OC_PythonObject.setVersion_coder_decoder_copier_(
+            1, pyobjectEncode, pyobjectDecode, copy.copy)
+
+setupPythonObject()

File pyobjc-core/Lib/objc/test/test_archive_python.py

+"""
+Testcases for NSArchive-ing python objects.
+
+(Implementation is incomplete)
+"""
+import sys, copy_reg
+
+import objc.test
+
+from objc.test.fnd import NSArchiver, NSUnarchiver
+from objc.test.fnd import NSKeyedArchiver, NSKeyedUnarchiver
+from objc.test.fnd import NSData, NSArray, NSDictionary
+from objc.test.fnd import NSMutableArray, NSMutableDictionary
+
+
+# 
+# First set of tests: the stdlib tests for pickling, this
+# should test everything but mixed Python/Objective-C 
+# object-graphs.
+#
+
+import test.pickletester
+
+MyList = test.pickletester.MyList
+
+# Quick hack to add a proper __repr__ to class C in
+# pickletester, makes it a lot easier to debug.
+def C__repr__(self):
+    return '<%s instance at %#x: %r>'%(
+        self.__class__.__name__, id(self), self.__dict__)
+test.pickletester.C.__repr__ = C__repr__
+del C__repr__
+
+def a_function():
+    pass
+
+class a_classic_class:
+    pass
+
+class a_newstyle_class (object):
+    pass
+
+def make_instance(state):
+    o = a_reducing_class()
+    o.__dict__.update(state)
+    return o
+
+class a_reducing_class (object):
+    def __reduce__(self):
+        return make_instance, (self.__dict__,)
+
+
+class TestKeyedArchiveSimple (objc.test.TestCase):
+    def testBasicObjects(self):
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(a_function)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(v is a_function)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(a_classic_class)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(v is a_classic_class)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(a_newstyle_class)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(v is a_newstyle_class)
+
+        o = a_classic_class()
+        o.x = 42
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, a_classic_class))
+        self.assertEquals(o.x, 42)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(u"hello")
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, unicode))
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_("hello")
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, str))
+        self.assertEquals(v, "hello")
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(sys.maxint * 4)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, long))
+        self.assertEquals(v, sys.maxint * 4)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(sys.maxint ** 4)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, long))
+        self.assertEquals(v, sys.maxint ** 4)
+
+    def testSimpleLists(self):
+        o = []
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, list))
+        self.assertEquals(v, o)
+
+        o = [u"hello", 42]
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, list))
+        self.assertEquals(v, o)
+
+    def testSimpleTuples(self):
+        o = ()
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, tuple))
+        self.assertEquals(v, o)
+
+        o = (u"hello", 42)
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, tuple))
+        self.assertEquals(v, o)
+
+    def testSimpleDicts(self):
+        o = {}
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, dict))
+        self.assertEquals(v, o)
+
+        o = {u"hello": u"bar", 42: 1.5 }
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, dict))
+        self.assertEquals(v, o)
+
+    def testNestedDicts(self):
+        o = {
+                u"hello": { 1:2 },
+                u"world": u"foobar"
+            }
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, dict))
+        self.assertEquals(v, o)
+
+        o = {}
+        o[u'self'] = o
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, dict))
+        self.assert_(v[u'self'] is v)
+
+    def testNestedSequences(self):
+        o = [ 1, 2, 3, (5, (u'a', u'b'), 6), {1:2} ]
+        o[-1] = o
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(isinstance(v, list))
+        self.assert_(v[-1] is v)
+        self.assertEquals(v[:-1], o[:-1])
+
+    def testNestedInstance(self):
+        o = a_classic_class()
+        o.value = o
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+        self.assert_(isinstance(v, a_classic_class))
+        self.assert_(v.value is v)
+
+    def dont_testNestedInstanceWithReduce(self):
+        # Test recursive instantation with a __reduce__ method
+        #
+        # This test is disabled because pickle doesn't support
+        # this (and we don't either)
+        o = a_reducing_class()
+        o.value = o
+
+        import pickle
+        b = pickle.dumps(o)
+        o2 = picle.loads(b)
+        print "+++", o2.value is o2
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(o)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+        self.assert_(isinstance(v, a_reducing_class))
+        print type(v.value)
+        print v.value
+        print v
+        self.assert_(v.value is v)
+
+    def testRecusiveNesting(self):
+        l = []
+        d = {1:l}
+        i = a_classic_class()
+        i.attr = d
+        l.append(i)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(l)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+        self.assertEquals(len(v), 1)
+        self.assertEquals(dir(v[0]), dir(i))
+        self.assertEquals(v[0].attr.keys(), [1])
+        self.assert_(v[0].attr[1] is v)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(d)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+        self.assert_(v[1][0].attr is v)
+        
+
+
+    def testTupleOfObjects(self):
+        o = a_classic_class()
+        t = (o, o, o)
+
+        buf = NSKeyedArchiver.archivedDataWithRootObject_(t)
+        self.assert_(isinstance(buf, NSData))
+        v = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+        self.assert_(isinstance(v, tuple))
+        self.assert_(len(v) == 3)
+        self.assert_(isinstance(v[0], a_classic_class))
+        self.assert_(v[0] is v[1])
+        self.assert_(v[0] is v[2])
+
+
+
+class TestKeyedArchivePlainPython (objc.test.TestCase, test.pickletester.AbstractPickleTests):
+    # Ensure that we don't run every test case three times
+    def setUp(self):
+        self._protocols = test.pickletester.protocols
+        test.pickletester.protocols = (2,)
+
+    def tearDown(self):
+        test.pickletester.protoocols = self._protocols
+
+
+    def dumps(self, arg, proto=0, fast=0):
+        # Ignore proto and fast
+        return NSKeyedArchiver.archivedDataWithRootObject_(arg)
+
+    def loads(self, buf):
+        return NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+    
+    # Disable a number of methods, these test things we're not interested in.
+    # (Most of these look at the generated byte-stream, as we're not writing data in pickle's
+    # format such tests are irrelevant to archiving support)
+    def test_insecure_strings(self): pass
+    def test_load_from_canned_string(self): pass
+    def test_maxint64(self): pass
+    def test_dict_chunking(self): pass
+    def test_float_format(self): pass
+    def test_garyp(self): pass
+    def test_list_chunking(self): pass
+    def test_singletons(self): pass
+    def test_simple_newobj(self): pass
+    def test_short_tuples(self): pass
+    def test_proto(self): pass
+    def test_long1(self): pass
+    def test_long4(self): pass
+
+
+    def test_long(self):
+        # The real test_long method takes way to much time, test a subset
+        x = 12345678910111213141516178920L << (256*8)
+        buf = self.dumps(x)
+        v = self.loads(buf)
+        self.assertEquals(v, x)
+
+        x = -x
+
+        buf = self.dumps(x)
+        v = self.loads(buf)
+
+        self.assertEquals(v, x)
+
+        for val in (0L, 1L, long(sys.maxint), long(sys.maxint * 128)):
+            for x in val, -val:
+                buf = self.dumps(x)
+                v = self.loads(buf)
+                self.assertEquals(v, x)
+
+
+
+
+
+    # Overriden tests for extension codes, the test code checks
+    # the actual byte stream.
+    def produce_global_ext(self, extcode, opcode):
+        e = test.pickletester.ExtensionSaver(extcode)
+        try:
+            copy_reg.add_extension(__name__, "MyList", extcode)
+            x = MyList([1, 2, 3])
+            x.foo = 42
+            x.bar = "hello"
+
+            s1 = self.dumps(x, 1)
+            y = self.loads(s1)
+            self.assertEqual(list(x), list(y))
+            self.assertEqual(x.__dict__, y.__dict__)
+        finally:
+            e.restore()
+
+    #
+    # The test_reduce* methods iterate over various protocol
+    # versions. Override to only look at protocol version 2.
+    #
+    def test_reduce_overrides_default_reduce_ex(self):
+        for proto in 2,: 
+            x = test.pickletester.REX_one()
+            self.assertEqual(x._reduce_called, 0)
+            s = self.dumps(x, proto)
+            self.assertEqual(x._reduce_called, 1)
+            y = self.loads(s)
+            self.assertEqual(y._reduce_called, 0)
+
+    def test_reduce_ex_called(self):
+        for proto in 2,: 
+            x = test.pickletester.REX_two()
+            self.assertEqual(x._proto, None)
+            s = self.dumps(x, proto)
+            self.assertEqual(x._proto, proto)
+            y = self.loads(s)
+            self.assertEqual(y._proto, None)
+
+    def test_reduce_ex_overrides_reduce(self):
+        for proto in 2,:
+            x = test.pickletester.REX_three()
+            self.assertEqual(x._proto, None)
+            s = self.dumps(x, proto)
+            self.assertEqual(x._proto, proto)
+            y = self.loads(s)
+            self.assertEqual(y._proto, None)
+
+    def test_reduce_ex_calls_base(self):
+        for proto in 2,:
+            x = test.pickletester.REX_four()
+            self.assertEqual(x._proto, None)
+            s = self.dumps(x, proto)
+            self.assertEqual(x._proto, proto)
+            y = self.loads(s)
+            self.assertEqual(y._proto, proto)
+
+    def test_reduce_calls_base(self):
+        for proto in 2,:
+            x = test.pickletester.REX_five()
+            self.assertEqual(x._reduce_called, 0)
+            s = self.dumps(x, proto)
+            self.assertEqual(x._reduce_called, 1)
+            y = self.loads(s)
+            self.assertEqual(y._reduce_called, 1)
+
+
+#
+# Disable testing of plain Archiving for now, need full support
+# for keyed-archiving first, then worry about adding "classic"
+# archiving.
+# 
+#class TestArchivePlainPython (TestKeyedArchivePlainPython):
+#    def dumps(self, arg, proto=0, fast=0):
+#        # Ignore proto and fast
+#        return NSArchiver.archivedDataWithRootObject_(arg)
+#
+#    def loads(self, buf):
+#        return NSUnarchiver.unarchiveObjectWithData_(buf)
+
+
+# 
+# Second set of tests: test if archiving a graph that
+# contains both python and objective-C objects works correctly.
+#
+class TestKeyedArchiveMixedGraphs (objc.test.TestCase):
+    def dumps(self, arg, proto=0, fast=0):
+        # Ignore proto and fast
+        return NSKeyedArchiver.archivedDataWithRootObject_(arg)
+
+    def loads(self, buf):
+        return NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
+
+    def test_list1(self):
+        o1 = a_classic_class()
+        o2 = a_newstyle_class()
+        o2.lst = NSArray.arrayWithObject_(o1)
+        l = NSArray.arrayWithArray_([o1, o2, [o1, o2]])
+
+        buf = self.dumps(l)
+        self.assert_(isinstance(buf, NSData))
+
+        out = self.loads(buf)
+        self.assert_(isinstance(out, NSArray))
+        self.assertEquals(len(out), 3)
+
+        p1 = out[0]
+        p2 = out[1]
+        p3 = out[2]
+
+        self.assert_(isinstance(p1, a_classic_class))
+        self.assert_(isinstance(p2, a_newstyle_class))
+        self.assert_(isinstance(p3, list))
+        self.assert_(p3[0] is p1)
+        self.assert_(p3[1] is p2)
+        self.assert_(isinstance(p2.lst , NSArray))
+        self.assert_(p2.lst[0] is p1)
+       
+
+
+
+
+#
+# And finally some tests to check if archiving of Python
+# subclasses of NSObject works correctly.
+#
+class TestArchivePythonObjCSubclass (objc.test.TestCase):
+    pass
+
+if __name__ == "__main__":
+    objc.test.main()

File pyobjc-core/Lib/objc/test/test_archiving.py

-"""
-Some testcases for archiving basic python types.
-
-Note that archiving isn't supported in the general case,
-just for basic python types (dict, list, tuple, int, float)
-
-XXX: this doesn't work correctly, both cyclic tests fail badly.
-"""
-import objc.test
-import objc
-import sys
-
-NSObject = objc.lookUpClass('NSObject')
-NSArchiver = objc.lookUpClass('NSArchiver')
-NSUnarchiver = objc.lookUpClass('NSUnarchiver')
-NSArray = objc.lookUpClass('NSArray')
-NSDictionary = objc.lookUpClass('NSDictionary')
-
-class TestArchivingPythonBasicTypes (objc.test.TestCase):
-    def testFloat(self):
-        for value in [-1.0, -42432.9, 5.4, 643e99, 1.0e180 * 1.0e180 ]:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, float) )
-
-            self.assertEquals(value, newVal)
-
-    def testInt(self):
-        if sys.maxint > 2 * 32:
-            VALS = [sys.maxint,  -sys.maxint - 1, 5, 5325 ]
-        else:
-            VALS = [sys.maxint,  -sys.maxint - 1, sys.maxint + 10 , -sys.maxint - 20, 5, 5325 ]
-        for value in VALS:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-
-            self.assert_( isinstance(newVal, (int, long)) )
-
-            self.assertEquals(value, newVal)
-
-    def testStr(self):
-        for value in [ 'aap', 'noot', '',  'mies' ]:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, unicode) )
-            self.assertEquals(value, newVal)
-
-    def testUnicode(self):
-        for value in [ u'aap', u'noot', u'',  u'mies', unichr(4000) ]:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, unicode) )
-            self.assertEquals(value, newVal)
-
-
-    def testTuple(self):
-        for value in [ (1,2,3), ('a', 4, 'd') ]:
-            self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-            """ These would be nice, but are non-trivial:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, NSArray) )
-            self.assertEquals(value, newVal)
-            """
-
-    def testList(self):
-        for value in [ [1,2,3], ['a', 4, 'd'] ]:
-            self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-            """ These would be nice, but are non-trivial:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, NSArray) )
-            self.assertEquals(value, newVal)
-            """
-
-    def testDictionary(self):
-        for value in [ dict(a='b', b=42), {1:99, 2:'a'} ]:
-            self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-            """ These would be nice, but are non-trivial:
-            dt = NSArchiver.archivedDataWithRootObject_(value)
-            self.assert_( dt is not None )
-
-            newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-            self.assert_( isinstance(newVal, NSDictionary) )
-            self.assertEquals(value, newVal)
-            """
-
-    def testComplex(self):
-        value = {
-            'a': 99,
-            'b': [1, 2, 3, 4],
-            'c': { 1: 99, 2:44 },
-            (1,2,3): ['a', {'b':'c'}, 99],
-        }
-
-        self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-        """ These would be nice, but are non-trivial:
-
-        dt = NSArchiver.archivedDataWithRootObject_(value)
-        self.assert_( dt is not None )
-
-        newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-        self.assert_( isinstance(newVal, NSDictionary) )
-        self.assertEquals(value, newVal)
-
-        value = NSDictionary.dictionaryWithDictionary_({
-                'a': NSArray.arrayWithArray_([{'a': 'b'}])
-            })
-
-        dt = NSArchiver.archivedDataWithRootObject_(value)
-        self.assert_( dt is not None )
-
-        newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-        self.assert_( isinstance(newVal, NSDictionary) )
-        self.assertEquals(value, newVal)
-        """
-
-    def testCyclicList(self):
-        value = [ 1, 2 ]
-        value.append(value)
-
-        self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-        """ These would be nice, but are non-trivial:
-        dt = NSArchiver.archivedDataWithRootObject_([value])
-        self.assert_( dt is not None )
-
-        newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-        self.assert_( isinstance(newVal, NSArray) )
-        self.assertEquals(value, newVal)
-        """
-
-    def testCyclicDictionary(self):
-        value = {'a': 'b'}
-        value['c'] = value
-
-        self.assertRaises(ValueError, NSArchiver.archivedDataWithRootObject_, value)
-        """ These would be nice, but are non-trivial:
-        dt = NSArchiver.archivedDataWithRootObject_(value)
-        self.assert_( dt is not None )
-
-        newVal = NSUnarchiver.unarchiveObjectWithData_(dt)
-        self.assert_( isinstance(newVal, NSDictionary) )
-        self.assertEquals(value, newVal)
-        """
-
-
-
-if __name__ == "__main__":
-    objc.test.main()

File pyobjc-core/Lib/objc/test/test_exceptions.py

                 u'key2\u1234\u2049': u'value2\u1234\u2049',
             })
 
+    def testRaisingStringsInObjectiveC(self):
+        # Bug #1741095, @throw anNSString
+
+        o = PyObjCTestExceptions.alloc().init()
+        try:
+            o.raiseAString()
+
+        except objc.error, e:
+            self.assertEquals(e._pyobjc_exc_, u"thrown string")
+
 if __name__ == "__main__":
     unittest.main()

File pyobjc-core/Lib/objc/test/test_pickling_objc.py

+"""
+Test cases for testing if it is possible to pickle
+Objective-C objects
+"""
+import objc.test
+
+
+# Test cases for pickling ObjC objects 
+class TestPickleObjC (objc.test.TestCase):
+    pass
+
+
+# Test cases for pickling mixed Python/ObjC 
+# object graphs
+class TestPickleMixedGraph (objc.test.TestCase):
+    pass
+
+
+# Test cases for pickling Python subclasses
+# of NSObject
+class TestPicklePythonNSObject (objc.test.TestCase):
+    pass
+
+if __name__ == "__main__":
+    objc.test.main()

File pyobjc-core/Modules/objc/OC_PythonArray.m

 	return value;
 }
 
+-(PyObject*)__pyobjc_PythonTransient__:(int*)cookie
+{
+	*cookie = 0;
+	Py_INCREF(value);
+	return value;
+}
+
 -(void)release
 {
 	/* See comment in OC_PythonUnicode */
 	PyObjC_END_WITH_GIL;
 }
 
-- (void)encodeWithCoder:(NSCoder*)coder
+-(void)encodeWithCoder:(NSCoder*)coder
 {
-	/* 
-	 * Forcefully disable coding for now, to avoid generating invalid
-	 * encoded streams.
-	 */        
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Encoding python objects of type %s is not supported", value->ob_type->tp_name, coder];
-	
+	/*
+	 * Instances of 'list' and 'tuple' are encoded directly,
+	 * for other sequences use the generic pickle support code.
+	 */
+	if (1 && PyTuple_CheckExact(value)) {
+		if ([coder allowsKeyedCoding]) {
+			[coder encodeInt32:1 forKey:@"pytype"];
+		} else {
+			int v = 1;
+			[coder encodeValueOfObjCType:@encode(int) at:&v];
+		}
+		[super encodeWithCoder:coder];
+	} else if (1 && PyList_CheckExact(value)) {
+		if ([coder allowsKeyedCoding]) {
+			[coder encodeInt32:2 forKey:@"pytype"];
+		} else {
+			int v = 2;
+			[coder encodeValueOfObjCType:@encode(int) at:&v];
+		}
+		[super encodeWithCoder:coder];
+	} else {
+		if ([coder allowsKeyedCoding]) {
+			[coder encodeInt32:3 forKey:@"pytype"];
+		} else {
+			int v = 3;
+			[coder encodeValueOfObjCType:@encode(int) at:&v];
+		}
+		PyObjC_encodeWithCoder(value, coder);
+
+	}
 }
 
-- initWithCoder:(NSCoder*)coder
+/*
+ * A basic implementation of -initWithObjects:count:. This method is needed
+ * to support NSCoding for Python sequences.
+ */
+-(id)initWithObjects:(NSObject**)objects count:(NSUInteger)count
 {
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Decoding python objects is not supported", coder];
+	NSUInteger i;
+	PyObjC_BEGIN_WITH_GIL
+		for  (i = 0; i < count; i++) {
+			PyObject* v = PyObjC_IdToPython(objects[i]);
+			if (v == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+			int r = PyList_Append(value,  v);
+			if (r == -1) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+			Py_DECREF(v);
+		}
+
+	PyObjC_END_WITH_GIL
+	return self;
+}
+
+/* 
+ * Helper method for initWithCoder, needed to deal with
+ * recursive objects (e.g. o.value = o)
+ */
+-(void)pyobjcSetValue:(NSObject*)other
+{
+	PyObject* v = PyObjC_IdToPython(other);
+	Py_XDECREF(value);
+	value = v;
+}
+
+-(id)initWithCoder:(NSCoder*)coder
+{
+	PyObject* t;
+	int code;
+	if ([coder allowsKeyedCoding]) {
+		code = [coder decodeInt32ForKey:@"pytype"];
+	} else {
+		[coder decodeValueOfObjCType:@encode(int) at:&code];
+	}
+
+	PyObjC_BEGIN_WITH_GIL
+		value = PyList_New(0);
+		if (value == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+	PyObjC_END_WITH_GIL
+
+	switch (code) {
+	case 1:
+	      [super initWithCoder:coder];
+	      PyObjC_BEGIN_WITH_GIL
+		      t = value;
+		      value = PyList_AsTuple(t);
+		      Py_DECREF(t);
+		      if (value == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+		      }
+	      PyObjC_END_WITH_GIL
+	      return self;
+
+	case 2:
+	      [super initWithCoder:coder];
+	      return self;
+
+	case 3:
+
+		if (PyObjC_Decoder != NULL) {
+			PyObjC_BEGIN_WITH_GIL
+				PyObject* cdr = PyObjC_IdToPython(coder);
+				if (cdr == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+
+				PyObject* setValue;
+				PyObject* selfAsPython = PyObjCObject_New(self, 0, YES);
+				setValue = PyObject_GetAttrString(selfAsPython, "pyobjcSetValue_");
+
+				PyObject* v = PyObject_CallFunction(PyObjC_Decoder, "OO", cdr, setValue);
+				Py_DECREF(cdr);
+				Py_DECREF(setValue);
+				Py_DECREF(selfAsPython);
+
+				if (v == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+
+				Py_XDECREF(value);
+				value = v;
+
+				NSObject* proxy = PyObjC_FindObjCProxy(value);
+				if (proxy == NULL) {
+					PyObjC_RegisterObjCProxy(value, self);
+				} else {
+					[self release];
+					[proxy retain];
+					self = (OC_PythonArray*)proxy;
+				}
+
+
+			PyObjC_END_WITH_GIL
+
+			return self;
+		}
+	}
+
+	[NSException raise:NSInvalidArgumentException
+			format:@"decoding Python objects is not supported"];
+	[self release];
 	return nil;
 }
 
 
+#if 1
+-(NSObject*)replacementObjectForArchiver:(NSArchiver*)archiver 
+{
+	(void)(archiver);
+	return self;
+}
+
+-(NSObject*)replacementObjectForKeyedArchiver:(NSKeyedArchiver*)archiver
+{
+	(void)(archiver);
+	return self;
+}
+
+-(NSObject*)replacementObjectForCoder:(NSKeyedArchiver*)archiver
+{
+	(void)(archiver);
+	return self;
+}
+
+-(NSObject*)replacementObjectForPortCoder:(NSKeyedArchiver*)archiver
+{
+	(void)(archiver);
+	return self;
+}
+
+-(Class)classForArchiver
+{
+	return [OC_PythonArray class];
+}
+
+-(Class)classForKeyedArchiver
+{
+	return [OC_PythonArray class];
+}
+
+-(Class)classForCoder
+{
+	return [OC_PythonArray class];
+}
+
+-(Class)classForPortCoder
+{
+	return [OC_PythonArray class];
+}
+#endif
+
 @end /* implementation OC_PythonArray */

File pyobjc-core/Modules/objc/OC_PythonData.m

 	return value;
 }
 
+-(PyObject*)__pyobjc_PythonTransient__:(int*)cookie
+{
+	*cookie = 0;
+	Py_INCREF(value);
+	return value;
+}
+
+
 -(void)release
 {
 	/* See comment in OC_PythonUnicode */

File pyobjc-core/Modules/objc/OC_PythonDate.m

 	Py_INCREF(value);
 	return value;
 }
+-(PyObject*)__pyobjc_PythonTransient__:(int*)cookie
+{
+	*cookie = 0;
+	Py_INCREF(value);
+	return value;
+}
 
 -(void)release
 {
 
 - (void)encodeWithCoder:(NSCoder*)coder
 {
-	/* 
-	 * Forcefully disable coding for now, to avoid generating invalid
-	 * encoded streams.
-	 */        
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Encoding python objects of type %s is not supported", value->ob_type->tp_name, coder];
-	
+	PyObjC_encodeWithCoder(value, coder);
+}
+
+
+/* 
+ * Helper method for initWithCoder, needed to deal with
+ * recursive objects (e.g. o.value = o)
+ */
+-(void)pyobjcSetValue:(NSObject*)other
+{
+	PyObject* v = PyObjC_IdToPython(other);
+	Py_XDECREF(value);
+	value = v;
 }
 
 - initWithCoder:(NSCoder*)coder
 {
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Decoding python objects is not supported", coder];
-	return nil;
+	value = NULL;
+
+	if (PyObjC_Decoder != NULL) {
+		PyObjC_BEGIN_WITH_GIL
+			PyObject* cdr = PyObjC_IdToPython(coder);
+			if (cdr == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+			PyObject* setValue;
+			PyObject* selfAsPython = PyObjCObject_New(self, 0, YES);
+			setValue = PyObject_GetAttrString(selfAsPython, "pyobjcSetValue_");
+
+			PyObject* v = PyObject_CallFunction(PyObjC_Decoder, "OO", cdr, setValue);
+			Py_DECREF(cdr);
+			Py_DECREF(setValue);
+			Py_DECREF(selfAsPython);
+
+			if (v == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+			Py_XDECREF(value);
+			value = v;
+
+			NSObject* proxy = PyObjC_FindObjCProxy(value);
+			if (proxy == NULL) {
+				PyObjC_RegisterObjCProxy(value, self);
+			} else {
+				[self release];
+				[proxy retain];
+				self = (OC_PythonDate*)proxy;
+			}
+
+
+		PyObjC_END_WITH_GIL
+
+		return self;
+
+	} else {
+		[NSException raise:NSInvalidArgumentException
+				format:@"decoding Python objects is not supported"];
+		return nil;
+
+	}
 }
 
 -(NSDate*)_make_oc_value

File pyobjc-core/Modules/objc/OC_PythonDictionary.m

 	Py_INCREF(value);
 	return value;
 }
+-(PyObject*)__pyobjc_PythonTransient__:(int*)cookie
+{
+	*cookie = 0;
+	Py_INCREF(value);
+	return value;
+}
 
 -(NSUInteger)count
 {
 }
 
 
-- (void)encodeWithCoder:(NSCoder*)coder
+- initWithObjects:(NSObject**)objects 
+	  forKeys:(NSObject**)keys 
+	    count:(NSUInteger)count
 {
-	/* 
-	 * Forcefully disable coding for now, to avoid generating invalid
-	 * encoded streams.
-	 */        
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Encoding python objects of type %s is not supported", value->ob_type->tp_name, coder];
+	/* This implementation is needed for our support for the NSCoding
+	 * protocol, NSDictionary's initWithCoder: will call this method.
+	 */
+	NSUInteger i;
+
+	PyObjC_BEGIN_WITH_GIL
+		for  (i = 0; i < count; i++) {
+			PyObject* k;
+			PyObject* v;
+			int r;
+
+			if (objects[i] == [NSNull null]) {
+				v = Py_None;
+				Py_INCREF(Py_None);
+			} else {
+				v = PyObjC_IdToPython(objects[i]);
+				if (v == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+			}
+
+			if (keys[i] == [NSNull null]) {
+				k = Py_None;
+				Py_INCREF(Py_None);
+			} else {
+				k = PyObjC_IdToPython(keys[i]);
+				if (k == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+			}
+
+			r = PyDict_SetItem(value, k, v);
+			Py_DECREF(k); Py_DECREF(v);
+
+			if (r == -1) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+		}
+	PyObjC_END_WITH_GIL
+	return self;
+}
+
+/* 
+ * Helper method for initWithCoder, needed to deal with
+ * recursive objects (e.g. o.value = o)
+ */
+-(void)pyobjcSetValue:(NSObject*)other
+{
+	PyObject* v = PyObjC_IdToPython(other);
+	Py_XDECREF(value);
+	value = v;
 }
 
 - initWithCoder:(NSCoder*)coder
 {
-	[NSException raise:NSInvalidArgumentException format:@"PyObjC: Decoding python objects is not supported", coder];
+	int code;
+	if ([coder allowsKeyedCoding]) {
+		code = [coder decodeInt32ForKey:@"pytype"];
+	} else {
+		[coder decodeValueOfObjCType:@encode(int) at:&code];
+	}
+	switch (code) {
+	case 1:
+		PyObjC_BEGIN_WITH_GIL
+			value = PyDict_New();
+			if (value == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+		PyObjC_END_WITH_GIL
+
+		self = [super initWithCoder:coder];
+		return self;
+	
+	case 2:
+		if (PyObjC_Decoder != NULL) {
+			PyObjC_BEGIN_WITH_GIL
+				PyObject* cdr = PyObjC_IdToPython(coder);
+				if (cdr == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+
+				PyObject* setValue;
+				PyObject* selfAsPython = PyObjCObject_New(self, 0, YES);
+				setValue = PyObject_GetAttrString(selfAsPython, "pyobjcSetValue_");
+
+				PyObject* v = PyObject_CallFunction(PyObjC_Decoder, "OO", cdr, setValue);
+				Py_DECREF(cdr);
+				Py_DECREF(setValue);
+				Py_DECREF(selfAsPython);
+
+				if (v == NULL) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+
+				Py_XDECREF(value);
+				value = v;
+
+				NSObject* proxy = PyObjC_FindObjCProxy(value);
+				if (proxy == NULL) {
+					PyObjC_RegisterObjCProxy(value, self);
+				} else {
+					[self release];
+					[proxy retain];
+					self = (OC_PythonDictionary*)proxy;
+				}
+
+
+			PyObjC_END_WITH_GIL
+
+			return self;
+
+		} else {
+			[NSException raise:NSInvalidArgumentException
+					format:@"decoding Python objects is not supported"];
+			return nil;
+
+		}
+	}
+	[NSException raise:NSInvalidArgumentException
+			format:@"decoding Python objects is not supported"];
+	[self release];
 	return nil;
 }
 
+- (void)encodeWithCoder:(NSCoder*)coder
+{
+	if (1 && PyDict_CheckExact(value)) {
+		if ([coder allowsKeyedCoding]) {
+			[coder encodeInt32:1 forKey:@"pytype"];
+		} else {
+			int v = 1;
+			[coder encodeValueOfObjCType:@encode(int) at:&v];
+		}
+		[super encodeWithCoder:coder];
+
+	} else {
+		if ([coder allowsKeyedCoding]) {
+			[coder encodeInt32:2 forKey:@"pytype"];
+		} else {
+			int v = 2;
+			[coder encodeValueOfObjCType:@encode(int) at:&v];
+		}
+		PyObjC_encodeWithCoder(value, coder);
+
+	}
+}
+
+
+#if 1
+
+-(NSObject*)replacementObjectForArchiver:(NSArchiver*)archiver
+{
+	(void)archiver;
+	return self;
+}
+
+-(NSObject*)replacementObjectForKeyedArchiver:(NSKeyedArchiver*)archiver
+{
+	(void)archiver;
+	return self;
+}
+
+
+-(Class)classForArchiver
+{
+	return [OC_PythonDictionary class];
+}
+
+-(Class)classForKeyedArchiver
+{
+	return [OC_PythonDictionary class];
+}
+
+-(Class)classForCoder
+{
+	return [OC_PythonDictionary class];
+}
+
+-(Class)classForPortCoder
+{
+	return [OC_PythonDictionary class];
+}
+
+#endif
+
 @end  // interface OC_PythonDictionary

File pyobjc-core/Modules/objc/OC_PythonNumber.h

+#include "pyobjc.h"
+
+@interface OC_PythonNumber : NSNumber
+{
+	PyObject* value;
+#if 0
+	union {
+		long long 		as_longlong;
+		unsigned long long 	as_ulonglong;
+		double    		as_double;
+	}	c_value;
+#endif
+}
+
++ newWithPythonObject:(PyObject*)value;
+- initWithPythonObject:(PyObject*)value;
+-(void)dealloc;
+-(PyObject*)__pyobjc_PythonObject__;
+
+@end

File pyobjc-core/Modules/objc/OC_PythonNumber.m

+#include "pyobjc.h"
+
+@implementation OC_PythonNumber
+
++ newWithPythonObject:(PyObject*)v;
+{
+	OC_PythonNumber* res;
+
+	res = [[OC_PythonNumber alloc] initWithPythonObject:v];
+	[res autorelease];
+	return res;
+}
+
+- initWithPythonObject:(PyObject*)v;
+{
+	self = [super init];
+	if (unlikely(self == nil)) return nil;
+
+	Py_INCREF(v);
+	Py_XDECREF(value);
+	value = v;
+	return self;
+}
+
+-(PyObject*)__pyobjc_PythonObject__
+{
+	Py_INCREF(value);
+	return value;
+}
+
+-(PyObject*)__pyobjc_PythonTransient__:(int*)cookie
+{
+	*cookie = 0;
+	Py_INCREF(value);
+	return value;
+}
+
+-(void)release
+{
+	/* See comment in OC_PythonUnicode */
+	PyObjC_BEGIN_WITH_GIL
+		[super release];
+	PyObjC_END_WITH_GIL
+}
+
+-(void)dealloc
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObjC_UnregisterObjCProxy(value, self);
+		Py_XDECREF(value);
+
+	PyObjC_END_WITH_GIL
+
+	[super dealloc];
+}
+
+-(const char*)objCType
+{
+	PyObjC_BEGIN_WITH_GIL
+		if (PyBool_Check(value)) {
+			PyObjC_GIL_RETURN(@encode(BOOL));
+		} else if (PyFloat_Check(value)) {
+			PyObjC_GIL_RETURN(@encode(double));
+		} else if (PyInt_Check(value)) {
+			PyObjC_GIL_RETURN(@encode(long));
+		} else if (PyLong_Check(value)) {
+			PyObjC_GIL_RETURN(@encode(long long));
+		} 
+	PyObjC_END_WITH_GIL
+	[NSException raise:NSInvalidArgumentException 
+		    format:@"Cannot determine objective-C type of this number"];
+	return @encode(char);
+}
+
+-(void)getValue:(void*)buffer
+{
+	const char* encoded = [self objCType];
+	int r;
+	PyObjC_BEGIN_WITH_GIL
+		r = depythonify_c_value(encoded, value, buffer);
+		if (r == -1) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+	PyObjC_END_WITH_GIL
+}
+
+/* TODO:
+-(BOOL)isEqualToValue:(NSValue*)other
+  // convert other to Python, then use python's comparison operators to
+  // check for equality.
+}
+*/
+
+
+-(BOOL)boolValue
+{
+	return (BOOL)PyObject_IsTrue(value);
+}
+
+-(char)charValue
+{
+	return (char)[self longLongValue];
+}
+
+-(NSDecimal)decimalValue
+{
+	/* FIXME */
+	[NSException raise:NSInvalidArgumentException
+	              format:@"Cannot convert python number to NSDecimal"];
+}
+
+-(double)doubleValue
+{
+	PyObjC_BEGIN_WITH_GIL
+		if (PyFloat_Check(value)) {
+			PyObjC_GIL_RETURN(PyFloat_AsDouble(value));
+		} 
+	PyObjC_END_WITH_GIL
+	return (double)[self longLongValue];
+}
+
+-(float)floatValue
+{
+	return (float)[self doubleValue];
+}
+
+-(NSInteger)integerValue
+{
+	return (NSInteger)[self longLongValue];
+}
+
+-(int)intValue
+{
+	return (int)[self longLongValue];
+}
+
+
+-(long)longValue
+{
+	return (long)[self longLongValue];
+}
+
+-(short)shortValue
+{
+	return (short)[self longLongValue];
+}
+
+
+-(unsigned char)unsignedCharValue
+{
+	return (unsigned char)[self unsignedLongLongValue];
+}
+-(NSUInteger)unsignedIntegerValue
+{
+	return (NSUInteger)[self unsignedLongLongValue];
+}
+-(unsigned int)unsignedIntValue
+{
+	return (unsigned int)[self unsignedLongLongValue];
+}
+-(unsigned long)unsignedLongValue
+{
+	return (unsigned long)[self unsignedLongLongValue];
+}
+-(unsigned short)unsignedShortValue
+{
+	return (unsigned short)[self unsignedLongLongValue];
+}
+
+-(long long)longLongValue
+{
+	long long result;
+
+	PyObjC_BEGIN_WITH_GIL
+		if (PyInt_Check(value)) {
+			result =  PyInt_AsLong(value);
+			PyObjC_GIL_RETURN(result);
+		} else if (PyFloat_Check(value)) {
+			result =  (long long)PyFloat_AsDouble(value);
+			PyObjC_GIL_RETURN(result);
+		} else if (PyLong_Check(value)) {
+			result =  PyLong_AsLongLong(value);
+			PyObjC_GIL_RETURN(result);
+		}
+	PyObjC_END_WITH_GIL
+
+	[NSException raise:NSInvalidArgumentException 
+		    format:@"Cannot determine objective-C type of this number"];
+	return -1;
+}
+
+-(unsigned long long)unsignedLongLongValue
+{
+	unsigned long long result;
+
+	PyObjC_BEGIN_WITH_GIL
+		if (PyInt_Check(value)) {
+			result =  (unsigned long long)PyInt_AsLong(value);
+			PyObjC_GIL_RETURN(result);
+		} else if (PyFloat_Check(value)) {
+			result =  (unsigned long long)PyFloat_AsDouble(value);
+			PyObjC_GIL_RETURN(result);
+		} else if (PyLong_Check(value)) {
+			result =  PyLong_AsUnsignedLongLong(value);
+			PyObjC_GIL_RETURN(result);
+		}
+	PyObjC_END_WITH_GIL
+
+	[NSException raise:NSInvalidArgumentException 
+		    format:@"Cannot determine objective-C type of this number"];
+	return -1;
+}
+
+-(NSString*)description
+{
+	return [self stringValue];
+}
+