summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt10
-rw-r--r--docs/api/interfaces.rst1
-rw-r--r--docs/glossary.rst9
-rw-r--r--repoze/bfg/configuration.py17
-rw-r--r--repoze/bfg/interfaces.py28
-rw-r--r--repoze/bfg/tests/test_configuration.py5
-rw-r--r--repoze/bfg/tests/test_url.py40
-rw-r--r--repoze/bfg/url.py13
-rw-r--r--repoze/bfg/urldispatch.py9
9 files changed, 116 insertions, 16 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 748080683..5bf4a8ef8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -45,6 +45,14 @@ Features
is caught by the router, even if an exception view could not be
found.
+- The ``add_route`` method of a Configurator now accepts a
+ ``pregenerator`` argument. The pregenerator for the resulting route
+ is called by ``route_url`` in order to adjust the set of arguments
+ passed to it by the user for special purposes, such as Pylons
+ 'subdomain' support. It will influence the URL returned by
+ ``route_url``. See the ``repoze.bfg.interfaces.IRoutePregenerator``
+ interface for more information.
+
Backwards Incompatibilities
---------------------------
@@ -110,6 +118,8 @@ Documentation
- The "Request Processing" narrative chapter has been updated to note
finished and response callback steps.
+- New interface in interfaces API documentation: ``IRoutePregenerator``.
+
1.3a12 (2010-09-08)
===================
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index 974ab2ae9..be2de2c7f 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -23,4 +23,5 @@ Other Interfaces
.. autointerface:: IRoute
+ .. autointerface:: IRoutePregenerator
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 158af2230..e3b46c1f6 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -784,3 +784,12 @@ Glossary
A user-defined callback executed by the :term:`router`
unconditionally at the very end of request processing . See
:ref:`using_finished_callbacks`.
+
+ pregenerator
+ A pregenerator is a function associated by a developer with a
+ :term:`route`. It is called by :func:`repoze.bfg.url.route_url`
+ in order to adjust the set of arguments passed to it by the user
+ for special purposes. It will influence the URL returned by
+ ``route_url``. See
+ :class:`repoze.bfg.interfaces.IRoutePregenerator` for more
+ information.
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index e4ac824ef..f0c26cc3b 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -1072,6 +1072,7 @@ class Configurator(object):
view_attr=None,
use_global_views=False,
path=None,
+ pregenerator=None,
_info=u''):
""" Add a :term:`route configuration` to the current
configuration state, as well as possibly a :term:`view
@@ -1145,6 +1146,19 @@ class Configurator(object):
.. note:: This feature is new as of :mod:`repoze.bfg` 1.3.
+ pregenerator
+
+ This option should be a callable object that implements the
+ :class:`repoze.bfg.interfaces.IRoutePregenerator`
+ interface. A :term:`pregenerator` is a callable called by
+ the :mod:`repoze.bfg.url.route_url` function to augment or
+ replace the arguments it is passed when generating a URL
+ for the route. This is a feature not often used directly
+ by applications, it is meant to be hooked by frameworks
+ that use :mod:`repoze.bfg` as a base.
+
+ .. note:: This feature is new as of :mod:`repoze.bfg` 1.3.
+
Predicate Arguments
pattern
@@ -1391,7 +1405,8 @@ class Configurator(object):
pattern = path
if pattern is None:
raise ConfigurationError('"pattern" argument may not be None')
- return mapper.connect(name, pattern, factory, predicates=predicates)
+ return mapper.connect(name, pattern, factory, predicates=predicates,
+ pregenerator=pregenerator)
def get_routes_mapper(self):
""" Return the :term:`routes mapper` object associated with
diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py
index 136680202..d67fd2dfb 100644
--- a/repoze/bfg/interfaces.py
+++ b/repoze/bfg/interfaces.py
@@ -255,6 +255,31 @@ class IDebugLogger(Interface):
ILogger = IDebugLogger # b/c
+class IRoutePregenerator(Interface):
+ def __call__(request, elements, kw):
+ """ A pregenerator is a function associated by a developer
+ with a :term:`route`. The pregenerator for a route is called
+ by :func:`repoze.bfg.url.route_url` in order to adjust the set
+ of arguments passed to it by the user for special purposes,
+ such as Pylons 'subdomain' support. It will influence the URL
+ returned by ``route_url``.
+
+ A pregenerator should return a two-tuple of ``(elements, kw)``
+ after examining the originals passed to this function, which
+ are the arguments ``(request, elements, kw)``. The simplest
+ pregenerator is::
+
+ def pregenerator(request, elements, kw):
+ return elements, kw
+
+ You can employ a pregenerator by passing a ``pregenerator``
+ argument to the
+ :meth:`repoze.bfg.configuration.Configurator.add_route`
+ function.
+
+ .. note:: This interface is new as of :mod:`repoze.bfg` 1.3.
+ """
+
class IRoute(Interface):
""" Interface representing the type of object returned from
``IRoutesMapper.get_route``"""
@@ -267,6 +292,9 @@ class IRoute(Interface):
'A sequence of :term:`route predicate` objects used to '
'determine if a request matches this route or not or not after '
'basic pattern matching has been completed.')
+ pregenerator = Attribute('This attribute should either be ``None`` or '
+ 'a callable object implementing the '
+ '``IRoutePregenerator`` interface')
def match(path):
"""
If the ``path`` passed to this function can be matched by the
diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py
index b095c5c4c..47649d6ba 100644
--- a/repoze/bfg/tests/test_configuration.py
+++ b/repoze/bfg/tests/test_configuration.py
@@ -2024,6 +2024,11 @@ class ConfiguratorTests(unittest.TestCase):
config = self._makeOne()
self.assertRaises(ConfigurationError, config.add_route, 'name')
+ def test_add_route_with_pregenerator(self):
+ config = self._makeOne()
+ route = config.add_route('name', 'pattern', pregenerator='123')
+ self.assertEqual(route.pregenerator, '123')
+
def test__override_not_yet_registered(self):
from repoze.bfg.interfaces import IPackageOverrides
package = DummyPackage('package')
diff --git a/repoze/bfg/tests/test_url.py b/repoze/bfg/tests/test_url.py
index e38c6a8eb..1b6f7814c 100644
--- a/repoze/bfg/tests/test_url.py
+++ b/repoze/bfg/tests/test_url.py
@@ -150,7 +150,7 @@ class TestRouteUrl(unittest.TestCase):
def test_with_elements(self):
from repoze.bfg.interfaces import IRoutesMapper
request = _makeRequest()
- mapper = DummyRoutesMapper(result='/1/2/3')
+ mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
request.registry.registerUtility(mapper, IRoutesMapper)
result = self._callFUT('flub', request, 'extra1', 'extra2',
a=1, b=2, c=3, _query={'a':1},
@@ -161,7 +161,7 @@ class TestRouteUrl(unittest.TestCase):
def test_no_elements(self):
from repoze.bfg.interfaces import IRoutesMapper
request = _makeRequest()
- mapper = DummyRoutesMapper(result='/1/2/3')
+ mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
request.registry.registerUtility(mapper, IRoutesMapper)
result = self._callFUT('flub', request, a=1, b=2, c=3, _query={'a':1},
_anchor=u"foo")
@@ -178,23 +178,37 @@ class TestRouteUrl(unittest.TestCase):
def test_generate_doesnt_receive_query_or_anchor(self):
from repoze.bfg.interfaces import IRoutesMapper
- mapper = DummyRoutesMapper(result='')
+ route = DummyRoute(result='')
+ mapper = DummyRoutesMapper(route=route)
from zope.component import getSiteManager
sm = getSiteManager()
sm.registerUtility(mapper, IRoutesMapper)
request = DummyRequest()
result = self._callFUT('flub', request, _query=dict(name='some_name'))
- self.assertEqual(mapper.kw, {}) # shouldnt have anchor/query
+ self.assertEqual(route.kw, {}) # shouldnt have anchor/query
self.assertEqual(result, 'http://example.com:5432?name=some_name')
def test_with_app_url(self):
from repoze.bfg.interfaces import IRoutesMapper
request = _makeRequest()
- mapper = DummyRoutesMapper(result='/1/2/3')
+ mapper = DummyRoutesMapper(route=DummyRoute(result='/1/2/3'))
request.registry.registerUtility(mapper, IRoutesMapper)
result = self._callFUT('flub', request, _app_url='http://example2.com')
self.assertEqual(result, 'http://example2.com/1/2/3')
+ def test_with_pregenerator(self):
+ from repoze.bfg.interfaces import IRoutesMapper
+ request = _makeRequest()
+ route = DummyRoute(result='/1/2/3')
+ def pregenerator(request, elements, kw):
+ return ('a',), {'_app_url':'http://example2.com'}
+ route.pregenerator = pregenerator
+ mapper = DummyRoutesMapper(route=route)
+ request.registry.registerUtility(mapper, IRoutesMapper)
+ result = self._callFUT('flub', request)
+ self.assertEqual(result, 'http://example2.com/1/2/3/a')
+ self.assertEqual(route.kw, {}) # shouldnt have anchor/query
+
class TestStaticUrl(unittest.TestCase):
def setUp(self):
cleanUp()
@@ -259,14 +273,19 @@ class DummyRequest:
class DummyRoutesMapper:
raise_exc = None
- def __init__(self, result='/1/2/3', raise_exc=False, routes=()):
+ def __init__(self, route=None, raise_exc=False):
+ self.route = route
+
+ def get_route(self, route_name):
+ return self.route
+
+class DummyRoute:
+ pregenerator = None
+ def __init__(self, result='/1/2/3'):
self.result = result
- self.routes = routes
- def generate(self, *route_args, **kw):
+ def generate(self, kw):
self.kw = kw
- if self.raise_exc:
- raise self.raise_exc
return self.result
def _makeRequest(environ=None):
@@ -274,7 +293,6 @@ def _makeRequest(environ=None):
request = DummyRequest(environ)
request.registry = Registry()
return request
-
class DummyStaticURLInfo:
def __init__(self, result):
diff --git a/repoze/bfg/url.py b/repoze/bfg/url.py
index 920e82d53..4f501dd7a 100644
--- a/repoze/bfg/url.py
+++ b/repoze/bfg/url.py
@@ -101,12 +101,23 @@ def route_url(route_name, request, *elements, **kw):
This function raises a :exc:`KeyError` if the URL cannot be
generated due to missing replacement names. Extra replacement
names are ignored.
+
+ If the route object which matches the ``route_name`` argument has
+ a :term:`pregenerator`, the ``*elements`` and ``**kw`` arguments
+ arguments passed to this function might be augmented or changed.
"""
try:
reg = request.registry
except AttributeError:
reg = get_current_registry() # b/c
mapper = reg.getUtility(IRoutesMapper)
+ route = mapper.get_route(route_name)
+
+ if route is None:
+ raise KeyError('No such route named %s' % route_name)
+
+ if route.pregenerator:
+ elements, kw = route.pregenerator(request, elements, kw)
anchor = ''
qs = ''
@@ -124,7 +135,7 @@ def route_url(route_name, request, *elements, **kw):
if '_app_url' in kw:
app_url = kw.pop('_app_url')
- path = mapper.generate(route_name, kw) # raises KeyError if generate fails
+ path = route.generate(kw) # raises KeyError if generate fails
if elements:
suffix = _join_elements(elements)
diff --git a/repoze/bfg/urldispatch.py b/repoze/bfg/urldispatch.py
index f9def62df..8aca2c421 100644
--- a/repoze/bfg/urldispatch.py
+++ b/repoze/bfg/urldispatch.py
@@ -16,13 +16,15 @@ _marker = object()
class Route(object):
implements(IRoute)
- def __init__(self, name, pattern, factory=None, predicates=()):
+ def __init__(self, name, pattern, factory=None, predicates=(),
+ pregenerator=None):
self.pattern = pattern
self.path = pattern # indefinite b/w compat, not in interface
self.match, self.generate = _compile_route(pattern)
self.name = name
self.factory = factory
self.predicates = predicates
+ self.pregenerator = pregenerator
class RoutesMapper(object):
implements(IRoutesMapper)
@@ -39,11 +41,12 @@ class RoutesMapper(object):
def get_route(self, name):
return self.routes.get(name)
- def connect(self, name, pattern, factory=None, predicates=()):
+ def connect(self, name, pattern, factory=None, predicates=(),
+ pregenerator=None):
if name in self.routes:
oldroute = self.routes[name]
self.routelist.remove(oldroute)
- route = Route(name, pattern, factory, predicates)
+ route = Route(name, pattern, factory, predicates, pregenerator)
self.routelist.append(route)
self.routes[name] = route
return route