diff options
| -rw-r--r-- | CHANGES.txt | 10 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 1 | ||||
| -rw-r--r-- | docs/glossary.rst | 9 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 17 | ||||
| -rw-r--r-- | repoze/bfg/interfaces.py | 28 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 5 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_url.py | 40 | ||||
| -rw-r--r-- | repoze/bfg/url.py | 13 | ||||
| -rw-r--r-- | repoze/bfg/urldispatch.py | 9 |
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 |
