summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-08-08 23:10:20 +0000
committerChris McDonough <chrism@agendaless.com>2008-08-08 23:10:20 +0000
commitcc5e490467c105b8cce35c0bca688ed18ec93255 (patch)
treeb80b330385a9d796ea99f74b88eaaeee90d057b6
parent8fc21eb80a3c0fbe2ec4984636f86acab228d6b7 (diff)
downloadpyramid-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.txt6
-rw-r--r--docs/api/traversal.rst5
-rw-r--r--repoze/bfg/configure.zcml4
-rw-r--r--repoze/bfg/interfaces.py2
-rw-r--r--repoze/bfg/tests/test_traversal.py123
-rw-r--r--repoze/bfg/tests/test_urldispatch.py8
-rw-r--r--repoze/bfg/traversal.py32
-rw-r--r--repoze/bfg/urldispatch.py3
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, ''