[Checkins] SVN: z3c.relationfield/trunk/src/z3c/relationfield/ Prevent the event failures from failing when utilities are missing or when objects do not implement IContained.

Alec Mitchell apm13 at columbia.edu
Sat Apr 11 23:20:35 EDT 2009


Log message for revision 99135:
  Prevent the event failures from failing when utilities are missing or when objects do not implement IContained. 
  

Changed:
  U   z3c.relationfield/trunk/src/z3c/relationfield/event.py
  U   z3c.relationfield/trunk/src/z3c/relationfield/testing.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/tests.py

-=-
Modified: z3c.relationfield/trunk/src/z3c/relationfield/event.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/event.py	2009-04-11 17:52:25 UTC (rev 99134)
+++ z3c.relationfield/trunk/src/z3c/relationfield/event.py	2009-04-12 03:20:35 UTC (rev 99135)
@@ -33,30 +33,38 @@
 
     Any relation object on the object will be removed from the catalog.
     """
-    catalog = component.getUtility(ICatalog)
- 
+    catalog = component.queryUtility(ICatalog)
+    if catalog is None:
+        return
+
     for name, relation in _relations(obj):
         if relation is not None:
-            catalog.unindex(relation)
+            try:
+                catalog.unindex(relation)
+            except KeyError:
+                # The relation value has already been unindexed.
+                pass
 
 @grok.subscribe(IHasOutgoingRelations, IObjectModifiedEvent)
 def updateRelations(obj, event):
     """Re-register relations, after they have been changed.
     """
-    # if we do not yet have a parent, we ignore this modified event
-    # completely. The object will need to be added somewhere first
-    # before modification events will have an effect
-    if obj.__parent__ is None:
+    catalog = component.getUtility(ICatalog)
+    intids = component.getUtility(IIntIds)
+
+    # check that the object has an intid, otherwise there's nothing to be done
+    try:
+        obj_id = intids.getId(obj)
+    except KeyError:
+        # The object has not been added to the ZODB yet
         return
 
     # remove previous relations coming from id (now have been overwritten)
-    catalog = component.getUtility(ICatalog)
-    intids = component.getUtility(IIntIds)
     # have to activate query here with list() before unindexing them so we don't
     # get errors involving buckets changing size
-    rels = list(catalog.findRelations({'from_id': intids.getId(obj)}))
+    rels = list(catalog.findRelations({'from_id': obj_id}))
     for rel in rels:
-        catalog.unindex(rel)    
+        catalog.unindex(rel)
 
     # add new relations
     addRelations(obj, event)
@@ -70,16 +78,23 @@
     obj = event.object
     if not IHasIncomingRelations.providedBy(obj):
         return
-    catalog = component.getUtility(ICatalog)
-    intids = component.getUtility(IIntIds)
+    catalog = component.queryUtility(ICatalog)
+    intids = component.queryUtility(IIntIds)
+    if catalog is None or intids is None:
+        return
 
     # find all relations that point to us
+    try:
+        obj_id = intids.getId(obj)
+    except KeyError:
+        # our intid was unregistered already
+        return
     rels = list(catalog.findRelations({'to_id': intids.getId(obj)}))
     for rel in rels:
         rel.broken(rel.to_path)
         # we also need to update the relations for these objects
         notify(ObjectModifiedEvent(rel.from_object))
-        
+
 def realize_relations(obj):
     """Given an object, convert any temporary relations on it to real ones.
     """

Modified: z3c.relationfield/trunk/src/z3c/relationfield/testing.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/testing.py	2009-04-11 17:52:25 UTC (rev 99134)
+++ z3c.relationfield/trunk/src/z3c/relationfield/testing.py	2009-04-12 03:20:35 UTC (rev 99135)
@@ -1,7 +1,46 @@
 import os
 import z3c.relationfield
+from zc.relation.interfaces import ICatalog
+from zope.interface import implements
+from zope.component import provideUtility, getGlobalSiteManager
 from zope.app.testing.functional import ZCMLLayer
+from zope.app.intid.interfaces import IIntIds
 
 ftesting_zcml = os.path.join(
     os.path.dirname(z3c.relationfield.__file__), 'ftesting.zcml')
 FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer')
+
+class MockIntIds(object):
+    """Dumb utility for unit tests, returns sequential integers. Not a
+    complete implementation."""
+    implements(IIntIds)
+
+    def getId(self, ob):
+        """Appropriately raises KeyErrors when the object is not registered,
+        e.g. always"""
+        raise KeyError(ob)
+mock_intids = MockIntIds()
+
+
+class MockCatalog(object):
+    """Does nothing except exist"""
+    implements(ICatalog)
+
+    def findRelations(self, query):
+        return []
+mock_catalog = MockCatalog()
+
+def register_fake_intid():
+    provideUtility(mock_intids)
+def unregister_fake_intid():
+    sm = getGlobalSiteManager()
+    sm.unregisterUtility(mock_intids)
+
+def register_fake_catalog():
+    provideUtility(mock_catalog)
+def unregister_fake_catalog():
+    sm = getGlobalSiteManager()
+    sm.unregisterUtility(mock_catalog)
+
+class MockContent(object):
+    implements(z3c.relationfield.interfaces.IHasRelations)

Added: z3c.relationfield/trunk/src/z3c/relationfield/tests.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/tests.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/tests.py	2009-04-12 03:20:35 UTC (rev 99135)
@@ -0,0 +1,68 @@
+from unittest import makeSuite, TestCase, TestSuite
+from z3c.relationfield.testing import (
+    register_fake_intid,
+    register_fake_catalog,
+    unregister_fake_intid,
+    unregister_fake_catalog,
+    MockContent,
+    )
+from z3c.relationfield import event
+from zope.component.interfaces import ObjectEvent
+from zope.component.interfaces import ComponentLookupError
+
+class EventTests(TestCase):
+    """Unit tests for the relation field event handlers.  The event handlers
+    should be tolerant of missing utilities and objects not yet registered with
+    the intid utility."""
+
+    def setUp(self):
+        self.content = MockContent()
+        register_fake_catalog()
+        register_fake_intid()
+
+    def tearDown(self):
+        unregister_fake_catalog()
+        unregister_fake_intid()
+
+    def test_missing_intids(self):
+        """Event handlers which trigger on object removal should not
+        throw exceptions when the utilities are missing.  The utilities may
+        have been deleted in the same transaction (e.g. site deletion)."""
+        # Remove intid utility and ensure the event handler doesn't fail
+        unregister_fake_intid()
+        try:
+            event.breakRelations(ObjectEvent(self.content))
+        except ComponentLookupError:
+            self.fail("breakRelations fails when intid utility is missing")
+
+    def test_break_relations_missing_catalog(self):
+        # Remove relations catalog and ensure the event handler doesn't fail
+        unregister_fake_catalog()
+        try:
+            event.breakRelations(ObjectEvent(self.content))
+        except ComponentLookupError:
+            self.fail("breakRelations fails when catalog utility is missing")
+
+    def test_remove_relations_missing_catalog(self):
+        # Remove relations catalog and ensure the event handler doesn't fail
+        unregister_fake_catalog()
+        try:
+            event.removeRelations(self.content, None)
+        except ComponentLookupError:
+            self.fail("removeRelations fails when catalog utility is missing")
+
+    def test_initid_failure(self):
+        """When an object has not yet been registered with the intid utility,
+        getId fails with a KeyError.  The event handlers need to handle this
+        cleanly."""
+        # Our content is not yet registered with the intid utility
+        # and has no __parent__ attribute
+        try:
+            event.updateRelations(self.content, None)
+        except (KeyError, AttributeError):
+            self.fail("updateRelations fails with unregistered object")
+
+def test_suite():
+    suite=TestSuite()
+    suite.addTest(makeSuite(EventTests))
+    return suite



More information about the Checkins mailing list