Index: feature_acquisition_free_traversal.wiki =================================================================== --- feature_acquisition_free_traversal.wiki (revision 0) +++ feature_acquisition_free_traversal.wiki (revision 6813) @@ -0,0 +1,78 @@ +|| ''' ZopeNoaqTraversal''' || Ersteller: '' ["maurerd"]'' || +|| Erstellt am: [[Date(2008-04-09T06:45:41Z)]] || +|||| Traversal without acquisition. || +---- + += Table of Contents = +[[TableOfContents]] + += Introduction = +As everybody who has worked with acquisition knows, acquisition can +cause undesired and sometimes surprising effects. + +Our experience with such an undesired effect comes from +an optimized `QueueCatalog`. Requests to this `QueueCatalog` +consist of a pair path and a set of indexes -- with the meaning: update +indexes for the given path. Unindex requests are represented by +path and `None`, as the `QueueCatalog` can easily determine whether +the object corresponding to the path still exists (it is a reindex +request) or no longer exists (it is an unindex). +`unrestrictedTraverse` is used to resolve the path into an object +and `unrestrictedTraverse` uses acquisition. This can easily +lead to misclassifications when objects have been deleted. + +The `QueueCatalog` use case is only one of a large class of use +cases: acquisition is unwanted whenever one knows that the +traversal argument is derived from a physical path. +This usually is true for catalogs. + +Suppressing acquisition whenever it is known that acquisition +should not be necessary, leads to more robust solutions. +Therefore, acquisition control should directly be supported +by the `ITraversable` interface. + += Feature = +Extend the `OFS.ITraversable` interface such that `unrestrictedTraverse` +and `restrictedTraverse` get an additional parameter `noaq=False`. +If set to `True`, the traversal does not allow acquisition. + +In order to inform traversal hooks whether acquisition is unwanted, +the implementation in `OFS.Traversable` puts a `TraversalRequestNoAcquisition` +key in the pseudo request. + += Example Use Cases = + + * Let our `QueueCatalog` work reliably + + * Let the `ZCatalog` `updateCatalog` be able to remove ghost doublicates + created through copying an object containing the catalog. + + * Enable robust solutions whenever traversal parameters are known + to be derived from physical paths or relative physical paths. + += Notes = + + * It is unlikely that the proposal will be accepted -- as + the `ITraversable` interface extension will break + all its implementations apart from `OFS.Traversable.Traversable` + (which is extended in the same way). + + Extending interfaces is a difficult problem -- everywhere. + +[[BR]] +[[BR]] +[[BR]] +[[BR]] +[[BR]] +[[BR]] +[[BR]] +[[BR]] + +------ +[wiki:/Diskussion Diskussionsseite][[BR]] +[[AttachTable]] +---- + +[[BR]][[BR]] + + Index: lib/python/OFS/Traversable.py =================================================================== --- lib/python/OFS/Traversable.py (revision 1291) +++ lib/python/OFS/Traversable.py (working copy) @@ -119,7 +119,7 @@ return path security.declarePrivate('unrestrictedTraverse') - def unrestrictedTraverse(self, path, default=_marker, restricted=False): + def unrestrictedTraverse(self, path, default=_marker, restricted=False, noaq=False): """Lookup an object by path. path -- The path to the object. May be a sequence of strings or a slash @@ -136,6 +136,11 @@ restricted -- If false (default) then no security checking is performed. If true, then all of the objects along the path are validated with the security machinery. Usually invoked using restrictedTraverse(). + + noaq -- If true then acquisition is not used during (normal) + traversal. The pseudo request informs hooks via + the key "TraversalRequestNoAcquisition" whether acquisition + is unwanted. """ if not path: return self @@ -146,7 +151,9 @@ else: path = list(path) - REQUEST = {'TraversalRequestNameStack': path} + REQUEST = {'TraversalRequestNameStack': path, + 'TraversalRequestNoAcquisition': noaq, + } path.reverse() path_pop = path.pop @@ -269,6 +276,7 @@ raise e else: # No view, try acquired attributes + if noaq: raise e # we do not want acquisition try: if restricted: next = guarded_getattr(obj, name, _marker) @@ -296,9 +304,9 @@ raise security.declarePublic('restrictedTraverse') - def restrictedTraverse(self, path, default=_marker): + def restrictedTraverse(self, path, default=_marker, noaq=False): # Trusted code traversal code, always enforces securitys - return self.unrestrictedTraverse(path, default, restricted=True) + return self.unrestrictedTraverse(path, default, restricted=True, noaq=noaq) InitializeClass(Traversable) Index: lib/python/OFS/tests/testTraverse.py =================================================================== --- lib/python/OFS/tests/testTraverse.py (revision 1291) +++ lib/python/OFS/tests/testTraverse.py (working copy) @@ -245,6 +245,33 @@ self.failUnlessRaises( KeyError, self.folder1.unrestrictedTraverse, '/folder1/file2/' ) + def testTraverseNoAcquisition( self ): + self.failUnlessRaises( + KeyError, + self.folder1.unrestrictedTraverse, + 'folder1', + noaq=True, + ) + self.failUnlessRaises( + KeyError, + self.folder1.restrictedTraverse, + 'folder1', + noaq=True, + ) + # test hook information + class _Object(SimpleItem): + noaq = None + def __bobo_traverse__(self, request, name): + self.noaq = request['TraversalRequestNoAcquisition'] + return self + o = _Object() + self.folder1._setObject('o', o) + self.folder1.unrestrictedTraverse('o/x', noaq=True) + self.failUnlessEqual(o.noaq, True) + del o.noaq + self.folder1.unrestrictedTraverse('o/x') + self.failUnlessEqual(o.noaq, False) + def testTraverseMethodRestricted(self): self.root.my = Restricted('my') my = self.root.my Index: lib/python/OFS/interfaces.py =================================================================== --- lib/python/OFS/interfaces.py (revision 1291) +++ lib/python/OFS/interfaces.py (working copy) @@ -240,7 +240,7 @@ together. """ - def unrestrictedTraverse(path, default=None, restricted=0): + def unrestrictedTraverse(path, default=None, restricted=0, noaq=False): """Lookup an object by path. path -- The path to the object. May be a sequence of strings or a slash @@ -257,9 +257,13 @@ restricted -- If false (default) then no security checking is performed. If true, then all of the objects along the path are validated with the security machinery. Usually invoked using restrictedTraverse(). + + noaq -- If true then acquisition is not used during (normal) + traversal. An implementation should inform its potential hooks + in an adequate way whether or not acquisition is wanted. """ - def restrictedTraverse(path, default=None): + def restrictedTraverse(path, default=None, noaq=False): """Trusted code traversal code, always enforces security. """