Commits

Ronald Oussoren committed b1d16e1

Adds OC_PythonSet, a subclass of NSMutableSet that is used to wrap
set() and frozenset(). Includes full unittests.

Also fixes a number of bugs in OC_PythonEnumerator.

Comments (0)

Files changed (10)

pyobjc-core/Lib/objc/test/test_set_proxy.py

+"""
+Tests for the proxy of Python sets
+"""
+import sys
+import objc.test
+from objc.test.fnd import NSSet, NSMutableSet, NSPredicate, NSObject, NSNull
+from objc.test.pythonset import OC_TestSet
+import objc
+
+OC_PythonSet = objc.lookUpClass("OC_PythonSet")
+
+class OC_SetPredicate (NSPredicate):
+    # A simple test predicate class
+    def initWithFunction_(self, pred):
+        self = super(OC_SetPredicate, self).init()
+        if self is None:
+            return None
+
+        self.pred = pred
+        return self
+
+    def evaluateWithObject_(self, object):
+        return self.pred(object)
+
+class OC_TestElem(NSObject):
+
+    def __new__(self, k):
+        return self.alloc().initWithK_(k)
+
+    def initWithK_(self, k):
+        super(OC_TestElem, self).init()
+        self.k = k
+        return self
+
+    def __eq__(self, other):
+        return self.k == other.k
+
+    def __hash__(self):
+        return hash(self.k)
+
+
+class BasicSetTests:
+    # Tests for sets that don't try to mutate the set.
+    # Shared between tests for set() and frozenset()
+    setClass = None
+
+    def testProxyClass(self):
+        # Ensure that the right class is used to proxy sets
+        self.assert_(OC_TestSet.classOf_(self.setClass()) is OC_PythonSet)
+
+    def testMutableCopy(self):
+
+        s = self.setClass(range(20))
+        o = OC_TestSet.set_mutableCopyWithZone_(s, None)
+        self.assertEquals(s, o)
+        self.assert_(s is not o)
+        self.assert_(isinstance(o, set))
+
+        s = self.setClass()
+        o = OC_TestSet.set_mutableCopyWithZone_(s, None)
+        self.assertEquals(s, o)
+        self.assert_(s is not o)
+        self.assert_(isinstance(o, set))
+
+
+    def testAllObject(self):
+        s = self.setClass()
+        self.assertEquals(OC_TestSet.allObjectsOfSet_(s), [])
+
+        s = self.setClass([1,2,3])
+        o = OC_TestSet.allObjectsOfSet_(s)
+        o.sort()
+        self.assertEquals(o, [1,2,3])
+
+    def testCount(self):
+        s = self.setClass()
+        self.assertEquals(OC_TestSet.countOfSet_(s), 0)
+
+        s = self.setClass([1,2,3])
+        self.assertEquals(OC_TestSet.countOfSet_(s), 3)
+
+    def testAnyObject(self):
+        s = self.setClass()
+        self.assertEquals(OC_TestSet.anyObjectOfSet_(s), None)
+
+        s = self.setClass([1,2,3,4])
+        self.assert_(OC_TestSet.anyObjectOfSet_(s) in s)
+
+    def testContainsObject_(self):
+        s = self.setClass([1,2,3])
+
+        self.assert_(not OC_TestSet.set_containsObject_(s, 4))
+        self.assert_(OC_TestSet.set_containsObject_(s, 2))
+
+    def testFilteredSetUsingPredicate(self):
+        s = self.setClass(range(10))
+        p = OC_SetPredicate.alloc().initWithFunction_(lambda x: x % 2 == 0)
+
+        o = OC_TestSet.set_filteredSetUsingPredicate_(s, p)
+        self.assertEquals(o, self.setClass([0, 2, 4, 6, 8]))
+        self.assertEquals(len(s), 10)
+
+    def testMakeObjectsPerform(self):
+        o1 = OC_TestElem(1)
+        o2 = OC_TestElem(2)
+        o3 = OC_TestElem(3)
+        s = self.setClass([o1, o2, o3])
+       
+        o = OC_TestSet.set_member_(s, OC_TestElem(4))
+        self.assert_(o is None)
+
+        o = OC_TestSet.set_member_(s, OC_TestElem(2))
+        self.assert_(o is o2)
+
+    def testObjectEnumerator(self):
+        s = self.setClass(range(10))
+
+        enum = OC_TestSet.objectEnumeratorOfSet_(s)
+        l = []
+        v = enum.nextObject()
+        while v is not None:
+            l.append(v)
+            v = enum.nextObject()
+        self.assertEquals(l, list(range(10)))
+
+        s = self.setClass([1, 2, None, 3])
+        enum = OC_TestSet.objectEnumeratorOfSet_(s)
+        l = []
+        v = enum.nextObject()
+        while v is not None:
+            l.append(v)
+            v = enum.nextObject()
+
+
+        self.assertEquals(dict.fromkeys(l), dict.fromkeys([1,2,NSNull.null(),3]))
+
+    def testIsSubSet(self):
+        s1 = self.setClass(range(10))
+        s2 = self.setClass(range(5))
+
+        self.assert_(OC_TestSet.set_isSubsetOfSet_(s2, s1))
+        self.assert_(OC_TestSet.set_isSubsetOfSet_(s2, s2))
+        self.assert_(not OC_TestSet.set_isSubsetOfSet_(s1, s2))
+
+    def testIntersects(self):
+        s1 = self.setClass([1,2,3,4])
+        s2 = self.setClass([3,4,5,6])
+        s3 = self.setClass([5,6,7,8])
+
+        self.assert_(OC_TestSet.set_intersectsSet_(s1, s2))
+        self.assert_(OC_TestSet.set_intersectsSet_(s2, s3))
+        self.assert_(not OC_TestSet.set_intersectsSet_(s1, s3))
+
+    def testDescription(self):
+        s = self.setClass([OC_TestElem(1), 2])
+        o = OC_TestSet.descriptionOfSet_(s)
+        self.assert_(isinstance(o, unicode))
+
+
+class TestImmutableSet (objc.test.TestCase, BasicSetTests):
+    setClass = frozenset
+
+    def testCopy(self):
+        s = self.setClass()
+        o = OC_TestSet.set_copyWithZone_(s, None)
+        self.assertEquals(s, o)
+
+        s = self.setClass(range(20))
+        o = OC_TestSet.set_copyWithZone_(s, None)
+        self.assertEquals(s, o)
+
+    def testNotMutable(self):
+        # Ensure that a frozenset cannot be mutated
+        o = self.setClass([1,2,3])
+        self.assertRaises((TypeError, AttributeError),
+                OC_TestSet.set_addObject_, o, 4)
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_removeObject_, o, 2)
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_addObjectsFromArray_, o, [4, 5, 6])
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_filterUsingPredicate_, o, 
+                NSPredicate.predicateWithValue_(True))
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_intersectSet_, o, self.setClass([2,3,4]))
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_minusSet_, o, self.setClass([2,3,4]))
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_setSet_, o, self.setClass([2,3,4]))
+
+        self.assertRaises(TypeError,
+                OC_TestSet.set_minusSet_, o, self.setClass([2,3,4]))
+
+        self.assertRaises(TypeError,
+                OC_TestSet.removeAllObjecsFromSet_, o)
+
+
+class TestMutableSet (objc.test.TestCase, BasicSetTests):
+    setClass = set
+
+    def testCopy(self):
+        s = self.setClass()
+        o = OC_TestSet.set_copyWithZone_(s, None)
+        self.assertEquals(s, o)
+        self.assert_(s is not o)
+
+        s = self.setClass(range(20))
+        o = OC_TestSet.set_copyWithZone_(s, None)
+        self.assertEquals(s, o)
+        self.assert_(s is not o)
+
+    def testUnionSet(self):
+        s1 = self.setClass([1,2,3])
+        s2 = self.setClass([3,4,5])
+
+        OC_TestSet.set_unionSet_(s1, s2)
+        self.assertEquals(s1, self.setClass([1,2,3,4,5]))
+
+    def testSetSet(self):
+        s1 = self.setClass([1,2,3])
+        s2 = self.setClass([3,4,5])
+
+        OC_TestSet.set_setSet_(s1, s2)
+        self.assertEquals(s1, self.setClass([3,4,5]))
+
+    def testMinusSet(self):
+        s1 = self.setClass([1,2,3])
+        s2 = self.setClass([3,4,5])
+
+        OC_TestSet.set_minusSet_(s1, s2)
+        self.assertEquals(s1, self.setClass([1, 2]))
+
+    def testIntersectSet(self):
+        s1 = self.setClass([1,2,3])
+        s2 = self.setClass([3,4,5])
+
+        OC_TestSet.set_intersectSet_(s1, s2)
+        self.assertEquals(s1, self.setClass([3]))
+
+    def testFilterSet(self):
+        s = self.setClass(range(10))
+        p = OC_SetPredicate.alloc().initWithFunction_(lambda x: x % 2 == 0)
+
+        OC_TestSet.set_filterUsingPredicate_(s, p)
+        self.assertEquals(s, self.setClass([0, 2, 4, 6, 8]))
+
+    def testAddObject(self):
+        s = self.setClass([1,2,3])
+
+        OC_TestSet.set_addObject_(s, 1)
+        self.assertEquals(s, self.setClass([1,2,3]))
+
+        OC_TestSet.set_addObject_(s, 9)
+        self.assertEquals(s, self.setClass([1,2,3,9]))
+
+    def testAddObjectsFromArray(self):
+        s = self.setClass([1,2,3])
+
+        OC_TestSet.set_addObjectsFromArray_(s, [1,2])
+        self.assertEquals(s, self.setClass([1,2,3]))
+
+        OC_TestSet.set_addObjectsFromArray_(s, [9,5,4])
+        self.assertEquals(s, self.setClass([1,2,3,9,5,4]))
+
+    def testRemoveObject(self):
+        s = self.setClass([1,2,3])
+
+        OC_TestSet.set_removeObject_(s, 1)
+        self.assertEquals(s, self.setClass([2,3]))
+
+        OC_TestSet.set_removeObject_(s, 9)
+        self.assertEquals(s, self.setClass([2,3]))
+
+    def testRemoveAllObjects(self):
+        s = self.setClass([1,2,3])
+
+        OC_TestSet.removeAllObjecsFromSet_(s)
+        self.assertEquals(s, self.setClass())
+
+
+
+if __name__ == "__main__":
+    objc.test.main()

pyobjc-core/Modules/objc/OC_PythonEnumerator.m

 
 -nextObject
 {
-	if (!valid) return nil;
+	if (!valid) {
+		return nil;
+	}
 
 	NSObject* result = nil;
 
 	PyObjC_BEGIN_WITH_GIL
 		PyObject* object = PyIter_Next(value);
 		if (object == NULL) {
-			if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
+			if (!PyErr_Occurred()) {
 				valid = NO;
 				PyErr_Clear();
 				PyObjC_GIL_RETURN(nil);
+			} else {
+				PyObjC_GIL_FORWARD_EXC();
 			}
 
-			PyObjC_GIL_FORWARD_EXC();
-
 		}
 		result = PyObjC_PythonToId(object);
 		if (result == nil) {
-			PyObjC_GIL_FORWARD_EXC();
+			if (PyErr_Occurred()) {
+				PyObjC_GIL_FORWARD_EXC();
+			} else {
+				PyObjC_GIL_RETURN([NSNull null]);
+			}
 		}
 
 	PyObjC_END_WITH_GIL

pyobjc-core/Modules/objc/OC_PythonNumber.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;

pyobjc-core/Modules/objc/OC_PythonObject.h

 
 extern PyObject* PyObjC_Encoder;
 extern PyObject* PyObjC_Decoder;
+extern PyObject* PyObjC_CopyFunc;
 
 extern void PyObjC_encodeWithCoder(PyObject* pyObject, NSCoder* coder);
 

pyobjc-core/Modules/objc/OC_PythonObject.m

 		} else {
 			r = -1;
 		}
+
+	} else if (PyAnySet_Check(argument)) {
+		rval = [OC_PythonSet newWithPythonObject:argument];
+		if (rval) {
+			PyObjC_RegisterObjCProxy(argument, rval);
+			r = 0;
+		} else {
+			r = -1;
+		}
+
 	} else if ((rval = PyObjC_CFTypeToID(argument))) {
 		// unwrapped cf
 		r = 0;
 			PyObjC_GIL_FORWARD_EXC();
 		}
 		
+		/* Check if the object is "set-like" */
+		instance = [OC_PythonSet depythonifyObject:obj];
+		if (instance != nil) {
+			PyObjC_RegisterObjCProxy(obj, instance);
+			PyObjC_GIL_RETURN(instance);
+		} 
+		if (PyErr_Occurred()) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
 		/* Check if the object is "datetime-like" */
 		instance = [OC_PythonDate depythonifyObject:obj];
 		if (instance != nil) {

pyobjc-core/Modules/objc/OC_PythonSet.h

+#include "pyobjc.h"
+
+@interface OC_PythonSet : NSMutableSet
+{
+	PyObject* value;
+}
+
++ depythonifyObject:(PyObject*)object;
++ newWithPythonObject:(PyObject*)value;
+- initWithPythonObject:(PyObject*)value;
+-(void)dealloc;
+-(PyObject*)__pyobjc_PythonObject__;
+
+@end

pyobjc-core/Modules/objc/OC_PythonSet.m

+#include "pyobjc.h"
+
+
+static PyObject* mapTypes = NULL;
+
+
+@implementation OC_PythonSet
+
++ depythonifyObject:(PyObject*)object
+{
+	Py_ssize_t i, len;
+	
+	if (mapTypes == NULL) return NULL;
+
+	len = PyList_GET_SIZE(mapTypes);
+
+	for (i = 0; i < len; i++) {
+		PyObject* tp = PyList_GET_ITEM(mapTypes, i);
+		int r = PyObject_IsInstance(object, tp);
+		if (r == -1) {
+			return NULL;
+		}
+
+		if (!r) continue;
+
+		/* Instance of this type should be pythonifyed as a sequence */
+		return [OC_PythonSet newWithPythonObject:object];
+	}
+
+	return NULL;
+}
+
++ depythonifyTable
+{
+	NSObject* result; 
+
+	PyObjC_BEGIN_WITH_GIL
+
+		if (mapTypes == NULL) {
+			mapTypes = PyList_New(0);
+			if (mapTypes == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+		}
+		result = PyObjC_PythonToId(mapTypes);
+		if (result == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+	PyObjC_END_WITH_GIL
+
+	return result;
+}
+
++ newWithPythonObject:(PyObject*)v;
+{
+	OC_PythonSet* res;
+
+	res = [[OC_PythonSet 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];
+}
+
+
+/* NSCoding support */
+
+- (void)encodeWithCoder:(NSCoder*)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
+{
+	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_PythonSet*)proxy;
+			}
+
+
+		PyObjC_END_WITH_GIL
+
+		return self;
+
+	} else {
+		[NSException raise:NSInvalidArgumentException
+				format:@"decoding Python objects is not supported"];
+		return nil;
+
+	}
+}
+
+
+/*
+ * Implementation of the NSMutableSet interface
+ */
+-(id)copyWithZone:(NSZone*)zone
+{
+	(void)zone;
+	if (PyObjC_CopyFunc != NULL) {
+		PyObjC_BEGIN_WITH_GIL
+			PyObject* tmp = PyObject_CallFunction(PyObjC_CopyFunc, "O", value);
+			if (tmp == NULL) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+			NSObject* result = PyObjC_PythonToId(tmp);
+			Py_DECREF(tmp);
+			if (PyErr_Occurred()) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+			[result retain];
+			PyObjC_GIL_RETURN(result);
+
+		PyObjC_END_WITH_GIL
+
+	} else {
+		[NSException raise:NSInvalidArgumentException 
+		            format:@"cannot copy python set"];
+		return nil;
+	}
+}
+
+-(id)mutableCopyWithZone:(NSZone*)zone
+{
+	(void)zone;
+	PyObjC_BEGIN_WITH_GIL
+
+		PyObject* tmp = PySet_New(value);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		NSObject* result = PyObjC_PythonToId(tmp);
+		Py_DECREF(tmp);
+		if (PyErr_Occurred()) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+		
+		[result retain];
+		PyObjC_GIL_RETURN(result);
+
+	PyObjC_END_WITH_GIL
+}
+
+-(NSArray*)allObjects
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmp = PySequence_List(value);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		NSArray* result = (NSArray*)PyObjC_PythonToId(tmp);
+		Py_DECREF(tmp);
+		if (PyErr_Occurred()) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		PyObjC_GIL_RETURN(result);
+
+	PyObjC_END_WITH_GIL
+}
+
+-(NSObject*)anyObject
+{
+	PyObjC_BEGIN_WITH_GIL
+		if (PySet_Size(value) == 0) {
+			PyObjC_GIL_RETURN(nil);
+		}
+
+		PyObject* tmp = PyObject_GetIter(value);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		PyObject* v = PyIter_Next(tmp);
+		Py_DECREF(tmp);
+		if (v == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+		
+		NSObject* result = PyObjC_PythonToId(v);
+		Py_DECREF(v);
+		if (PyErr_Occurred()) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+		
+		PyObjC_GIL_RETURN(result);
+
+	PyObjC_END_WITH_GIL
+}
+
+-(BOOL)containsObject:(id)anObject
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmp = PyObjC_IdToPython(anObject);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		int r = PySequence_Contains(value, tmp);
+		Py_DECREF(tmp);
+		if (r == -1) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+
+		if (r) {
+			PyObjC_GIL_RETURN(YES);
+		} else {
+			PyObjC_GIL_RETURN(NO);
+		}
+	PyObjC_END_WITH_GIL
+}
+
+-(NSUInteger)count
+{
+	PyObjC_BEGIN_WITH_GIL
+		Py_ssize_t result = PySequence_Size(value);
+		if (result == -1) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		PyObjC_GIL_RETURN((NSUInteger)result);
+	PyObjC_END_WITH_GIL
+}
+
+-(NSEnumerator*)objectEnumerator
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmp = PyObject_GetIter(value);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		NSEnumerator* result = [OC_PythonEnumerator newWithPythonObject:tmp];
+		Py_DECREF(tmp);
+
+		PyObjC_GIL_RETURN(result);
+
+	PyObjC_END_WITH_GIL
+}
+
+/* It seems impossible to create an efficient implementation of this method,
+ * iteration is basicly the only way to fetch the requested object
+ *
+ * XXX: this means we should implement more of NS(Mutable)Set interface,
+ * that's a lot more efficient than iterating over and over again.
+ *
+ */
+-(id)member:(id)anObject
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmpMember = PyObjC_IdToPython(anObject);
+		if (tmpMember == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		int r = PySequence_Contains(value, tmpMember);
+		if (r == -1) {
+			Py_DECREF(tmpMember);
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		if (!r) {
+			Py_DECREF(tmpMember);
+			PyObjC_GIL_RETURN(nil);
+		}
+
+
+		/* This sucks, we have to iterate over the contents of the
+		 * set to find the object we need...
+		 */
+		PyObject* tmp = PyObject_GetIter(value);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		PyObject* v;
+		
+		while ((v = PyIter_Next(tmp)) != NULL) {
+			r = PyObject_RichCompareBool(v, tmpMember, Py_EQ);
+			if (r == -1) {
+				Py_DECREF(tmp);
+				Py_DECREF(tmpMember);
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+			if (r) {
+				/* Found the object */
+				Py_DECREF(tmp);
+				Py_DECREF(tmpMember);
+
+				NSObject* result = PyObjC_PythonToId(v);
+				if (PyErr_Occurred()) {
+					PyObjC_GIL_FORWARD_EXC();
+				}
+				PyObjC_GIL_RETURN(result);
+			}
+		}
+
+		Py_DECREF(tmp);
+		Py_DECREF(tmpMember);
+		PyObjC_GIL_RETURN(nil);
+
+
+	PyObjC_END_WITH_GIL
+}
+
+-(void)removeAllObjects
+{
+	PyObjC_BEGIN_WITH_GIL
+		if (PyFrozenSet_CheckExact(value)) {
+			PyErr_SetString(PyExc_TypeError,
+				"Cannot mutate a frozenstring");
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		if (PyAnySet_Check(value)) {
+			int r = PySet_Clear(value);\
+			if (r == -1) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+		} else {
+			/* Assume an object that conforms to 
+			 * the set interface 
+			 */
+			 PyObject* r;
+			 
+			 r = PyObject_CallMethod(value, "clear", NULL);
+			 if (r == NULL) {
+				 PyObjC_GIL_FORWARD_EXC();
+			 }
+			 Py_DECREF(r);
+		}
+	PyObjC_END_WITH_GIL
+}
+
+-(void)removeObject:(id)anObject
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmp = PyObjC_IdToPython(anObject);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		} 
+
+		if (PyFrozenSet_CheckExact(value)) {
+			PyErr_SetString(PyExc_TypeError,
+				"Cannot mutate a frozenstring");
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		if (PyAnySet_Check(value)) {
+			int r = PySet_Discard(value, tmp);
+			Py_DECREF(tmp);
+			if (r == -1) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+		} else {
+			 PyObject* r;
+			 
+			 r = PyObject_CallMethod(value, "discard", "O", tmp);
+			 Py_DECREF(tmp);
+			 if (r == NULL) {
+				 PyObjC_GIL_FORWARD_EXC();
+			 }
+			 Py_DECREF(r);
+		}
+
+	PyObjC_END_WITH_GIL
+}
+
+-(void)addObject:(id)anObject
+{
+	PyObjC_BEGIN_WITH_GIL
+		PyObject* tmp = PyObjC_IdToPython(anObject);
+		if (tmp == NULL) {
+			PyObjC_GIL_FORWARD_EXC();
+		} 
+
+		if (PyFrozenSet_CheckExact(value)) {
+			PyErr_SetString(PyExc_TypeError,
+				"Cannot mutate a frozenstring");
+			PyObjC_GIL_FORWARD_EXC();
+		}
+
+		if (PyAnySet_Check(value)) {
+			int r = PySet_Add(value, tmp);
+			Py_DECREF(tmp);
+			if (r == -1) {
+				PyObjC_GIL_FORWARD_EXC();
+			}
+
+		} else {
+			 PyObject* r;
+			 
+			 r = PyObject_CallMethod(value, "add", "O", tmp);
+			 Py_DECREF(tmp);
+			 if (r == NULL) {
+				 PyObjC_GIL_FORWARD_EXC();
+			 }
+			 Py_DECREF(r);
+		}
+		
+	PyObjC_END_WITH_GIL
+}
+
+@end

pyobjc-core/Modules/objc/pyobjc.h

 #include "OC_PythonEnumerator.h"
 #include "OC_PythonDate.h"
 #include "OC_PythonNumber.h"
+#include "OC_PythonSet.h"
 #include "method-signature.h"
 #include "objc_util.h"
 #include "objc-class.h"

pyobjc-core/Modules/objc/test/pythonset.m

+#include <Python.h>
+#include "pyobjc-api.h"
+
+#import <Foundation/Foundation.h>
+
+@interface OC_TestSet : NSObject {}
+@end
+
+@implementation OC_TestSet
+
++(Class)classOf:(NSObject*)value
+{
+	return [value class];
+}
+
+/* copying */
++(id)set:(NSSet*)set copyWithZone:(NSZone*)zone
+{
+	return [set copyWithZone:zone];
+}
+
++(id)set:(NSSet*)set mutableCopyWithZone:(NSZone*)zone
+{
+	return [set mutableCopyWithZone:zone];
+}
+
+
+/* Base set */
+
++(NSArray*)allObjectsOfSet:(NSSet*)set
+{
+	return [set allObjects];
+}
+
++(id)anyObjectOfSet:(NSSet*)set
+{
+	return [set anyObject];
+}
+
++(BOOL)set:(NSSet*)set containsObject:(id)anObject
+{
+	return [set containsObject:anObject];
+}
+
++(NSUInteger)countOfSet:(NSSet*)set
+{
+	return [set count];
+}
+
++(NSString*)descriptionOfSet:(NSSet*)set
+{
+	return [set description];
+}
+
++(NSString*)set:(NSSet*)set descriptionWithLocale:(id)locale
+{
+	return [set descriptionWithLocale:locale];
+}
+
++(NSSet*)set:(NSSet*)set filteredSetUsingPredicate:(NSPredicate*)predicate
+{
+	return [set filteredSetUsingPredicate:predicate];
+}
+
++(BOOL)set:(NSMutableSet*)set intersectsSet:(NSSet *)otherSet
+{
+	return [set intersectsSet:otherSet];
+}
+
++(BOOL)set:(NSSet*)set isEqualToSet:(NSSet*)otherSet
+{
+	return [set isEqualToSet:otherSet];
+}
+
++(BOOL)set:(NSSet*)set isSubsetOfSet:(NSSet*)otherSet
+{
+	return [set isSubsetOfSet:otherSet];
+}
+
++(void)set:(NSSet*)set makeObjectsPerformSelector:(SEL)aSelector
+{
+	return [set makeObjectsPerformSelector:aSelector];
+}
+
++(void)set:(NSSet*)set makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject
+{
+	return [set makeObjectsPerformSelector:aSelector withObject:anObject];
+}
+
++(id)set:(NSSet*)set member:(id)anObject
+{
+	return [set member:anObject];
+}
+
++(NSEnumerator*)objectEnumeratorOfSet:(NSSet*)set
+{
+	return [set objectEnumerator];
+}
+
++(NSSet*)set:(NSSet*)set setByAddingObject:(id)anObject
+{
+	return [set setByAddingObject:anObject];
+}
+
++(NSSet*)set:(NSSet*)set setByAddingObjectsFromArray:(NSArray*)anObject
+{
+	return [set setByAddingObjectsFromArray:anObject];
+}
+
++(NSSet*)set:(NSSet*)set setByAddingObjectsFromSet:(NSSet*)anObject
+{
+	return [set setByAddingObjectsFromSet:anObject];
+}
+
++(void)set:(NSSet*)set setValue:(id)value forKey:(id)key
+{
+	[set setValue:value forKey:key];
+}
+
++(id)set:(NSSet*)set valueForKey:(id)key
+{
+	return [set valueForKey:key];
+}
+
+/* Mutable set */
+
++(void)set:(NSMutableSet*)set addObject:(id)anObject
+{
+	[set addObject:anObject];
+}
+
++(void)set:(NSMutableSet*)set addObjectsFromArray:(NSArray *)anArray
+{
+	[set addObjectsFromArray:anArray];
+}
+
++(void)set:(NSMutableSet*)set filterUsingPredicate:(NSPredicate *)predicate
+{
+	[set filterUsingPredicate:predicate];
+}
+
++(void)set:(NSMutableSet*)set intersectSet:(NSSet *)otherSet
+{
+	[set intersectSet:otherSet];
+}
+
++(void)set:(NSMutableSet*)set minusSet:(NSSet *)otherSet
+{
+	[set minusSet:otherSet];
+}
+
++(void)removeAllObjecsFromSet:(NSMutableSet*)set
+{
+	[set removeAllObjects];
+}
+
++(void)set:(NSMutableSet*)set removeObject:(id)anObject
+{
+	[set removeObject:anObject];
+}
+
++(void)set:(NSMutableSet*)set setSet:(NSSet *)otherSet
+{
+	[set setSet:otherSet];
+}
+
++(void)set:(NSMutableSet*)set unionSet:(NSSet *)otherSet
+{
+	[set unionSet:otherSet];
+}
+
+
+
+@end
+
+
+static PyMethodDef NULL_methods[] = {
+	        { 0, 0, 0, 0 }
+};
+
+void initpythonset(void);
+void initpythonset(void)
+{
+	PyObject* m;
+
+	m = Py_InitModule4("pythonset", NULL_methods,
+		NULL, NULL, PYTHON_API_VERSION);
+
+	if (PyObjC_ImportAPI(m) < 0) return;
+
+	PyModule_AddObject(m, "OC_TestSet",
+	    PyObjCClass_New([OC_TestSet class]));
+}
   instances before using them from Objective-C (such as using an 
   ``NSDateFormatter``)
 
-- There is experimental support for archiving Python objects 
   using an ``NSKeyedArchiver``. 
 
 - Objective-C classes that support the ``NSCopying`` protocol can now be
     This is used instead of ``NSNumber`` because we might loose information
     otherwise (such as when using custom subclasses of ``int``).
 
-  * ``OC_PythonSet``: wraps a python set
+  * ``OC_PythonSet``: wraps a python set and is a subclass of ``NSMutableSet``
+
+- BUGFIX: ``OC_PythonEnumerator`` now actually works.
 
 - BUGFIX: using the ``@throw`` syntax one can raise arbitrary objects as
   exceptions (not just instances of NSException) in Objective-C. All