Commits

Ronald Oussoren committed f1e55dd

Add an new way for specifying protocol conformance in Python 3.x

It is now possible to write a class like this:

class MyObject (NSObject, protocols=[SomeProtocol]):
pass

The old way of using protocols as mix-in classes still works::

class MyObject (NSObject, SomeProtocol):
pass

The new syntax only works for Python 3.x (it is a syntax error
in Python 2.x).

The new was added because the older method does not work with the current
python 3.3 repository, and because it better documents what the
code intends to accomplish.

Comments (0)

Files changed (4)

pyobjc-core/Doc/protocols.rst

 XXX: is this necessary? we could also use the same strategy as for informal
 protocols, and drop the informal_protocol wrappers for formal protocols.
 
-Declaring conformance to a formal protocol is done by using the formal protocol
-as a mix-in, and by implementing its methods:
+In python 2.x declaring conformance to a formal protocol is done by using 
+the formal protocol as a mix-in, and by implementing its methods:
 
  .. sourcecode:: python
     :linenos:
 		def unlock(self):
 			pass
 
+In python 3.x  you don't use the protocols as mix-ins, but specify them as
+a keyword argument:
+
+ .. sourcecode:: python
+    :linenos:
+
+	NSLocking = objc.protocolNamed('NSLocking')
+
+	class MyLockingObject(NSObject, protocols=[NSLocking]):
+		def lock(self):
+			pass
+
+		def unlock(self):
+			pass
+
 XXX: Change this example when the pyobjc_classMethods hack is no longer necessary.
 
 The class now formally implements the ``NSLocking`` protocol, this can be

pyobjc-core/Modules/objc/objc-class.m

  * Note: This function creates new _classes_
  */
 
+static int
+class_init(PyObject *cls, PyObject *args, PyObject *kwds)
+{
+	if (kwds != NULL) {
+		if (PyDict_Check(kwds) && PyDict_Size(kwds) == 1) {
+			if (PyDict_GetItemString(kwds, "protocols") != NULL) {
+				return PyType_Type.tp_init(cls, args, NULL);
+			}
+		}
+	}
+	return PyType_Type.tp_init(cls, args, kwds);
+}
 
 static PyObject*
 class_new(PyTypeObject* type __attribute__((__unused__)), 
 		PyObject* args, PyObject* kwds)
 {
-static	char* keywords[] = { "name", "bases", "dict", NULL };
+static	char* keywords[] = { "name", "bases", "dict", "protocols", NULL };
 	char* name;
 	PyObject* bases;
 	PyObject* dict;
 	PyObject* protectedMethods = NULL;
 	PyObject* hiddenSelectors = NULL;
 	PyObject* hiddenClassSelectors = NULL;
+	PyObject* arg_protocols = NULL;
 	BOOL      isCFProxyClass = NO;
+	int       r;
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO:__new__",
-			keywords, &name, &bases, &dict)) {
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO|O",
+			keywords, &name, &bases, &dict, &arg_protocols)) {
 		return NULL;
 	}
 
 					"multiple objective-C bases");
 			return NULL;
 		} else if (PyObjCInformalProtocol_Check(v)) {
-			PyList_Append(protocols, v);
+			r = PyList_Append(protocols, v);
+			if (r == -1) {
+				Py_DECREF(protocols);
+				Py_DECREF(real_bases);
+				Py_DECREF(protectedMethods);
+				Py_DECREF(hiddenSelectors);
+				Py_DECREF(hiddenClassSelectors);
+			}
 		} else if (PyObjCFormalProtocol_Check(v)) {
-			PyList_Append(protocols, v);
+			r = PyList_Append(protocols, v);
+			if (r == -1) {
+				Py_DECREF(protocols);
+				Py_DECREF(real_bases);
+				Py_DECREF(protectedMethods);
+				Py_DECREF(hiddenSelectors);
+				Py_DECREF(hiddenClassSelectors);
+			}
 		} else {
-			PyList_Append(real_bases, v);
+			r = PyList_Append(real_bases, v);
+			if (r == -1) {
+				Py_DECREF(protocols);
+				Py_DECREF(real_bases);
+				Py_DECREF(protectedMethods);
+				Py_DECREF(hiddenSelectors);
+				Py_DECREF(hiddenClassSelectors);
+			}
 		}
 	}
 
+	if (arg_protocols != NULL) {
+		PyObject* seq;
+		Py_ssize_t i, seqlen;
+
+		seq = PySequence_Fast(protocols, 
+			"'protocols' not a sequence?");
+		if (seq == NULL) {
+			Py_DECREF(protocols);
+			Py_DECREF(real_bases);
+			Py_DECREF(protectedMethods);
+			Py_DECREF(hiddenSelectors);
+			Py_DECREF(hiddenClassSelectors);
+			return NULL;
+		}
+		seqlen = PySequence_Fast_GET_SIZE(seq);
+		for (i = 0; i < seqlen; i++) {
+			r = PyList_Append(protocols,
+				PySequence_Fast_GET_ITEM(seq, i));
+			if (r == -1) {
+				Py_DECREF(seq);
+				Py_DECREF(protocols);
+				Py_DECREF(real_bases);
+				Py_DECREF(protectedMethods);
+				Py_DECREF(hiddenSelectors);
+				Py_DECREF(hiddenClassSelectors);
+			}
+		}
+		Py_DECREF(seq);
+	}
+
 	metadict = PyDict_New();
 	if (metadict == NULL) {
 		Py_DECREF(protocols);
 	0,					/* tp_descr_get */
 	0,					/* tp_descr_set */
 	0,					/* tp_dictoffset */
-	0,					/* tp_init */
+	class_init,				/* tp_init */
 	0,					/* tp_alloc */
 	class_new,				/* tp_new */
 	0,		        		/* tp_free */

pyobjc-core/NEWS.txt

 Version 2.4  (or 3.0)
 ---------------------
 
+- In Python 3.x there is a new way to explicitly specify which (informal)
+  protocols a class conforms to::
+
+     class MyClass (NSObject, protocols=[Protocol1, Protocol2]):
+        pass
+
+  Python 2.x does not support this syntax, you can still use the
+  following code there::
+
+     class MyClass (NSObject, Protocol1, Protocol2):
+        pass
+
+  Note: The Python 2.x style works upto Python 3.2. In Python 3.3 and later
+  the Python 2.x style declaration no longer works due to changes in the
+  language.
+
 - Updated Python support. With this release PyObjC supports Python 2.6 and later,
   including Python 3.3 (which has a completely new representation for unicode strings)
 

pyobjc-core/PyObjCTest/test_protocol.py

         self.assertRaises(TypeError, self.doIncompleteClass)
 
 
+    @onlyIf(sys.version_info[:2] < (3,3), "not valid for python 3.3 and later")
     def testOptional(self):
         class ProtoClass3 (NSObject, MyProto):
             def testMethod(self):