Commits

Ronald Oussoren committed 44cd16c

- unicode-object.m, NEWS: Add pickling support, we pickle in such way
that our unicode objects are restored as normal unicode objects
- objc-object.m, NEWS: Add __reduce__ method to make sure pickling won't
work, not even with protocol version 2. Without this fix, pickling with
protocol version 2 would dump incomplete objects.
- setup.py: Fix dependencies and PyPI information
- class-builder.m: Workaround for undocumented runtime feature:
the method lists use '-1' as a terminator. PyObjC sometimes caused
crashes because of this :-(

Comments (0)

Files changed (8)

pyobjc/Lib/Foundation/test/test_nsstring.py

 import unittest
 import objc
+import types
 
 from Foundation import *
 
 
         self.assertEquals(pyStr, "hello world")
 
+class TestPickle(unittest.TestCase):
+    """
+    Testcases for pickling of Objective-C strings. Those are pickled as
+    unicode strings.
+    """
+
+    def setUp(self):
+        self.strVal = NSTaskDidTerminateNotification
+
+    def testPickle(self):
+        """
+        Check that ObjC-strings pickle as unicode strings
+        """
+        import pickle
+
+        s = pickle.dumps(self.strVal, 0)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
+        s = pickle.dumps(self.strVal, 1)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
+        s = pickle.dumps(self.strVal, 2)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
+    def testCPickle(self):
+        """
+        Check that ObjC-strings pickle as unicode strings
+        """
+        import cPickle as pickle
+
+        s = pickle.dumps(self.strVal, 0)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
+        s = pickle.dumps(self.strVal, 1)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
+        s = pickle.dumps(self.strVal, 2)
+        v = pickle.loads(s)
+        self.assertEquals(type(v), types.UnicodeType)
+
 if __name__ == '__main__':
-    unittest.main( )
+    unittest.main()

pyobjc/Lib/objc/test/test_objc.py

 
 class TestClassDict(unittest.TestCase):
     def testDict(self):
-        self.assert_("drawAtPoint_" in objc.runtime.NSAttributedString.__dict__)
+        self.assert_("attributesAtIndex_longestEffectiveRange_inRange_" in objc.runtime.NSAttributedString.__dict__)
 
+class TestPickle(unittest.TestCase):
+    # We don't support pickling at the moment, make sure we enforce that.
+
+    def testPicklePure(self):
+        import pickle
+
+        o = objc.runtime.NSObject.alloc().init()
+        self.assertRaises(TypeError, pickle.dumps, o, 0)
+        self.assertRaises(TypeError, pickle.dumps, o, 1)
+        self.assertRaises(TypeError, pickle.dumps, o, 2)
+
+    def testCPicklePure(self):
+        import cPickle as pickle
+
+        o = objc.runtime.NSObject.alloc().init()
+        self.assertRaises(TypeError, pickle.dumps, o, 0)
+        self.assertRaises(TypeError, pickle.dumps, o, 1)
+        self.assertRaises(TypeError, pickle.dumps, o, 2)
 
 if __name__ == '__main__':
     unittest.main()

pyobjc/Modules/objc/class-builder.m

 	new_class->class.METHODLISTS = 
 		calloc(1, sizeof(struct objc_method_list*));
 	if (new_class->class.METHODLISTS == NULL) goto error_cleanup;
-	new_class->class.METHODLISTS[0] = NULL;
 
 	new_class->meta_class.METHODLISTS = 
 		calloc(1, sizeof(struct objc_method_list*));
 	if (new_class->meta_class.METHODLISTS == NULL) goto error_cleanup;
-	new_class->meta_class.METHODLISTS[0] = NULL;
+
+	/* 
+	 * This is MacOS X specific, and an undocumented feature (long live
+	 * Open Source!). 
+	 *
+	 * The code in the objc runtime assumes that the method lists are 
+	 * terminated by '-1', and will happily overwite existing data if
+	 * they aren't.
+	 */
+	new_class->class.METHODLISTS[0] = (struct objc_method_list*)-1;
+	new_class->meta_class.METHODLISTS[0] = (struct objc_method_list*)-1;
+
 #endif
 
 	new_class->class.super_class = super_class;

pyobjc/Modules/objc/module.m

 
 		/* install in methods to add */
 		objcMethod = &methodsToAdd->method_list[methodIndex];
-		objcMethod->method_name = sel_registerName(PyObjCSelector_Selector(aMethod));
+		objcMethod->method_name = PyObjCSelector_Selector(aMethod);
 
 		objcMethod->method_types = strdup(PyObjCSelector_Signature(
 			aMethod));

pyobjc/Modules/objc/objc-object.m

 	{ 0, 0, 0, 0, 0 }
 };
 
+/*
+ * We don't support pickling of Objective-C objects at the moment. The new
+ * version 2 of the pickle protocol has a default pickle method for new-style
+ * classes that doesn't work for us (it will write incomplete values to the
+ * pickle). This method forces a failure during pickling.
+ */
+static PyObject*
+meth_reduce(PyObject* self)
+{
+        PyErr_SetString(PyExc_TypeError,
+		"Cannot pickle Objective-C objects");
+	return NULL;
+}
+
+static PyMethodDef obj_methods[] = {
+	{
+		"__reduce__",
+		(PyCFunction)meth_reduce,
+		METH_NOARGS,
+		"Used for pickling"
+	},
+	{
+		NULL,
+		NULL,
+		0,
+		NULL
+	}
+};
+
+
 PyObjCClassObject PyObjCObject_Type = {
 #ifdef PyObjC_CLASS_INFO_IN_TYPE
    {
 	0,					/* tp_weaklistoffset */
 	0,					/* tp_iter */
 	0,					/* tp_iternext */
-	0,					/* tp_methods */
+	obj_methods,				/* tp_methods */
 	0,					/* tp_members */
 	obj_getset,				/* tp_getset */
 	0,					/* tp_base */

pyobjc/Modules/objc/unicode-object.m

 	return Py_None;
 }
 
+/*
+ * Make sure objc_unicode objects don't get pickled.
+ *
+ * See meth_reduce in objc-object.m for details.
+ */
+static PyObject*
+meth_reduce(PyObject* self)
+{
+	PyObject* retVal;
+	PyObject *v, *v2;
+
+	retVal = PyTuple_New(2);
+	if (retVal == NULL) {
+		return NULL;
+	}
+
+	v = (PyObject*)&PyUnicode_Type;
+	Py_INCREF(v);
+	PyTuple_SET_ITEM(retVal, 0, v);
+
+	v = PyUnicode_FromObject(self);
+	if (v == NULL ) {
+		Py_DECREF(retVal);
+		return NULL;
+	}
+
+	v2 = PyTuple_New(1);
+	if (v2 == NULL) {
+		Py_DECREF(v);
+		Py_DECREF(retVal);
+		return NULL;
+	}
+	PyTuple_SET_ITEM(v2, 0, v);
+	PyTuple_SET_ITEM(retVal, 1, v2);
+
+	return retVal;
+}
+
 static PyMethodDef class_methods[] = {
 	{
 	  "nsstring",
 	  METH_NOARGS,
 	  "Copy contents of the NSString to the unicode object"
 	},
+	{
+	  "__reduce__",
+	  (PyCFunction)meth_reduce,
+	  METH_NOARGS,
+	  "Used for pickling"
+	},
         { 0, 0, 0, 0 } /* sentinel */
 };
 
 
 WHAT'S NEW:
 
+Version 1.0b1+ (CVS):
+- objc.pyobjc_unicode objects are now pickled as unicode objects, previously
+  the couldn't be pickled or were pickled as incomplete objects (protocol 
+  version 2). 
+
+- Pickling of ObjC objects never worked, we now explicitly throw an exception
+  if you try to pickle one: pickle protocol version 2 silently wrote the 
+  incomplete state of objects to the pickle.
+
 Version 1.0b1 (2003-07-05):
 
 - More tutorials
 """
 
 if sys.version >= '2.2.3':
+    # Add additional setup arguments, used to register with PyPI, Python 2.2.2
+    # and earlier don't support these (nor the 'register' subcommand of 
+    # setup.py)
     SetupExtraArguments = {
         'classifiers': [
             'Development Status :: 5 - Production/Stable',
             'Environment :: Console',
             'Environment :: MacOS X :: Cocoa',
             'Intended Audience :: Developers',
-            'License :: OSI Approved :: MIT License',
+            'License :: OSI Approvied:: MIT License',
             'Natural Language :: English',
             'Operating System :: MacOS :: MacOS X',
             'Programming Language :: Python',
             'Topic :: Software Development :: Libraries :: Python Modules',
             'Topic :: Software Development :: User Interfaces',
         ],
+        'license': 'MIT License',
         'download_url': 'http://pyobjc.sourceforge.net/software/index.php',
     }
 else:
 # makes development slightly more convenient.
 if sys.version >= '2.3':
     FoundationDepends = {
-        'depends': glob.glob('Modules/Foundation/_Fnd_*.inc'),
+        'depends': glob.glob('build/codegen/_Fnd_*.inc'),
     }
     AppKitDepends = {
-        'depends': glob.glob('Modules/AppKit/_App_*.inc'),
+        'depends': glob.glob('build/codegen/_App_*.inc'),
     }
     AppKitMappingDepends = {
         'depends': glob.glob('Modules/AppKit/_AppKitMapping_*.m'),
         'depends': glob.glob('Modules/Foundation/_FoundationMapping_*.m'),
     }
     AddressBookDepends = {
-        'depends': glob.glob('Modules/AddressBook/*.inc'),
+        'depends': glob.glob('build/codegen/*.inc'),
     }
     PrefPanesDepends = {
-        'depends': glob.glob('Modules/PreferencePanes/*.inc'),
+        'depends': glob.glob('build/codegen/*.inc'),
     }
     InterfaceBuilderDepends = {
-        'depends': glob.glob('Modules/InterfaceBuilder/*.inc'),
+        'depends': glob.glob('build/codegen/*.inc'),
     }
     WebKitDepends = {
-        'depends': glob.glob('Modules/WebKit/*.inc'),
+        'depends': glob.glob('build/codegen/*.inc'),
     }
 else:
     FoundationDepends = {}