diff options
| author | Chris McDonough <chrism@agendaless.com> | 2008-08-08 23:10:20 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2008-08-08 23:10:20 +0000 |
| commit | cc5e490467c105b8cce35c0bca688ed18ec93255 (patch) | |
| tree | b80b330385a9d796ea99f74b88eaaeee90d057b6 | |
| parent | 8fc21eb80a3c0fbe2ec4984636f86acab228d6b7 (diff) | |
| download | pyramid-cc5e490467c105b8cce35c0bca688ed18ec93255.tar.gz pyramid-cc5e490467c105b8cce35c0bca688ed18ec93255.tar.bz2 pyramid-cc5e490467c105b8cce35c0bca688ed18ec93255.zip | |
- Add ``find_context_from_path`` and ``find_root`` traversal APIs.
In the process, make ITraverser a uni-adapter (on context) rather
than a multiadapter (on context and request).
| -rw-r--r-- | CHANGES.txt | 6 | ||||
| -rw-r--r-- | docs/api/traversal.rst | 5 | ||||
| -rw-r--r-- | repoze/bfg/configure.zcml | 4 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 2 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_traversal.py | 123 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 8 | ||||
| -rw-r--r-- | repoze/bfg/traversal.py | 32 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 3 |
8 files changed, 149 insertions, 34 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index eac0c6b48..84a27b021 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,9 @@ +After 0.2.7 + + - Add ``find_context_from_path`` and ``find_root`` traversal APIs. + In the process, make ITraverser a uni-adapter (on context) rather + than a multiadapter (on context and request). + 0.2.7 - Add a ``request_type`` attribute to the available attributes of a diff --git a/docs/api/traversal.rst b/docs/api/traversal.rst index eac63a271..240b47af6 100644 --- a/docs/api/traversal.rst +++ b/docs/api/traversal.rst @@ -7,5 +7,10 @@ .. autofunction:: find_interface + .. autofunction:: find_root + .. autofunction:: model_url + .. autofunction:: find_context_from_path + + diff --git a/repoze/bfg/configure.zcml b/repoze/bfg/configure.zcml index 78dce0b83..58cbff93b 100644 --- a/repoze/bfg/configure.zcml +++ b/repoze/bfg/configure.zcml @@ -6,13 +6,13 @@ <adapter factory=".traversal.ModelGraphTraverser" provides=".interfaces.ITraverserFactory" - for="* .interfaces.IRequest" + for="*" /> <adapter factory=".urldispatch.RoutesModelTraverser" provides=".interfaces.ITraverserFactory" - for=".interfaces.IRoutesContext .interfaces.IRequest" + for=".interfaces.IRoutesContext" /> <include file="meta.zcml" /> diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 4d557b098..4f1a203df 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -23,7 +23,7 @@ class ITraverser(Interface): the result of an object graph traversal """ class ITraverserFactory(Interface): - def __call__(context, request): + def __call__(context): """ Return an object that implements IPublishTraverser """ class ITemplateFactory(Interface): diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py index 1ae92dcf7..075cfe85e 100644 --- a/repoze/bfg/tests/test_traversal.py +++ b/repoze/bfg/tests/test_traversal.py @@ -57,12 +57,10 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): from zope.interface.verify import verifyObject from repoze.bfg.interfaces import ITraverser context = DummyContext() - request = DummyRequest() - verifyObject(ITraverser, self._makeOne(context, request)) + verifyObject(ITraverser, self._makeOne(context)) def test_call_pathel_with_no_getitem(self): - request = DummyRequest() - policy = self._makeOne(None, request) + policy = self._makeOne(None) environ = self._getEnviron(PATH_INFO='/foo/bar') ctx, name, subpath = policy(environ) self.assertEqual(ctx, None) @@ -71,8 +69,7 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): def test_call_withconn_getitem_emptypath_nosubpath(self): root = DummyContext() - request = DummyRequest() - policy = self._makeOne(root, request) + policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='') ctx, name, subpath = policy(environ) self.assertEqual(ctx, root) @@ -82,8 +79,7 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): def test_call_withconn_getitem_withpath_nosubpath(self): foo = DummyContext() root = DummyContext(foo) - request = DummyRequest() - policy = self._makeOne(root, request) + policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/foo/bar') ctx, name, subpath = policy(environ) self.assertEqual(ctx, foo) @@ -92,9 +88,8 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): def test_call_withconn_getitem_withpath_withsubpath(self): foo = DummyContext() - request = DummyRequest() root = DummyContext(foo) - policy = self._makeOne(root, request) + policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/foo/bar/baz/buz') ctx, name, subpath = policy(environ) self.assertEqual(ctx, foo) @@ -103,9 +98,8 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): def test_call_with_explicit_viewname(self): foo = DummyContext() - request = DummyRequest() root = DummyContext(foo) - policy = self._makeOne(root, request) + policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/@@foo') ctx, name, subpath = policy(environ) self.assertEqual(ctx, root) @@ -117,7 +111,6 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) - request = DummyRequest() from zope.interface import directlyProvides from zope.location.interfaces import ILocation directlyProvides(root, ILocation) @@ -126,7 +119,7 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup): # give bar a direct parent and name to mix things up a bit bar.__name__ = 'bar' bar.__parent__ = foo - policy = self._makeOne(root, request) + policy = self._makeOne(root) environ = self._getEnviron(PATH_INFO='/foo/bar/baz') ctx, name, subpath = policy(environ) self.assertEqual(ctx, baz) @@ -196,6 +189,99 @@ class ModelURLTests(unittest.TestCase): result, 'http://example.com:5432/foo%20/bar/baz/this/theotherthing/that') +class FindRootTests(unittest.TestCase): + def _getFUT(self): + from repoze.bfg.traversal import find_root + return find_root + + def test_it(self): + dummy = DummyContext() + baz = DummyContext() + baz.__parent__ = dummy + baz.__name__ = 'baz' + dummy.__parent__ = None + dummy.__name__ = None + find = self._getFUT() + result = find(baz) + self.assertEqual(result, dummy) + +class FindContextFromPathTests(unittest.TestCase): + def _getFUT(self): + from repoze.bfg.traversal import find_context_from_path + return find_context_from_path + + def _registerTraverser(self, traverser): + import zope.component + gsm = zope.component.getGlobalSiteManager() + from repoze.bfg.interfaces import ITraverser + from zope.interface import Interface + gsm.registerAdapter(traverser, (Interface,), ITraverser) + + def test_relative_found(self): + dummy = DummyContext() + baz = DummyContext() + find = self._getFUT() + traverser = make_traverser(baz, '', []) + self._registerTraverser(traverser) + result = find(dummy, 'baz') + self.assertEqual(result, baz) + + def test_relative_notfound(self): + dummy = DummyContext() + baz = DummyContext() + find = self._getFUT() + traverser = make_traverser(baz, 'bar', []) + self._registerTraverser(traverser) + self.assertRaises(KeyError, find, dummy, 'baz') + + def test_absolute_found(self): + dummy = DummyContext() + baz = DummyContext() + baz.__parent__ = dummy + baz.__name__ = 'baz' + dummy.__parent__ = None + dummy.__name__ = None + find = self._getFUT() + traverser = make_traverser(dummy, '', []) + self._registerTraverser(traverser) + result = find(baz, '/') + self.assertEqual(result, dummy) + + def test_absolute_found(self): + dummy = DummyContext() + baz = DummyContext() + baz.__parent__ = dummy + baz.__name__ = 'baz' + dummy.__parent__ = None + dummy.__name__ = None + find = self._getFUT() + traverser = make_traverser(dummy, '', []) + self._registerTraverser(traverser) + result = find(baz, '/') + self.assertEqual(result, dummy) + self.assertEqual(dummy.wascontext, True) + + def test_absolute_notfound(self): + dummy = DummyContext() + baz = DummyContext() + baz.__parent__ = dummy + baz.__name__ = 'baz' + dummy.__parent__ = None + dummy.__name__ = None + find = self._getFUT() + traverser = make_traverser(dummy, 'fuz', []) + self._registerTraverser(traverser) + self.assertRaises(KeyError, find, baz, '/') + self.assertEqual(dummy.wascontext, True) + +def make_traverser(*args): + class DummyTraverser(object): + def __init__(self, context): + context.wascontext = True + def __call__(self, environ): + return args + return DummyTraverser + class DummyContext(object): def __init__(self, next=None): self.next = next @@ -208,12 +294,3 @@ class DummyContext(object): class DummyRequest: application_url = 'http://example.com:5432/' -class DummyTraverser: - def __init__(self, context): - self.context = context - - def __call__(self, environ, name): - try: - return name, self.context[name] - except KeyError: - return name, None diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index 0b69b5bec..84c7b5c09 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -76,9 +76,9 @@ class TestRoutesModelTraverser(unittest.TestCase): from repoze.bfg.urldispatch import RoutesModelTraverser return RoutesModelTraverser - def _makeOne(self, model, request): + def _makeOne(self, model): klass = self._getTargetClass() - return klass(model, request) + return klass(model) def test_class_conforms_to_ITraverser(self): from zope.interface.verify import verifyClass @@ -88,11 +88,11 @@ class TestRoutesModelTraverser(unittest.TestCase): def test_instance_conforms_to_ITraverser(self): from zope.interface.verify import verifyObject from repoze.bfg.interfaces import ITraverser - verifyObject(ITraverser, self._makeOne(None, None)) + verifyObject(ITraverser, self._makeOne(None)) def test_call(self): model = DummyModel() - traverser = self._makeOne(model, None) + traverser = self._makeOne(model) result = traverser({}) self.assertEqual(result[0], model) self.assertEqual(result[1], 'controller') diff --git a/repoze/bfg/traversal.py b/repoze/bfg/traversal.py index c72c90e5e..3fe11f9f7 100644 --- a/repoze/bfg/traversal.py +++ b/repoze/bfg/traversal.py @@ -41,10 +41,9 @@ _marker = () class ModelGraphTraverser(object): classProvides(ITraverserFactory) implements(ITraverser) - def __init__(self, root, request): + def __init__(self, root): self.root = root self.locatable = ILocation.providedBy(root) - self.request = request def __call__(self, environ): path = environ.get('PATH_INFO', '/') @@ -66,6 +65,35 @@ class ModelGraphTraverser(object): return ob, name, path +def find_root(model): + """ Find the root node in the graph to which ``model`` + belongs. Note that ``model`` should be :term:`location`-aware.""" + for location in LocationIterator(model): + if location.__parent__ is None: + model = location + break + return model + +def find_context_from_path(model, path): + """ Given a model object and a string representing a path + reference (a set of names delimited by forward-slashes), return an + context in this application's model graph at the specified path. + If the path starts with a slash, the path is considered absolute + and the graph traversal will start at the root object. If the + path does not start with a slash, the path is considered relative + and graph traversal will begin at the model object supplied to the + function. In either case, if the path cannot be resolved, a + KeyError will be thrown. Note that the model passed in should be + :term:`location`-aware.""" + + if path.startswith('/'): + model = find_root(model) + + ob, name, path = ITraverser(model)({'PATH_INFO':path}) + if name: + raise KeyError('%r has no subelement %s' % (ob, name)) + return ob + def find_interface(context, interface): """ Return an object providing ``interface`` anywhere in the parent chain of ``context`` or ``None`` if no object providing diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index b2711d5cb..7f81dbd30 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -81,9 +81,8 @@ class RoutesMapper(object): class RoutesModelTraverser(object): classProvides(ITraverserFactory) implements(ITraverser) - def __init__(self, context, request): + def __init__(self, context): self.context = context - self.request = request def __call__(self, environ): return self.context, self.context.controller, '' |
