diff options
| author | Chris McDonough <chrism@agendaless.com> | 2008-07-22 11:02:28 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2008-07-22 11:02:28 +0000 |
| commit | 8392d7094c96f777f6d1d5f0c62051702191d106 (patch) | |
| tree | 6fd39e2c10fc33a5b2be08e7467c9e6ad0887b78 | |
| parent | b584143b2c408b979c340939f48b8a4ffe725e62 (diff) | |
| download | pyramid-8392d7094c96f777f6d1d5f0c62051702191d106.tar.gz pyramid-8392d7094c96f777f6d1d5f0c62051702191d106.tar.bz2 pyramid-8392d7094c96f777f6d1d5f0c62051702191d106.zip | |
Add url-based dispatch.
| -rw-r--r-- | CHANGES.txt | 4 | ||||
| -rw-r--r-- | docs/api/urldispatch.rst | 30 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 3 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_traversal.py | 4 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 85 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 75 | ||||
| -rw-r--r-- | setup.py | 2 |
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, '' + + + + + @@ -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', ], |
