summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2008-08-06 03:30:40 +0000
committerChris McDonough <chrism@agendaless.com>2008-08-06 03:30:40 +0000
commit39fccbbfbceacaf1b3d5fb6f03a07fbe4d861969 (patch)
treeb06b38c284eae4e37bd46bc5b1182153cff8b7fa
parente17c8d815136218d7dd07e21cf78f4104d773d48 (diff)
downloadpyramid-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.txt17
-rw-r--r--docs/api/urldispatch.rst67
-rw-r--r--repoze/bfg/configure.zcml6
-rw-r--r--repoze/bfg/interfaces.py5
-rw-r--r--repoze/bfg/tests/test_urldispatch.py26
-rw-r--r--repoze/bfg/urldispatch.py58
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, ''