diff options
| author | Chris McDonough <chrism@agendaless.com> | 2008-08-06 03:30:40 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2008-08-06 03:30:40 +0000 |
| commit | 39fccbbfbceacaf1b3d5fb6f03a07fbe4d861969 (patch) | |
| tree | b06b38c284eae4e37bd46bc5b1182153cff8b7fa | |
| parent | e17c8d815136218d7dd07e21cf78f4104d773d48 (diff) | |
| download | pyramid-39fccbbfbceacaf1b3d5fb6f03a07fbe4d861969.tar.gz pyramid-39fccbbfbceacaf1b3d5fb6f03a07fbe4d861969.tar.bz2 pyramid-39fccbbfbceacaf1b3d5fb6f03a07fbe4d861969.zip | |
- Small url dispatch overhaul: the ``connect`` method of the
``urldispatch.RoutesMapper`` object now accepts a keyword
parameter named ``context_factory``. If this parameter is
supplied, it must be a callable which returns an instance. This
instance is used as the context for the request when a route is
matched.
- The registration of a RoutesModelTraverser no longer needs to be
performed by the application; it's in the bfg ZCML now.
| -rw-r--r-- | CHANGES.txt | 17 | ||||
| -rw-r--r-- | docs/api/urldispatch.rst | 67 | ||||
| -rw-r--r-- | repoze/bfg/configure.zcml | 6 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 5 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_urldispatch.py | 26 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 58 |
6 files changed, 127 insertions, 52 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index a76a91bc0..eac0c6b48 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,14 +1,27 @@ -After 0.2.6 +0.2.7 - Add a ``request_type`` attribute to the available attributes of a ``bfg:view`` configure.zcml element. This attribute will have a value which is a dotted Python path, pointing at an interface. If the request object implements this interface when the view lookup - is performed, the appropriate view will be called. + is performed, the appropriate view will be called. This is meant + to allow for simple "skinning" of sites based on request type. An + event subscriber should attach the interface to the request on + ingress to support skins. - Remove "template only" views. These were just confusing and were never documented. + - Small url dispatch overhaul: the ``connect`` method of the + ``urldispatch.RoutesMapper`` object now accepts a keyword + parameter named ``context_factory``. If this parameter is + supplied, it must be a callable which returns an instance. This + instance is used as the context for the request when a route is + matched. + + - The registration of a RoutesModelTraverser no longer needs to be + performed by the application; it's in the bfg ZCML now. + 0.2.6 - Add event sends for INewRequest and INewResponse. See the diff --git a/docs/api/urldispatch.rst b/docs/api/urldispatch.rst index 8d4930d34..40b22e5bc 100644 --- a/docs/api/urldispatch.rst +++ b/docs/api/urldispatch.rst @@ -8,42 +8,51 @@ .. autoclass:: RoutesMapper :members: -You can configure the ``RoutesModelTraverser`` into your application's -configure.zcml like so:: +An example of configuring a ``bfg:view`` stanza in ``configure.zcml`` +that maps a context found via :term:`Routes` URL dispatch to a view +function is as follows: - <adapter - factory="repoze.bfg.urldispatch.RoutesModelTraverser" - provides="repoze.bfg.interfaces.ITraverserFactory" - for="repoze.bfg.interfaces.IURLDispatchModel - repoze.bfg.interfaces.IRequest" - /> +.. code-block:: xml + :linenos: -An example of configuring a view that is willing to handle this sort -of dispatch:: + <bfg:view + for="repoze.bfg.interfaces.IRoutesContext" + view=".views.articles_view" + name="articles" + /> - <bfg:view - for="repoze.bfg.interfaces.IURLDispatchModel" - view=".views.articles_view" - name="articles" - /> +All context objects found via Routes URL dispatch will provide the +``IRoutesContext`` interface (attached dynamically). You might then +configure the ``RoutesMapper`` like so: -You might then configure the ``RoutesMapper`` like so:: +.. code-block:: python + :linenos: - def fallback_get_root(environ): - return {} # the graph traversal root is empty in this example + def fallback_get_root(environ): + return {} # the graph traversal root is empty in this example - get_root = RoutesMapper(fallback_get_root) - get_root.connect('archives/:article', controller='articles') + class Article(object): + def __init__(self, **kw): + self.__dict__update(kw) - import myapp - from repoze.bfg.router import make_app + get_root = RoutesMapper(fallback_get_root) + get_root.connect('archives/:article', controller='articles', + context_factory=Article) - app = make_app(get_root, myapp) + import myapp + from repoze.bfg.router import make_app + + app = make_app(get_root, myapp) + +The effect of this configuration: when this :mod:`repoze.bfg` +application runs, if any URL matches the pattern +``archives/:article``, the ``.views.articles_view`` view will be +called with its :term:`context` as a instance of the ``Article`` +class. The ``Article`` instance will have attributes matching the +keys and values in the Routes routing dictionary associated with the +request. -At this point, if any URL matches the pattern ``archives/:article``, -the ``.views.articles_view`` view will be called with its context as a -only-the-fly-generated-model with attributes matching the keys and -values in the Routes routing dictionary associated with the request. In this case in particular, when a user visits -``/archives/something``, the model will have an ``article`` attribute -with the value of ``something``. +``/archives/something``, the context will be an instance of the +Article class and it will have an ``article`` attribute with the value +of ``something``. diff --git a/repoze/bfg/configure.zcml b/repoze/bfg/configure.zcml index e57558cd2..78dce0b83 100644 --- a/repoze/bfg/configure.zcml +++ b/repoze/bfg/configure.zcml @@ -9,6 +9,12 @@ for="* .interfaces.IRequest" /> + <adapter + factory=".urldispatch.RoutesModelTraverser" + provides=".interfaces.ITraverserFactory" + for=".interfaces.IRoutesContext .interfaces.IRequest" + /> + <include file="meta.zcml" /> </configure> diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 4b2b626e6..4d557b098 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -66,8 +66,9 @@ class IViewPermissionFactory(Interface): def __call__(context, request): """ Return an IViewPermission """ -class IURLDispatchModel(Interface): - """ A model that is created as a result of URL dispatching """ +class IRoutesContext(Interface): + """ A context (model instance) that is created as a result of URL + dispatching""" class INewRequest(Interface): """ An event type that is emitted whenever repoze.bfg begins to diff --git a/repoze/bfg/tests/test_urldispatch.py b/repoze/bfg/tests/test_urldispatch.py index a3e156861..0b69b5bec 100644 --- a/repoze/bfg/tests/test_urldispatch.py +++ b/repoze/bfg/tests/test_urldispatch.py @@ -30,10 +30,36 @@ class RoutesMapperTests(unittest.TestCase): mapper.connect('archives/:action/:article', controller='foo') environ = self._getEnviron(PATH_INFO='/archives/action1/article1') result = mapper(environ) + from repoze.bfg.interfaces import IRoutesContext + self.failUnless(IRoutesContext.providedBy(result)) self.assertEqual(result.controller, 'foo') self.assertEqual(result.action, 'action1') self.assertEqual(result.article, 'article1') + def test_routes_mapper_custom_context_factory(self): + marker = () + get_root = make_get_root(marker) + mapper = self._makeOne(get_root) + from zope.interface import implements, Interface + class IDummy(Interface): + pass + class Dummy(object): + implements(IDummy) + def __init__(self, **kw): + self.__dict__.update(kw) + mapper.connect('archives/:action/:article', controller='foo', + context_factory=Dummy) + 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') + from repoze.bfg.interfaces import IRoutesContext + self.failUnless(IRoutesContext.providedBy(result)) + self.failUnless(isinstance(result, Dummy)) + self.failUnless(IDummy.providedBy(result)) + self.failIf(hasattr(result, 'context_factory')) + def test_url_for(self): marker = () get_root = make_get_root(marker) diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py index 9bdb1e5fa..434a2aa57 100644 --- a/repoze/bfg/urldispatch.py +++ b/repoze/bfg/urldispatch.py @@ -1,33 +1,34 @@ from zope.interface import implements from zope.interface import classProvides +from zope.interface import alsoProvides from routes import Mapper from routes import request_config -from repoze.bfg.interfaces import IURLDispatchModel +from repoze.bfg.interfaces import IRoutesContext from repoze.bfg.interfaces import ITraverserFactory from repoze.bfg.interfaces import ITraverser -class RoutesModel(object): - implements(IURLDispatchModel) +_marker = () + +class RoutesContext(object): 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 + """ 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.""" + interface ``repoze.bfg.interfaces.IRoutesContext`` 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, @@ -42,32 +43,51 @@ class RoutesMapper(object): path = environ.get('PATH_INFO', '/') args = self.mapper.match(path) if args: + context_factory = args.get('context_factory', _marker) + if context_factory is _marker: + context_factory = RoutesContext + else: + args = args.copy() + del args['context_factory'] 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 + context = context_factory(**args) + alsoProvides(context, IRoutesContext) + return context + # 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""" + *Mapper* object. One difference exists: if the + ``context_factory`` is passed in with a value as a keyword + argument, this callable will be called when a model object + representing the ``context``` for the request needs to be + constructed. It will be called with the (all-keyword) + arguments supplied by the Routes mapper's ``match`` method for + this route, and should return an instance of a class. If + ``context_factory`` is not supplied in this way for a route, a + default context factory (the ``RoutesContext`` class) will be + used. The interface ``repoze.bfg.interfaces.IRoutesContext`` + will always be tacked on to the context instance in addition + to whatever interfaces the context instance already supplies.""" self.mapper.connect(*arg, **kw) class RoutesModelTraverser(object): classProvides(ITraverserFactory) implements(ITraverser) - def __init__(self, model, request): - self.model = model + def __init__(self, context, request): + self.context = context self.request = request def __call__(self, environ): - return self.model, self.model.controller, '' + return self.context, self.context.controller, '' |
