[Zope-Checkins] SVN: Zope/trunk/ - LP #374810: ``__bobo_traverse__`` implementation can raise

Christian Zagrodnick cz at gocept.com
Mon Jun 21 10:47:19 EDT 2010


Log message for revision 113725:
  - LP #374810: ``__bobo_traverse__`` implementation can raise
  ``ZPublisher.interfaces.UseTraversalDefault`` to indicate that there is no
  special casing for the given name and that standard traversal logic should be
  applied.
  
  

Changed:
  U   Zope/trunk/doc/CHANGES.rst
  U   Zope/trunk/src/OFS/Traversable.py
  U   Zope/trunk/src/OFS/tests/testTraverse.py
  U   Zope/trunk/src/ZPublisher/BaseRequest.py
  U   Zope/trunk/src/ZPublisher/interfaces.py
  U   Zope/trunk/src/ZPublisher/tests/testBaseRequest.py

-=-
Modified: Zope/trunk/doc/CHANGES.rst
===================================================================
--- Zope/trunk/doc/CHANGES.rst	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/doc/CHANGES.rst	2010-06-21 14:47:19 UTC (rev 113725)
@@ -127,6 +127,11 @@
 Features Added
 ++++++++++++++
 
+- LP #374810: ``__bobo_traverse__`` implementation can raise
+  ``ZPublisher.interfaces.UseTraversalDefault`` to indicate that there is no
+  special casing for the given name and that standard traversal logic should
+  be applied.
+
 - LP #142226: Added an extra keyword argument to the HTTPResponse 
   setCookie method to suppress enclosing the cookie value field 
   in double quotes.

Modified: Zope/trunk/src/OFS/Traversable.py
===================================================================
--- Zope/trunk/src/OFS/Traversable.py	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/src/OFS/Traversable.py	2010-06-21 14:47:19 UTC (rev 113725)
@@ -30,6 +30,7 @@
 from Acquisition.interfaces import IAcquirer
 from OFS.interfaces import ITraversable
 from zExceptions import NotFound
+from ZPublisher.interfaces import UseTraversalDefault
 from ZODB.POSException import ConflictError
 
 from zope.interface import implements
@@ -207,60 +208,66 @@
                         except LocationError:
                             raise AttributeError(name)
 
-                    elif bobo_traverse is not None:
-                        next = bobo_traverse(REQUEST, name)
-                        if restricted:
-                            if aq_base(next) is not next:
-                                # The object is wrapped, so the acquisition
-                                # context is the container.
-                                container = aq_parent(aq_inner(next))
-                            elif getattr(next, 'im_self', None) is not None:
-                                # Bound method, the bound instance
-                                # is the container
-                                container = next.im_self
-                            elif getattr(aq_base(obj), name, _marker) is next:
-                                # Unwrapped direct attribute of the object so
-                                # object is the container
-                                container = obj
+                    else:
+                        next = UseTraversalDefault # indicator
+                        try:
+                            if bobo_traverse is not None:
+                                next = bobo_traverse(REQUEST, name)
+                                if restricted:
+                                    if aq_base(next) is not next:
+                                        # The object is wrapped, so the acquisition
+                                        # context is the container.
+                                        container = aq_parent(aq_inner(next))
+                                    elif getattr(next, 'im_self', None) is not None:
+                                        # Bound method, the bound instance
+                                        # is the container
+                                        container = next.im_self
+                                    elif getattr(aq_base(obj), name, _marker) is next:
+                                        # Unwrapped direct attribute of the object so
+                                        # object is the container
+                                        container = obj
+                                    else:
+                                        # Can't determine container
+                                        container = None
+                                    # If next is a simple unwrapped property, its
+                                    # parentage is indeterminate, but it may have
+                                    # been acquired safely. In this case validate
+                                    # will raise an error, and we can explicitly
+                                    # check that our value was acquired safely.
+                                    try:
+                                        ok = validate(obj, container, name, next)
+                                    except Unauthorized:
+                                        ok = False
+                                    if not ok:
+                                        if (container is not None or
+                                            guarded_getattr(obj, name, _marker)
+                                                is not next):
+                                            raise Unauthorized(name)
+                        except UseTraversalDefault:
+                            # behave as if there had been no '__bobo_traverse__'
+                            bobo_traverse = None
+                        if next is UseTraversalDefault:
+                            if getattr(aq_base(obj), name, _marker) is not _marker:
+                                if restricted:
+                                    next = guarded_getattr(obj, name)
+                                else:
+                                    next = getattr(obj, name)
                             else:
-                                # Can't determine container
-                                container = None
-                            # If next is a simple unwrapped property, its
-                            # parentage is indeterminate, but it may have
-                            # been acquired safely. In this case validate
-                            # will raise an error, and we can explicitly
-                            # check that our value was acquired safely.
-                            try:
-                                ok = validate(obj, container, name, next)
-                            except Unauthorized:
-                                ok = False
-                            if not ok:
-                                if (container is not None or
-                                    guarded_getattr(obj, name, _marker)
-                                        is not next):
+                                try:
+                                    next = obj[name]
+                                    # The item lookup may return a NullResource,
+                                    # if this is the case we save it and return it
+                                    # if all other lookups fail.
+                                    if isinstance(next, NullResource):
+                                        resource = next
+                                        raise KeyError(name)
+                                except AttributeError:
+                                    # Raise NotFound for easier debugging
+                                    # instead of AttributeError: __getitem__
+                                    raise NotFound(name)
+                                if restricted and not validate(
+                                    obj, obj, None, next):
                                     raise Unauthorized(name)
-                    else:
-                        if getattr(aq_base(obj), name, _marker) is not _marker:
-                            if restricted:
-                                next = guarded_getattr(obj, name)
-                            else:
-                                next = getattr(obj, name)
-                        else:
-                            try:
-                                next = obj[name]
-                                # The item lookup may return a NullResource,
-                                # if this is the case we save it and return it
-                                # if all other lookups fail.
-                                if isinstance(next, NullResource):
-                                    resource = next
-                                    raise KeyError(name)
-                            except AttributeError:
-                                # Raise NotFound for easier debugging
-                                # instead of AttributeError: __getitem__
-                                raise NotFound(name)
-                            if restricted and not validate(
-                                obj, obj, None, next):
-                                raise Unauthorized(name)
 
                 except (AttributeError, NotFound, KeyError), e:
                     # Try to look for a view

Modified: Zope/trunk/src/OFS/tests/testTraverse.py
===================================================================
--- Zope/trunk/src/OFS/tests/testTraverse.py	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/src/OFS/tests/testTraverse.py	2010-06-21 14:47:19 UTC (rev 113725)
@@ -354,6 +354,33 @@
         self.failUnlessRaises(Unauthorized,
                               self.root.folder1.restrictedTraverse, 'stuff')
 
+    def testBoboTraverseTraversalDefault(self):
+        from OFS.SimpleItem import SimpleItem
+        from ZPublisher.interfaces import UseTraversalDefault
+        class BoboTraversableUseTraversalDefault(SimpleItem):
+            """
+              A BoboTraversable class which may use "UseTraversalDefault"
+              (dependent on "name") to indicate that standard traversal should
+              be used.
+            """
+            default = 'Default'
+
+            def __bobo_traverse__(self, request, name):
+                if name == 'normal': return 'Normal'
+                raise UseTraversalDefault
+
+
+        bb = BoboTraversableUseTraversalDefault()
+        # normal access -- no traversal default used
+        self.assertEqual(bb.unrestrictedTraverse('normal'), 'Normal')
+        # use traversal default
+        self.assertEqual(bb.unrestrictedTraverse('default'), 'Default')
+        # test traversal default with acqires attribute
+        si = SimpleItem()
+        si.default_acquire = 'Default_Acquire'
+        si.bb = bb
+        self.assertEqual(si.unrestrictedTraverse('bb/default_acquire'), 'Default_Acquire')
+
     def testAcquiredAttributeDenial(self):
         # Verify that restrictedTraverse raises the right kind of exception
         # on denial of access to an acquired attribute.  If it raises

Modified: Zope/trunk/src/ZPublisher/BaseRequest.py
===================================================================
--- Zope/trunk/src/ZPublisher/BaseRequest.py	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/src/ZPublisher/BaseRequest.py	2010-06-21 14:47:19 UTC (rev 113725)
@@ -20,6 +20,7 @@
 
 from Acquisition import aq_base
 from Acquisition.interfaces import IAcquirer
+from ZPublisher.interfaces import UseTraversalDefault
 from zExceptions import Forbidden
 from zExceptions import NotFound
 from zope.component import queryMultiAdapter
@@ -79,31 +80,35 @@
         if name[:1]=='_':
             raise Forbidden("Object name begins with an underscore at: %s" % URL)
 
-        if hasattr(object,'__bobo_traverse__'):
-            try:
-                subobject=object.__bobo_traverse__(request, name)
-                if type(subobject) is type(()) and len(subobject) > 1:
-                    # Add additional parents into the path
-                    # XXX There are no tests for this:
-                    request['PARENTS'][-1:] = list(subobject[:-1])
-                    object, subobject = subobject[-2:]
-            except (AttributeError, KeyError, NotFound), e:
-                # Try to find a view
-                subobject = queryMultiAdapter((object, request), Interface, name)
-                if subobject is not None:
-                    # OFS.Application.__bobo_traverse__ calls
-                    # REQUEST.RESPONSE.notFoundError which sets the HTTP
-                    # status code to 404
-                    request.response.setStatus(200)
-                    # We don't need to do the docstring security check
-                    # for views, so lets skip it and return the object here.
-                    if IAcquirer.providedBy(subobject):
-                        subobject = subobject.__of__(object)
-                    return subobject
-                # No view found. Reraise the error raised by __bobo_traverse__
-                raise e
-        else:
-            # No __bobo_traverse__
+        subobject = UseTraversalDefault  # indicator
+        try:
+            if hasattr(object,'__bobo_traverse__'):
+                try:
+                    subobject=object.__bobo_traverse__(request, name)
+                    if type(subobject) is type(()) and len(subobject) > 1:
+                        # Add additional parents into the path
+                        # XXX There are no tests for this:
+                        request['PARENTS'][-1:] = list(subobject[:-1])
+                        object, subobject = subobject[-2:]
+                except (AttributeError, KeyError, NotFound), e:
+                    # Try to find a view
+                    subobject = queryMultiAdapter((object, request), Interface, name)
+                    if subobject is not None:
+                        # OFS.Application.__bobo_traverse__ calls
+                        # REQUEST.RESPONSE.notFoundError which sets the HTTP
+                        # status code to 404
+                        request.response.setStatus(200)
+                        # We don't need to do the docstring security check
+                        # for views, so lets skip it and return the object here.
+                        if IAcquirer.providedBy(subobject):
+                            subobject = subobject.__of__(object)
+                        return subobject
+                    # No view found. Reraise the error raised by __bobo_traverse__
+                    raise e
+        except UseTraversalDefault:
+            pass
+        if subobject is UseTraversalDefault:
+            # No __bobo_traverse__ or default traversal requested
             # Try with an unacquired attribute:
             if hasattr(aq_base(object), name):
                 subobject = getattr(object, name)

Modified: Zope/trunk/src/ZPublisher/interfaces.py
===================================================================
--- Zope/trunk/src/ZPublisher/interfaces.py	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/src/ZPublisher/interfaces.py	2010-06-21 14:47:19 UTC (rev 113725)
@@ -56,5 +56,17 @@
     something calls response.write() for the first time. Note that this is
     carries a reference to the *response*, not the request.
     """
-    
+
     response = Attribute(u"The current HTTP response")
+
+
+# Exceptions
+
+class UseTraversalDefault(Exception):
+    """Indicate default traversal in ``__bobo_traverse__``
+
+    This exception can be raised by '__bobo_traverse__' implementations to
+    indicate that it has no special casing for the given name and that standard
+    traversal logic should be applied.
+
+    """

Modified: Zope/trunk/src/ZPublisher/tests/testBaseRequest.py
===================================================================
--- Zope/trunk/src/ZPublisher/tests/testBaseRequest.py	2010-06-21 11:08:59 UTC (rev 113724)
+++ Zope/trunk/src/ZPublisher/tests/testBaseRequest.py	2010-06-21 14:47:19 UTC (rev 113725)
@@ -124,6 +124,27 @@
                 return self, self._default_path
         return DummyObjectWithBD()
 
+    def _makeObjectWithBBT(self):
+        from ZPublisher.interfaces import UseTraversalDefault
+
+        class _DummyResult(object):
+            ''' '''
+            def __init__(self, tag):
+                self.tag = tag
+
+        class DummyObjectWithBBT(self._makeBasicObjectClass()):
+            """ Dummy class with __bobo_traverse__
+            """
+            default = _DummyResult('Default')
+
+            def __bobo_traverse__(self, REQUEST, name):
+                if name == 'normal':
+                    return _DummyResult('Normal')
+                elif name == 'default':
+                    raise UseTraversalDefault
+                raise AttributeError(name)
+        return DummyObjectWithBBT()
+
     def _makeObjectWithBDBBT(self):
         class DummyObjectWithBDBBT(self._makeBasicObjectClass()):
             """Dummy class with __browser_default__."""
@@ -255,6 +276,16 @@
         self.failUnlessRaises(NotFound, r.traverse,
                               'folder/objWithBBT/bbt_foo')
 
+    def test_traverse_UseTraversalDefault(self):
+        root, folder = self._makeRootAndFolder()
+        folder._setObject('objWithBBT', self._makeObjectWithBBT())
+        # test non default usage
+        r = self._makeOne(root)
+        self.assertEqual(r.traverse('folder/objWithBBT/normal').tag, 'Normal')
+        # test default usage
+        r = self._makeOne(root)
+        self.assertEqual(r.traverse('folder/objWithBBT/default').tag, 'Default')
+
     def test_traverse_withBDBBT(self):
         # Test for an object which has a __browser_default__
         # and __bobo_traverse__



More information about the Zope-Checkins mailing list