diff options
| author | Chris McDonough <chrism@plope.com> | 2013-08-20 10:16:00 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2013-08-20 10:16:00 -0400 |
| commit | 2d039b9333f99cfe19fed82f441aebde6c4c9be3 (patch) | |
| tree | 97152add5c6fb0d95f56ab668cc95d38db553fea | |
| parent | 70dbf740b5b3500edbcc4b2dff376f43e6250d79 (diff) | |
| parent | d07d167f6dcdc5ef03e8aaca3c953e984a5a5f1a (diff) | |
| download | pyramid-2d039b9333f99cfe19fed82f441aebde6c4c9be3.tar.gz pyramid-2d039b9333f99cfe19fed82f441aebde6c4c9be3.tar.bz2 pyramid-2d039b9333f99cfe19fed82f441aebde6c4c9be3.zip | |
Merge branch 'feature.external-routes'
| -rw-r--r-- | CHANGES.txt | 4 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 35 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 35 | ||||
| -rw-r--r-- | pyramid/tests/test_url.py | 71 |
4 files changed, 144 insertions, 1 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index b805a12a0..1eeb0ce7b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -142,6 +142,10 @@ Features - The ``alchemy`` scaffold tests now provide better coverage. See https://github.com/Pylons/pyramid/pull/1029 +- The ``pyramid.config.Configurator.add_route`` method now supports being + called with an external URL as pattern. See + https://github.com/Pylons/pyramid/issues/611 for more information. + Bug Fixes --------- diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 310c160c0..62eb89348 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -105,6 +105,7 @@ to using the previous combination of ``add_route`` and ``add_view``. .. _route_pattern_syntax: + Route Pattern Syntax ~~~~~~~~~~~~~~~~~~~~ @@ -127,6 +128,10 @@ and: /{foo}/bar/baz +If a pattern is a valid URL it won't be ever matched against an incoming +request. Instead it can be useful for generating external URLs. See +:ref:`External routes <external_route_narr>` for details. + A pattern segment (an individual item between ``/`` characters in the pattern) may either be a literal string (e.g. ``foo``) *or* it may be a replacement marker (e.g. ``{foo}``) or a certain combination of both. A @@ -754,9 +759,39 @@ other non-``name`` and non-``pattern`` arguments to exception to this rule is use of the ``pregenerator`` argument, which is not ignored when ``static`` is ``True``. +:ref:`External routes <external_route_narr>` are implicitly static. + .. versionadded:: 1.1 the ``static`` argument to :meth:`~pyramid.config.Configurator.add_route` +.. _external_route_narr: + + +External Routes +--------------- + +.. versionadded:: 1.5 + +Route patterns that are valid URLs, are treated as external routes. Like +:ref:`static routes <static_route_narr>` they are useful for URL generation +purposes only and are never considered for matching at request time. + +.. code-block:: python + :linenos: + + >>> config = Configurator() + >>> config.add_route('youtube', 'https://youtube.com/watch/{video_id}') + ... + >>> request.route_url('youtube', video_id='oHg5SJYRHA0') + >>> "https://youtube.com/watch/oHg5SJYRHA0" + +Most pattern replacements and calls to +:meth:`pyramid.request.Request.route_url` will work as expected. However, calls +to :meth:`pyramid.request.Request.route_path` against external patterns will +raise an exception, and passing ``_app_url`` to +:meth:`~pyramid.request.Request.route_url` to generate a URL against a route +that has an external pattern will also raise an exception. + .. index:: single: redirecting to slash-appended routes diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index c86e4a2dd..0ed370c94 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,5 +1,6 @@ import warnings +from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, @@ -384,7 +385,39 @@ class RoutesConfiguratorMixin(object): if pattern is None: raise ConfigurationError('"pattern" argument may not be None') - if self.route_prefix: + # check for an external route; an external route is one which is + # is a full url (e.g. 'http://example.com/{id}') + parsed = urlparse.urlparse(pattern) + if parsed.hostname: + pattern = parsed.path + + original_pregenerator = pregenerator + def external_url_pregenerator(request, elements, kw): + if '_app_url' in kw: + raise ValueError( + 'You cannot generate a path to an external route ' + 'pattern via request.route_path nor pass an _app_url ' + 'to request.route_url when generating a URL for an ' + 'external route pattern (pattern was "%s") ' % + (pattern,) + ) + if '_scheme' in kw: + scheme = kw['_scheme'] + elif parsed.scheme: + scheme = parsed.scheme + else: + scheme = request.scheme + kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) + + if original_pregenerator: + elements, kw = original_pregenerator( + request, elements, kw) + return elements, kw + + pregenerator = external_url_pregenerator + static = True + + elif self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') mapper = self.get_routes_mapper() diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index bf1c514c5..6f1ee3bf0 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1027,6 +1027,77 @@ class Test_current_route_path(unittest.TestCase): self.assertEqual(request.elements, ('abc',)) self.assertEqual(request.kw, {'_anchor':'abc'}) +class Test_external_static_url_integration(unittest.TestCase): + + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _makeRequest(self): + from pyramid.request import Request + return Request.blank('/') + + def test_generate_external_url(self): + self.config.add_route('acme', 'https://acme.org/path/{foo}') + request = self._makeRequest() + request.registry = self.config.registry + self.assertEqual( + request.route_url('acme', foo='bar'), + 'https://acme.org/path/bar') + + def test_generate_external_url_without_scheme(self): + self.config.add_route('acme', '//acme.org/path/{foo}') + request = self._makeRequest() + request.registry = self.config.registry + self.assertEqual( + request.route_url('acme', foo='bar'), + 'http://acme.org/path/bar') + + def test_generate_external_url_with_explicit_scheme(self): + self.config.add_route('acme', '//acme.org/path/{foo}') + request = self._makeRequest() + request.registry = self.config.registry + self.assertEqual( + request.route_url('acme', foo='bar', _scheme='https'), + 'https://acme.org/path/bar') + + def test_generate_external_url_with_explicit_app_url(self): + self.config.add_route('acme', 'http://acme.org/path/{foo}') + request = self._makeRequest() + request.registry = self.config.registry + self.assertRaises(ValueError, + request.route_url, 'acme', foo='bar', _app_url='http://fakeme.com') + + def test_generate_external_url_route_path(self): + self.config.add_route('acme', 'https://acme.org/path/{foo}') + request = self._makeRequest() + request.registry = self.config.registry + self.assertRaises(ValueError, request.route_path, 'acme', foo='bar') + + def test_generate_external_url_with_pregenerator(self): + def pregenerator(request, elements, kw): + kw['_query'] = {'q': 'foo'} + return elements, kw + self.config.add_route('acme', 'https://acme.org/path/{foo}', + pregenerator=pregenerator) + request = self._makeRequest() + request.registry = self.config.registry + self.assertEqual( + request.route_url('acme', foo='bar'), + 'https://acme.org/path/bar?q=foo') + + def test_external_url_with_route_prefix(self): + def includeme(config): + config.add_route('acme', '//acme.org/{foo}') + self.config.include(includeme, route_prefix='some_prefix') + request = self._makeRequest() + request.registry = self.config.registry + self.assertEqual( + request.route_url('acme', foo='bar'), + 'http://acme.org/bar') + class DummyContext(object): def __init__(self, next=None): self.next = next |
