summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-07-22 11:02:28 +0000
committerChris McDonough <chrism@agendaless.com>2008-07-22 11:02:28 +0000
commit8392d7094c96f777f6d1d5f0c62051702191d106 (patch)
tree6fd39e2c10fc33a5b2be08e7467c9e6ad0887b78
parentb584143b2c408b979c340939f48b8a4ffe725e62 (diff)
downloadpyramid-8392d7094c96f777f6d1d5f0c62051702191d106.tar.gz
pyramid-8392d7094c96f777f6d1d5f0c62051702191d106.tar.bz2
pyramid-8392d7094c96f777f6d1d5f0c62051702191d106.zip
Add url-based dispatch.
-rw-r--r--CHANGES.txt4
-rw-r--r--docs/api/urldispatch.rst30
-rw-r--r--docs/index.rst1
-rw-r--r--repoze/bfg/interfaces.py3
-rw-r--r--repoze/bfg/tests/test_traversal.py4
-rw-r--r--repoze/bfg/tests/test_urldispatch.py85
-rw-r--r--repoze/bfg/urldispatch.py75
-rw-r--r--setup.py2
8 files changed, 202 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index fa65c0851..4272aafaa 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,7 @@
+After 0.2.3
+
+ - Added url-based dispatch.
+
0.2.3
- Add API functions for authenticated_userid and effective_principals.
diff --git a/docs/api/urldispatch.rst b/docs/api/urldispatch.rst
new file mode 100644
index 000000000..81b28cebb
--- /dev/null
+++ b/docs/api/urldispatch.rst
@@ -0,0 +1,30 @@
+.. _urldispatch_module:
+
+:mod:`repoze.bfg.urldispatch`
+=============================
+
+.. automodule:: repoze.bfg.urldispatch
+
+ .. autoclass:: RoutesMapper
+ :members:
+
+You can configure the ``RoutesModelTraverser`` into your application's
+configure.zcml like so::
+
+ <adapter
+ factory="repoze.bfg.urldispatch.RoutesModelTraverser"
+ provides=".interfaces.ITraverserFactory"
+ for="repoze.bfg.interfaces.IURLDispatchModel repoze.bfg.interfaces.IRequest"
+ />
+
+An example of configuring a view that is willing to handle this sort
+of dispatch::
+
+ <bfg:view
+ for="repoze.bfg.interfaces.IURLDispatchModel"
+ view=".views.url_dispatch_view"
+ name="url_dispatch_controller"
+ />
+
+This view will be called with its context as a model with attributes
+matching the Routes routing dictionary associated with the request.
diff --git a/docs/index.rst b/docs/index.rst
index 86b524120..9e50b2a09 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -38,6 +38,7 @@ and run a web application.
api/security
api/template
api/traversal
+ api/urldispatch
api/wsgi
Indices and tables
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index a239793ed..489fe0559 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -66,3 +66,6 @@ class IViewPermissionFactory(Interface):
def __call__(context, request):
""" Return an IViewPermission """
+class IURLDispatchModel(Interface):
+ """ A model that is created as a result of URL dispatching """
+
diff --git a/repoze/bfg/tests/test_traversal.py b/repoze/bfg/tests/test_traversal.py
index 590a06b81..5cc7551c0 100644
--- a/repoze/bfg/tests/test_traversal.py
+++ b/repoze/bfg/tests/test_traversal.py
@@ -48,12 +48,12 @@ class ModelGraphTraverserTests(unittest.TestCase, PlacelessSetup):
environ.update(kw)
return environ
- def test_class_conforms_to_IPublishTraverser(self):
+ def test_class_conforms_to_ITraverser(self):
from zope.interface.verify import verifyClass
from repoze.bfg.interfaces import ITraverser
verifyClass(ITraverser, self._getTargetClass())
- def test_instance_conforms_to_IPublishTraverser(self):
+ def test_instance_conforms_to_ITraverser(self):
from zope.interface.verify import verifyObject
from repoze.bfg.interfaces import ITraverser
context = DummyContext()
diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py
new file mode 100644
index 000000000..a3e156861
--- /dev/null
+++ b/repoze/bfg/tests/test_urldispatch.py
@@ -0,0 +1,85 @@
+import unittest
+
+class RoutesMapperTests(unittest.TestCase):
+ def _getEnviron(self, **kw):
+ environ = {'SERVER_NAME':'localhost',
+ 'wsgi.url_scheme':'http'}
+ environ.update(kw)
+ return environ
+
+ def _getTargetClass(self):
+ from repoze.bfg.urldispatch import RoutesMapper
+ return RoutesMapper
+
+ def _makeOne(self, get_root):
+ klass = self._getTargetClass()
+ return klass(get_root)
+
+ def test_routes_mapper_no_route_matches(self):
+ marker = ()
+ get_root = make_get_root(marker)
+ mapper = self._makeOne(get_root)
+ environ = self._getEnviron(PATH_INFO='/')
+ result = mapper(environ)
+ self.assertEqual(result, marker)
+
+ def test_routes_mapper_route_matches(self):
+ marker = ()
+ get_root = make_get_root(marker)
+ mapper = self._makeOne(get_root)
+ mapper.connect('archives/:action/:article', controller='foo')
+ environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
+ result = mapper(environ)
+ self.assertEqual(result.controller, 'foo')
+ self.assertEqual(result.action, 'action1')
+ self.assertEqual(result.article, 'article1')
+
+ def test_url_for(self):
+ marker = ()
+ get_root = make_get_root(marker)
+ mapper = self._makeOne(get_root)
+ mapper.connect('archives/:action/:article', controller='foo')
+ environ = self._getEnviron(PATH_INFO='/archives/action1/article1')
+ result = mapper(environ)
+ from routes import url_for
+ result = url_for(controller='foo', action='action2', article='article2')
+ self.assertEqual(result, '/archives/action2/article2')
+
+class TestRoutesModelTraverser(unittest.TestCase):
+ def _getTargetClass(self):
+ from repoze.bfg.urldispatch import RoutesModelTraverser
+ return RoutesModelTraverser
+
+ def _makeOne(self, model, request):
+ klass = self._getTargetClass()
+ return klass(model, request)
+
+ def test_class_conforms_to_ITraverser(self):
+ from zope.interface.verify import verifyClass
+ from repoze.bfg.interfaces import ITraverser
+ verifyClass(ITraverser, self._getTargetClass())
+
+ 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))
+
+ def test_call(self):
+ model = DummyModel()
+ traverser = self._makeOne(model, None)
+ result = traverser({})
+ self.assertEqual(result[0], model)
+ self.assertEqual(result[1], 'controller')
+ self.assertEqual(result[2], '')
+
+class DummyModel:
+ controller = 'controller'
+
+def make_get_root(result):
+ def dummy_get_root(environ):
+ return result
+ return dummy_get_root
+
+
+
+
diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py
new file mode 100644
index 000000000..9bdb1e5fa
--- /dev/null
+++ b/repoze/bfg/urldispatch.py
@@ -0,0 +1,75 @@
+from zope.interface import implements
+from zope.interface import classProvides
+
+from routes import Mapper
+from routes import request_config
+
+from repoze.bfg.interfaces import IURLDispatchModel
+from repoze.bfg.interfaces import ITraverserFactory
+from repoze.bfg.interfaces import ITraverser
+
+class RoutesModel(object):
+ implements(IURLDispatchModel)
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+class RoutesMapper(object):
+ """ The RoutesMapper is a wrapper for the ``get_root`` callable
+ passed in to the repoze.bfg Router at initialization time. When
+ it is instantiated, it wraps the get_root of an application in
+ such a way that the `Routes
+ <http://routes.groovie.org/index.html>`_ engine has the 'first
+ crack' at resolving the current request URL to a repoze.bfg view.
+ If the ``RoutesModelTraverser`` is configured in your
+ application's configure.zcml, any view that claims it is 'for' the
+ interface ``repoze.bfg.interfaces.IURLDispatchModel`` will be
+ called if its *name* matches the Routes 'controller' name for the
+ match. It will be passed a context object that has attributes
+ that match the Routes match arguments dictionary keys. If no
+ Routes route matches the current request, the 'fallback' get_root
+ is called."""
+ def __init__(self, get_root):
+ self.get_root = get_root
+ self.mapper = Mapper(controller_scan=None, directory=None,
+ explicit=True, always_scan=False)
+ self.mapper.explicit = True
+ self._regs_created = False
+
+ def __call__(self, environ):
+ if not self._regs_created:
+ self.mapper.create_regs([])
+ self._regs_created = True
+ path = environ.get('PATH_INFO', '/')
+ args = self.mapper.match(path)
+ if args:
+ config = request_config()
+ config.mapper = self.mapper
+ config.mapper_dict = args
+ config.host = environ.get('HTTP_HOST', environ['SERVER_NAME'])
+ config.protocol = environ['wsgi.url_scheme']
+ config.redirect = None
+ model = RoutesModel(**args)
+ return model
+ # fall back to original get_root
+ return self.get_root(environ)
+
+ def connect(self, *arg, **kw):
+ """ Add a route to the Routes mapper associated with this
+ request. This method accepts the same arguments as a Routes
+ *Mapper* object"""
+ self.mapper.connect(*arg, **kw)
+
+class RoutesModelTraverser(object):
+ classProvides(ITraverserFactory)
+ implements(ITraverser)
+ def __init__(self, model, request):
+ self.model = model
+ self.request = request
+
+ def __call__(self, environ):
+ return self.model, self.model.controller, ''
+
+
+
+
+
diff --git a/setup.py b/setup.py
index 16b8ca413..210ac66e3 100644
--- a/setup.py
+++ b/setup.py
@@ -57,6 +57,7 @@ setup(name='repoze.bfg',
'z3c.pt',
'FormEncode',
'PasteScript',
+ 'Routes',
],
tests_require=[
'zope.interface',
@@ -68,6 +69,7 @@ setup(name='repoze.bfg',
'z3c.pt',
'FormEncode',
'PasteScript',
+ 'Routes',
'Sphinx',
'docutils',
],