diff options
author | Jim Fulton <jim@zope.com> | 2002-06-10 23:29:49 +0000 |
---|---|---|
committer | Jim Fulton <jim@zope.com> | 2002-06-10 23:29:49 +0000 |
commit | 9477e5b0825355d9bfe242b00e6b1a56060183f4 (patch) | |
tree | 9815b7bb6b07472017d7f2e201fdb112ca75b905 | |
download | zope-traversing-9477e5b0825355d9bfe242b00e6b1a56060183f4.tar.gz |
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.
-rw-r--r-- | AcquireNamespace.py | 57 | ||||
-rw-r--r-- | AttrItemNamespaces.py | 49 | ||||
-rw-r--r-- | CreateNamespace.py | 35 | ||||
-rw-r--r-- | DefaultTraversable.py | 39 | ||||
-rw-r--r-- | EtcNamespace.py | 54 | ||||
-rw-r--r-- | Exceptions.py | 22 | ||||
-rw-r--r-- | INamespaceHandler.py | 34 | ||||
-rw-r--r-- | ITraversable.py | 32 | ||||
-rw-r--r-- | ITraverser.py | 53 | ||||
-rw-r--r-- | Namespaces.py | 44 | ||||
-rw-r--r-- | ParameterParsing.py | 46 | ||||
-rw-r--r-- | PresentationNamespaces.py | 45 | ||||
-rw-r--r-- | Traverser.py | 124 | ||||
-rw-r--r-- | __init__.py | 18 | ||||
-rw-r--r-- | tests/__init__.py | 14 | ||||
-rw-r--r-- | tests/testAcquire.py | 63 | ||||
-rw-r--r-- | tests/testAtterItem.py | 48 | ||||
-rw-r--r-- | tests/testEtc.py | 49 | ||||
-rw-r--r-- | tests/testNamespaceTrversal.py | 52 | ||||
-rw-r--r-- | tests/testPresentation.py | 73 | ||||
-rw-r--r-- | tests/testTraverser.py | 240 | ||||
-rw-r--r-- | traversing.zcml | 19 |
22 files changed, 1210 insertions, 0 deletions
diff --git a/AcquireNamespace.py b/AcquireNamespace.py new file mode 100644 index 0000000..6d1c5c3 --- /dev/null +++ b/AcquireNamespace.py @@ -0,0 +1,57 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: AcquireNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Namespaces import provideNamespaceHandler +from Exceptions import UnexpectedParameters +from Zope.Exceptions import NotFoundError +from Zope.ComponentArchitecture import queryAdapter +from Zope.Proxy.ContextWrapper import ContextWrapper, getWrapperContext +from ITraversable import ITraversable + +class ExcessiveWrapping(NotFoundError): + """Too many levels of acquisition wrapping. We don't believe them.""" + +def acquire(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + + i = 0 + origOb = ob + while i < 200: + i += 1 + traversable = queryAdapter(ob, ITraversable, None) + if traversable is not None: + + try: + # XXX what do we do if the path gets bigger? + path = [] + next = traversable.traverse(name, parameters, pname, path) + if path: continue + except NotFoundError: + pass + else: + return ContextWrapper(next, ob, name=name) + + ob = getWrapperContext(ob) + if ob is None: + raise NotFoundError(origOb, pname) + + raise ExcessiveWrapping(origOb, pname) + +provideNamespaceHandler('acquire', acquire) + diff --git a/AttrItemNamespaces.py b/AttrItemNamespaces.py new file mode 100644 index 0000000..e602804 --- /dev/null +++ b/AttrItemNamespaces.py @@ -0,0 +1,49 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: AttrItemNamespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Namespaces import provideNamespaceHandler +from Exceptions import UnexpectedParameters + +def attr(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + return getattr(ob, name) + +provideNamespaceHandler('attribute', attr) + +def item(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + return ob[name] + +provideNamespaceHandler('item', item) + + +# YAGNI +# +# def accessor(name, parameters, ob, request): +# if parameters: +# raise UnexpectedParameters(parameters) +# +# method = getattr(ob, name, None) +# if method is None: +# raise NotFound(ob, name, request) +# +# return method() +# +# provideNamespaceHandler('accessor', accessor) diff --git a/CreateNamespace.py b/CreateNamespace.py new file mode 100644 index 0000000..383dad6 --- /dev/null +++ b/CreateNamespace.py @@ -0,0 +1,35 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: CreateNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Zope.ComponentArchitecture import getService +from Namespaces import provideNamespaceHandler +from Exceptions import UnexpectedParameters +from Zope.Exceptions import NotFoundError +from Zope.Proxy.ContextWrapper import ContextWrapper + +def create(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + + for addable in getService(ob, 'AddableContent').getAddables(ob): + if addable.id == name: + return ContextWrapper(addable, ob, name=name) + + raise NotFoundError(ob, pname) + +provideNamespaceHandler('create', create) diff --git a/DefaultTraversable.py b/DefaultTraversable.py new file mode 100644 index 0000000..e972122 --- /dev/null +++ b/DefaultTraversable.py @@ -0,0 +1,39 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +from ITraversable import ITraversable +from Zope.Exceptions import NotFoundError +from Exceptions import UnexpectedParameters + +class DefaultTraversable: + """Traverses objects via attribute and item lookup""" + + __implements__ = ITraversable + + def __init__(self, subject): + self._subject = subject + + def traverse(self, name, parameters, pname, furtherPath): + if parameters: + raise UnexpectedParameters(parameters) + subject = self._subject + r = getattr(subject, name, self) # self used as marker + if r is not self: + return r + + if hasattr(subject, '__getitem__'): + # Let exceptions propagate. + return self._subject[name] + else: + raise NotFoundError(self._subject, name) + diff --git a/EtcNamespace.py b/EtcNamespace.py new file mode 100644 index 0000000..1c77801 --- /dev/null +++ b/EtcNamespace.py @@ -0,0 +1,54 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: EtcNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" +from Zope.App.OFS.ApplicationControl.ApplicationControl \ + import ApplicationController +from Namespaces import provideNamespaceHandler +from Exceptions import UnexpectedParameters +from Zope.Exceptions import NotFoundError + +def etc(name, parameters, pname, ob, request): + # XXX + + # This is here now to allow us to get service managers from a + # separate namespace from the content. We add and etc + # namespace to allow us to handle misc objects. We'll apply + # YAGNI for now and hard code this. We'll want something more + # general later. We were thinking of just calling "get" + # methods, but this is probably too magic. In particular, we + # will treat returned objects as sub-objects wrt security and + # not all get methods may satisfy this assumption. It might be + # best to introduce some sort of etc registry. + + if parameters: + raise UnexpectedParameters(parameters) + + if name == 'ApplicationController' and ob is None: + return ApplicationController + + if name != 'Services': + + raise NotFoundError(ob, pname, request) + + method_name = "getServiceManager" + method = getattr(ob, method_name, None) + if method is None: + raise NotFound(ob, pname, request) + + return method() + +provideNamespaceHandler('etc', etc) diff --git a/Exceptions.py b/Exceptions.py new file mode 100644 index 0000000..09a64e3 --- /dev/null +++ b/Exceptions.py @@ -0,0 +1,22 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: Exceptions.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" +from Zope.Exceptions import NotFoundError + +class UnexpectedParameters(NotFoundError): + """Unexpected namespace parameters were provided. + """ diff --git a/INamespaceHandler.py b/INamespaceHandler.py new file mode 100644 index 0000000..bd7b553 --- /dev/null +++ b/INamespaceHandler.py @@ -0,0 +1,34 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: INamespaceHandler.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Interface import Interface + +class INamespaceHandler(Interface): + + def __call__(name, parameters, pname, object, request): + """Access a name in a namespace + + The name lookup usually depends on an object and/or a + request. If an object or request is unavailable, None will be passed. + + The parameters provided, are passed as a sequence of + name, value items. The 'pname' argument has the original name + before parameters were removed. + + It is not the respoonsibility of the handler to wrap the return value. + """ diff --git a/ITraversable.py b/ITraversable.py new file mode 100644 index 0000000..b81305f --- /dev/null +++ b/ITraversable.py @@ -0,0 +1,32 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import Interface + +class ITraversable(Interface.Base): + """To traverse an object, this interface must be provided""" + + def traverse(name, parameters, pname, furtherPath): + """Get the next item on the path + + Should return the item corresponding to 'name' or raise + Zope.Exceptions.NotFoundError where appropriate. + + The parameters provided, are passed as a sequence of + name, value items. The 'pname' argument has the original name + before parameters were removed. + + furtherPath is a list of names still to be traversed. This method is + allowed to change the contents of furtherPath. + + """ diff --git a/ITraverser.py b/ITraverser.py new file mode 100644 index 0000000..f02d72b --- /dev/null +++ b/ITraverser.py @@ -0,0 +1,53 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: ITraverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +import Interface + +_RAISE_KEYERROR=[] + +class ITraverser(Interface.Base): + """Provide traverse features""" + + def getPhysicalRoot(): + """ + Returns the top-level Application object, or the base object if it is + unwrapped. + """ + + def getPhysicalPath(): + """ + Returns a path (an immutable sequence of strings) from the root. + + This path can be used to access this object again later, for example + in a copy/paste operation. Returns an empty tuple if the base object + is not wrapped. + """ + + def traverse(path, default=_RAISE_KEYERROR): + """ + Return an object given a path. + + Path is either an immutable sequence of strings or a slash ('/') + delimited string. + + If the first string in the path sequence is an empty string, + or the path begins with a '/', start at the root. Otherwise the path + is relative to the current context. + + If the object is not found, return 'default' argument. + """ diff --git a/Namespaces.py b/Namespaces.py new file mode 100644 index 0000000..8ad5960 --- /dev/null +++ b/Namespaces.py @@ -0,0 +1,44 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: Namespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Zope.Exceptions import NotFoundError +from Zope.Proxy.ContextWrapper import ContextWrapper + +_namespace_handlers = {} + +def provideNamespaceHandler(ns, handler): + _namespace_handlers[ns] = handler + +def namespaceLookup(name, ns, qname, parameters, object, request=None): + """Lookup a value from a namespace + + name -- the original name + ns -- The namespace + qname -- The name without any parameters + """ + + handler = _namespace_handlers.get(ns) + if handler is None: + raise NotFoundError(name) + new = ContextWrapper(handler(qname, parameters, name, object, request), + object, name=name) + return new + +# Register the etc, view, and resource namespaces +import EtcNamespace, PresentationNamespaces, AttrItemNamespaces +import CreateNamespace, AcquireNamespace diff --git a/ParameterParsing.py b/ParameterParsing.py new file mode 100644 index 0000000..ff624a2 --- /dev/null +++ b/ParameterParsing.py @@ -0,0 +1,46 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: ParameterParsing.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +import re + +namespace_pattern = re.compile('[+][+]([a-zA-Z0-9_]+)[+][+]') + +def parameterizedNameParse(name): + """Parse a name with parameters, including namespace parameters. + + Return: + + - namespace, or None if there isn't one. + + - unparameterized name + + - sequence of parameters, as name-value pairs. + """ + + ns = '' + if name[:2] == '@@': + ns = 'view' + name = name[2:] + else: + match = namespace_pattern.match(name) + if match: + prefix, ns = match.group(0, 1) + name = name[len(prefix):] + + return ns, name, () diff --git a/PresentationNamespaces.py b/PresentationNamespaces.py new file mode 100644 index 0000000..99dd451 --- /dev/null +++ b/PresentationNamespaces.py @@ -0,0 +1,45 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: PresentationNamespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from Zope.ComponentArchitecture import getView, getResource +from Namespaces import provideNamespaceHandler +from Exceptions import UnexpectedParameters +from Zope.Exceptions import NotFoundError + +class NoRequest(NotFoundError): + """Atempt to access a presentation component outside of a request context + """ + +def view(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + if not request: + raise NoRequest(pname) + return getView(ob, name, request) + +provideNamespaceHandler('view', view) + +def resource(name, parameters, pname, ob, request): + if parameters: + raise UnexpectedParameters(parameters) + if not request: + raise NoRequest(pname) + return getResource(ob, name, request) + +provideNamespaceHandler('resource', resource) + diff --git a/Traverser.py b/Traverser.py new file mode 100644 index 0000000..011d7c2 --- /dev/null +++ b/Traverser.py @@ -0,0 +1,124 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Default implementation of ITraverser. + +$Id: Traverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from ITraverser import ITraverser +from ITraversable import ITraversable +from Zope.ContextWrapper.IWrapper import IWrapper +from Zope.Proxy.ContextWrapper import getWrapperContext, getWrapperData +from Zope.Proxy.ContextWrapper import ContextWrapper +from Zope.ComponentArchitecture import queryAdapter +from Zope.Exceptions import NotFoundError, Unauthorized +from Namespaces import namespaceLookup +from ParameterParsing import parameterizedNameParse +from Zope.Security.SecurityManagement import getSecurityManager + +from types import StringTypes + +from __future__ import generators + +# A chain generator; let's us walk the wrapper chain down to the root +def WrapperChain(w): + while w is not None: + yield w + w = getWrapperContext(w) + +_marker = object() + +class Traverser: + """Provide traverse features""" + + __implements__ = ITraverser + + # This adapter can be used for any object. + + def __init__(self, wrapper): + self._wrapper = wrapper + + def getPhysicalRoot(self): + # Loop over all wrappers until the last one, which is the root. + for w in WrapperChain(self._wrapper): + pass + return w + + def getPhysicalPath(self): + path = [] + + for w in WrapperChain(self._wrapper): + d = getWrapperData(w) + if d: + path.insert(0, d['name']) + + return tuple(path) + + def traverse(self, path, default=_marker, request=None): + if not path: + return self._wrapper + + if isinstance(path, StringTypes): + path = path.split('/') + if len(path) > 1 and not path[-1]: + # Remove trailing slash + path.pop() + else: + path = list(path) + + path.reverse() + pop = path.pop + + curr = self._wrapper + if not path[-1]: + # Start at the root + pop() + curr = self.getPhysicalRoot() + try: + while path: + name = pop() + + if name == '.': + continue + + if name == '..': + curr = getWrapperContext(curr) or curr + continue + + + if name and name[:1] in '@+': + ns, nm, parms = parameterizedNameParse(name) + if ns: + curr = namespaceLookup(name, ns, nm, parms, + curr, request) + continue + else: + parms = () + nm = name + + traversable = queryAdapter(curr, ITraversable, None) + if traversable is None: + raise NotFoundError, 'No traversable adapter found' + + next = traversable.traverse(nm, parms, name, path) + curr = ContextWrapper(next, curr, name=name) + + return curr + + except: + if default == _marker: + raise + return default + + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..656c898 --- /dev/null +++ b/__init__.py @@ -0,0 +1,18 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" +Traversing the object tree. +""" + + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..cd904c8 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,14 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""App tests""" diff --git a/tests/testAcquire.py b/tests/testAcquire.py new file mode 100644 index 0000000..0beb6ab --- /dev/null +++ b/tests/testAcquire.py @@ -0,0 +1,63 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: testAcquire.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from unittest import TestCase, TestSuite, main, makeSuite +from Zope.ComponentArchitecture.tests.PlacelessSetup import PlacelessSetup +from Zope.App.Traversing.ITraversable import ITraversable +from Zope.App.Traversing.DefaultTraversable import DefaultTraversable +from Zope.ComponentArchitecture.GlobalAdapterService import provideAdapter +from Zope.Proxy.ContextWrapper import ContextWrapper, getWrapperContext +from Zope.App.Traversing.AcquireNamespace import acquire +from Zope.Exceptions import NotFoundError + +class Test(PlacelessSetup, TestCase): + + def test(self): + provideAdapter(None, ITraversable, DefaultTraversable) + + class C: + def __init__(self, name): + self.name = name + + a = C('a') + a.a1 = C('a1') + a.a2 = C('a2') + a.a2.a21 = C('a21') + a.a2.a21.a211 = C('a211') + + a2 = ContextWrapper(a.a2, a) + a21 = ContextWrapper(a.a2.a21, a2) + a211 = ContextWrapper(a.a2.a21.a211, a21) + + acquired = acquire('a1', (), 'a1;acquire', a211, None) + + self.assertEqual(acquired.name, 'a1') + + self.assertRaises(NotFoundError, + acquire, 'a3', (), 'a1;acquire', a211, None) + + + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/tests/testAtterItem.py b/tests/testAtterItem.py new file mode 100644 index 0000000..70eb072 --- /dev/null +++ b/tests/testAtterItem.py @@ -0,0 +1,48 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: testAtterItem.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from unittest import TestCase, TestSuite, main, makeSuite +from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup + + +class C: + a = 1 + def __getitem__(self, key): return key+'value' +c=C() + + +class Test(CleanUp, TestCase): + + def testAttr(self): + from Zope.App.Traversing.AttrItemNamespaces import attr + self.assertEqual(attr('a', (), 'a;attribute', c, None), 1) + + def testItem(self): + from Zope.App.Traversing.AttrItemNamespaces import item + self.assertEqual(item('a', (), 'a;item', c, None), 'avalue') + + + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/tests/testEtc.py b/tests/testEtc.py new file mode 100644 index 0000000..8080e1f --- /dev/null +++ b/tests/testEtc.py @@ -0,0 +1,49 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: testEtc.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from unittest import TestCase, TestSuite, main, makeSuite +from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup + +class Test(CleanUp, TestCase): + + def testApplicationControl(self): + from Zope.App.Traversing.EtcNamespace import etc + from Zope.App.OFS.ApplicationControl.ApplicationControl \ + import ApplicationController + + self.assertEqual( + etc('ApplicationController', (), '++etc++Services', None, None), + ApplicationController) + + def testServices(self): + from Zope.App.Traversing.EtcNamespace import etc + class C: + def getServiceManager(self): return 42 + + self.assertEqual(etc('Services', (), 'etc:Services', C(), None), 42) + + + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/tests/testNamespaceTrversal.py b/tests/testNamespaceTrversal.py new file mode 100644 index 0000000..aaeb28c --- /dev/null +++ b/tests/testNamespaceTrversal.py @@ -0,0 +1,52 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: testNamespaceTrversal.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from unittest import TestCase, TestSuite, main, makeSuite +from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup + +class C: + a = 1 + def __getitem__(self, key): return key+'value' + +c=C() + + +class Test(CleanUp, TestCase): + + def testAttr(self): + from Zope.App.Traversing.Traverser import Traverser + traverser = Traverser(c) + v = traverser.traverse('++attribute++a') + self.assertEqual(v, 1) + + def testItem(self): + from Zope.App.Traversing.Traverser import Traverser + traverser = Traverser(c) + v = traverser.traverse('++item++a') + self.assertEqual(v, 'avalue') + + + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/tests/testPresentation.py b/tests/testPresentation.py new file mode 100644 index 0000000..26c69ed --- /dev/null +++ b/tests/testPresentation.py @@ -0,0 +1,73 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +""" + +Revision information: +$Id: testPresentation.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +from unittest import TestCase, TestSuite, main, makeSuite +from Zope.ComponentArchitecture.tests.PlacelessSetup import PlacelessSetup +from Zope.ComponentArchitecture.GlobalViewService import provideView +from Zope.ComponentArchitecture.GlobalResourceService import provideResource +from Zope.App.Traversing.PresentationNamespaces import view, resource +from Zope.Exceptions import NotFoundError +from Interface import Interface + +class IContent(Interface): pass +class IPresentationType(Interface): pass + +class Content: __implements__ = IContent + +class Resource: + + def __init__(self, request): pass + __implements__ = IPresentationType + +class View: + __implements__ = IPresentationType + + def __init__(self, content, request): + self.content = content + +class Request: + + def getPresentationType(self): return IPresentationType + def getPresentationSkin(self): return '' + + +class Test(PlacelessSetup, TestCase): + + def testView(self): + provideView(IContent, 'foo', IPresentationType, [View]) + + ob = Content() + v = view('foo', (), '@@foo', ob, Request()) + self.assertEqual(v.__class__, View) + + def testResource(self): + provideResource('foo', IPresentationType, Resource) + + ob = Content() + r = resource('foo', (), '++resource++foo', ob, Request()) + self.assertEqual(r.__class__, Resource) + + +def test_suite(): + return TestSuite(( + makeSuite(Test), + )) + +if __name__=='__main__': + main(defaultTest='test_suite') diff --git a/tests/testTraverser.py b/tests/testTraverser.py new file mode 100644 index 0000000..0cfdf5e --- /dev/null +++ b/tests/testTraverser.py @@ -0,0 +1,240 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" + +$Id: testTraverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $ +""" + +import unittest +from Zope.App.Traversing.ITraverser import ITraverser +from Zope.App.Traversing.ITraversable import ITraversable +from Zope.App.Traversing.Traverser import Traverser +from Zope.App.Traversing.DefaultTraversable import DefaultTraversable +from Zope.Proxy.ContextWrapper import ContextWrapper +from Zope.Exceptions import NotFoundError, Unauthorized +from Zope.ComponentArchitecture import getService +from Zope.Security.SecurityManagement import setSecurityPolicy, \ + noSecurityManager + +from Interface.Verify import verifyClass +from Interface.Implements import instancesOfObjectImplements +from Zope.App.OFS.Services.ServiceManager.tests.PlacefulSetup import PlacefulSetup +from Zope.Security.Checker \ + import ProxyFactory, defineChecker, NamesChecker, CheckerPublic, Checker +from Zope.Security.SecurityManagement import newSecurityManager + +class C: + def __init__(self, name): + self.name = name + +class TraverserTests(PlacefulSetup, unittest.TestCase): + + def setUp(self): + PlacefulSetup.setUp(self) + # Build up a wrapper chain + self.root = ContextWrapper(C('root'), None, name='') + self.folder = ContextWrapper(C('folder'), self.root, name='folder') + self.item = ContextWrapper(C('item'), self.folder, name='item') + self.tr = Traverser(self.item) + + def testImplementsITraverser(self): + self.failUnless(ITraverser.isImplementedBy(self.tr)) + + def testVerifyInterfaces(self): + for interface in instancesOfObjectImplements(Traverser): + verifyClass(interface, Traverser) + + def testPhysicalRoot(self): + self.failUnless(self.tr.getPhysicalRoot() is self.root) + + def testPhysicalPath(self): + self.assertEquals(self.tr.getPhysicalPath(), ('', 'folder', 'item')) + + def testUnwrapped(self): + # The feature should do the right thing for unwrapped objects too + unwrapped = C('unwrapped') + tr = Traverser(unwrapped) + self.assertEquals(tr.getPhysicalPath(), ()) + self.failUnless(tr.getPhysicalRoot() is unwrapped) + +class UnrestrictedNoTraverseTests(unittest.TestCase): + def setUp(self): + self.root = root = C('root') + self.folder = folder = C('folder') + self.item = item = C('item') + + root.folder = folder + folder.item = item + + self.tr = Traverser(root) + + def testNoTraversable(self): + self.assertRaises(NotFoundError, self.tr.traverse, + 'folder') + +class UnrestrictedTraverseTests(PlacefulSetup, unittest.TestCase): + def setUp(self): + PlacefulSetup.setUp(self) + # Build up a wrapper chain + + getService(None,"Adapters").provideAdapter( + None, ITraversable, DefaultTraversable) + + self.root = root = C('root') + self.folder = folder = C('folder') + self.item = item = C('item') + + root.folder = folder + folder.item = item + + self.tr = Traverser(root) + + def testSimplePathString(self): + tr = self.tr + item = self.item + + self.assertEquals(tr.traverse('/folder/item'), item) + self.assertEquals(tr.traverse('folder/item'), item) + self.assertEquals(tr.traverse('/folder/item/'), item) + + def testSimplePathUnicode(self): + tr = self.tr + item = self.item + + self.assertEquals(tr.traverse(u'/folder/item'), item) + self.assertEquals(tr.traverse(u'folder/item'), item) + self.assertEquals(tr.traverse(u'/folder/item/'), item) + + def testSimplePathTuple(self): + tr = self.tr + item = self.item + + self.assertEquals(tr.traverse(('', 'folder', 'item')), + item) + self.assertEquals(tr.traverse(('folder', 'item')), item) + + def testComplexPathString(self): + tr = self.tr + item = self.item + + self.assertEquals(tr.traverse('/folder/../folder/./item'), + item) + self.assertEquals(tr.traverse( + '/../folder/../../folder/item'), item) + self.assertEquals(tr.traverse('../../folder/item'), item) + + def testNotFoundDefault(self): + self.assertEquals(self.tr.traverse('foo', 'notFound'), + 'notFound') + + def testNotFoundNoDefault(self): + self.assertRaises(NotFoundError, self.tr.traverse, 'foo') + +def Denied(*names): + + def check(name): + if name in names: + return 'Waaaa' + return CheckerPublic + + return Checker(check) + +class RestrictedTraverseTests(PlacefulSetup, unittest.TestCase): + _oldPolicy = None + _deniedNames = () + + def setUp(self): + PlacefulSetup.setUp(self) + + getService(None,"Adapters").provideAdapter( + None, ITraversable, DefaultTraversable) + + self.root = root = C('root') + self.folder = folder = C('folder') + self.item = item = C('item') + + root.folder = folder + folder.item = item + + self.tr = Traverser(ProxyFactory(root)) + + def testAllAllowed(self): + defineChecker(C, Checker(lambda name: CheckerPublic)) + tr = Traverser(ProxyFactory(self.root)) + item = self.item + + self.assertEquals(tr.traverse(('', 'folder', 'item')), item) + self.assertEquals(tr.traverse(('folder', 'item')), item) + + def testItemDenied(self): + newSecurityManager('no one') + defineChecker(C, Denied('item')) + tr = Traverser(ProxyFactory(self.root)) + folder = self.folder + + self.assertRaises(Unauthorized, tr.traverse, + ('', 'folder', 'item')) + self.assertRaises(Unauthorized, tr.traverse, + ('folder', 'item')) + self.assertEquals(tr.traverse(('', 'folder')), folder) + self.assertEquals(tr.traverse(('folder', '..', 'folder')), + folder) + self.assertEquals(tr.traverse(('folder',)), folder) + +class DefaultTraversableTests(unittest.TestCase): + def testImplementsITraversable(self): + self.failUnless(ITraversable.isImplementedBy(DefaultTraversable(None))) + + def testVerifyInterfaces(self): + for interface in instancesOfObjectImplements(DefaultTraversable): + verifyClass(interface, DefaultTraversable) + + def testAttributeTraverse(self): + root = C('root') + item = C('item') + root.item = item + df = DefaultTraversable(root) + + further = [] + next = df.traverse('item', (), 'item', further) + self.failUnless(next is item) + self.assertEquals(further, []) + + def testDictionaryTraverse(self): + dict = {} + foo = C('foo') + dict['foo'] = foo + df = DefaultTraversable(dict) + + further = [] + next = df.traverse('foo', (), 'foo', further) + self.failUnless(next is foo) + self.assertEquals(further, []) + + def testNotFound(self): + df = DefaultTraversable(C('dummy')) + + self.assertRaises(NotFoundError, df.traverse, 'bar', (), 'bar', []) + +def test_suite(): + loader = unittest.TestLoader() + suite = loader.loadTestsFromTestCase(TraverserTests) + suite.addTest(loader.loadTestsFromTestCase(DefaultTraversableTests)) + suite.addTest(loader.loadTestsFromTestCase(UnrestrictedNoTraverseTests)) + suite.addTest(loader.loadTestsFromTestCase(UnrestrictedTraverseTests)) + suite.addTest(loader.loadTestsFromTestCase(RestrictedTraverseTests)) + return suite + +if __name__=='__main__': + unittest.TextTestRunner().run(test_suite()) diff --git a/traversing.zcml b/traversing.zcml new file mode 100644 index 0000000..29b8d77 --- /dev/null +++ b/traversing.zcml @@ -0,0 +1,19 @@ +<zopeConfigure + xmlns='http://namespaces.zope.org/zope' + xmlns:security='http://namespaces.zope.org/security' + xmlns:zmi='http://namespaces.zope.org/zmi' + xmlns:browser='http://namespaces.zope.org/browser' +> + +<adapter factory="Zope.App.Traversing.Traverser." + provides="Zope.App.Traversing.ITraverser." + /> + <!-- Ultimately, this should be registered only for IWrapper, but that + won't work like that just now. + for="Zope.ContextWrapper.IWrapper." /> --> + +<adapter factory="Zope.App.Traversing.DefaultTraversable." + provides="Zope.App.Traversing.ITraversable." /> + + +</zopeConfigure> |