From 33b638478a5d4e9d3190ec0130902091817601a0 Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 12:24:23 +0200 Subject: assemble a custom pregenerator for static external urls we split the pattern via urlparse and pass the parts into the pregenerator such that route_url will behave as expected, mainly by assembling an appropriate value for ``_app_url`` --- pyramid/config/routes.py | 18 ++++++++++++++++++ pyramid/tests/test_url.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index c86e4a2dd..f4f9ac888 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,4 +1,5 @@ import warnings +from urlparse import urlparse from pyramid.interfaces import ( IRequest, @@ -387,6 +388,23 @@ class RoutesConfiguratorMixin(object): if self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') + if pregenerator is None: + parsed = urlparse(pattern) + if parsed.hostname: + pattern = parsed.path + + def external_url_pregenerator(request, elements, kw): + if '_scheme' in kw and parsed.scheme != kw['_scheme']: + scheme = kw['_scheme'] + elif parsed.scheme: + scheme = parsed.scheme + else: + scheme = request.scheme + kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) + return elements, kw + + pregenerator = external_url_pregenerator + mapper = self.get_routes_mapper() introspectables = [] diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index bf1c514c5..c7b8cfca1 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1027,6 +1027,41 @@ 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') + + class DummyContext(object): def __init__(self, next=None): self.next = next -- cgit v1.2.3 From edbc1dd142564a654fdbc8f7e13ddf496bc73950 Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 15:06:22 +0200 Subject: if `_app_url` is given it takes precedence --- pyramid/config/routes.py | 15 ++++++++------- pyramid/tests/test_url.py | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index f4f9ac888..642c72701 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -394,13 +394,14 @@ class RoutesConfiguratorMixin(object): pattern = parsed.path def external_url_pregenerator(request, elements, kw): - if '_scheme' in kw and parsed.scheme != kw['_scheme']: - scheme = kw['_scheme'] - elif parsed.scheme: - scheme = parsed.scheme - else: - scheme = request.scheme - kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) + if not '_app_url' in kw: + if '_scheme' in kw and parsed.scheme != kw['_scheme']: + scheme = kw['_scheme'] + elif parsed.scheme: + scheme = parsed.scheme + else: + scheme = request.scheme + kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) return elements, kw pregenerator = external_url_pregenerator diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index c7b8cfca1..197dfb3eb 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1062,6 +1062,14 @@ class Test_external_static_url_integration(unittest.TestCase): '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.assertEqual(request.route_url('acme', foo='bar', _app_url='http://fakeme.com'), + 'http://fakeme.com/path/bar') + + class DummyContext(object): def __init__(self, next=None): self.next = next -- cgit v1.2.3 From df413c8308a99612e247469300aeb28544041f82 Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 15:16:16 +0200 Subject: document the behavior for ``route_path`` --- pyramid/tests/test_url.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 197dfb3eb..0e006f447 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1070,6 +1070,14 @@ class Test_external_static_url_integration(unittest.TestCase): 'http://fakeme.com/path/bar') + 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.assertEqual(request.route_path('acme', foo='bar'), + '/path/bar') + + class DummyContext(object): def __init__(self, next=None): self.next = next -- cgit v1.2.3 From 8a8eff5c12e161edf3fcf6942e784ef8126d696d Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 15:48:38 +0200 Subject: update changelog --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 48efad6d4..83bf0bb64 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -139,6 +139,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 --------- -- cgit v1.2.3 From 8866e256bf640817692cb16ec0ccc4c642a13b7d Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Sat, 17 Aug 2013 16:54:28 +0200 Subject: Document the external URL feature. --- docs/narr/urldispatch.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 310c160c0..34014c9c5 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 ~~~~~~~~~~~~~~~~~~~~ @@ -754,9 +755,43 @@ 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 ` are implicitely 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 ` 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" + +All pattern replacements and calls to +:meth:`pyramid.request.Request.route_url` will work as expected. Note that +:meth:`pyramid.request.Request.route_path` will also just return the external +URLs path part. + +.. note:: + + The external URL feature is implemented with a :term:`pregenerator` so you + cannot use both with the same route. + + .. index:: single: redirecting to slash-appended routes -- cgit v1.2.3 From a58474e1ffcf403a29d63adca34222c6234f2401 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Sat, 17 Aug 2013 16:55:25 +0200 Subject: Oops, indent one level deeper. --- pyramid/config/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 642c72701..9f91907dd 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -404,7 +404,7 @@ class RoutesConfiguratorMixin(object): kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) return elements, kw - pregenerator = external_url_pregenerator + pregenerator = external_url_pregenerator mapper = self.get_routes_mapper() -- cgit v1.2.3 From 58d507156b30ac194c99527247078cc5a25cb142 Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 17:09:34 +0200 Subject: typo --- docs/narr/urldispatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 34014c9c5..045af42eb 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -755,7 +755,7 @@ 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 ` are implicitely static. +:ref:`External routes ` are implicitly static. .. versionadded:: 1.1 the ``static`` argument to :meth:`~pyramid.config.Configurator.add_route` -- cgit v1.2.3 From 3bae69780cf5f7d8ff772adc085f3e064853f1a0 Mon Sep 17 00:00:00 2001 From: Tom Lazar Date: Sat, 17 Aug 2013 17:09:36 +0200 Subject: reference the static url route feature --- docs/narr/urldispatch.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 045af42eb..8f03b1080 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -128,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 ` 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 -- cgit v1.2.3 From a6aa55360986b008b086551ea7414d3bf6e054e9 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 19 Aug 2013 10:26:52 +0200 Subject: Fix indentation. --- pyramid/config/routes.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 9f91907dd..0e11428db 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -389,22 +389,23 @@ class RoutesConfiguratorMixin(object): pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') if pregenerator is None: - parsed = urlparse(pattern) - if parsed.hostname: - pattern = parsed.path - - def external_url_pregenerator(request, elements, kw): - if not '_app_url' in kw: - if '_scheme' in kw and parsed.scheme != kw['_scheme']: - scheme = kw['_scheme'] - elif parsed.scheme: - scheme = parsed.scheme - else: - scheme = request.scheme - kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) - return elements, kw - - pregenerator = external_url_pregenerator + parsed = urlparse(pattern) + if parsed.hostname: + pattern = parsed.path + + def external_url_pregenerator(request, elements, kw): + if not '_app_url' in kw: + if '_scheme' in kw and parsed.scheme != kw['_scheme']: + scheme = kw['_scheme'] + elif parsed.scheme: + scheme = parsed.scheme + else: + scheme = request.scheme + kw['_app_url'] = '{0}://{1}'.format( + scheme, parsed.netloc) + return elements, kw + + pregenerator = external_url_pregenerator mapper = self.get_routes_mapper() -- cgit v1.2.3 From 84367e57afc0d5538e02f670834809933d9cab26 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Aug 2013 22:56:54 -0500 Subject: allow pregenerator and route_prefix with external routes --- docs/narr/urldispatch.rst | 5 ----- pyramid/config/routes.py | 47 ++++++++++++++++++++++++++--------------------- pyramid/tests/test_url.py | 41 +++++++++++++++++++++++++++++++---------- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 8f03b1080..f3513624e 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -790,11 +790,6 @@ All pattern replacements and calls to :meth:`pyramid.request.Request.route_path` will also just return the external URLs path part. -.. note:: - - The external URL feature is implemented with a :term:`pregenerator` so you - cannot use both with the same route. - .. index:: single: redirecting to slash-appended routes diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 0e11428db..af7ddd6f3 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,6 +1,6 @@ import warnings -from urlparse import urlparse +from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, @@ -385,28 +385,33 @@ class RoutesConfiguratorMixin(object): if pattern is None: raise ConfigurationError('"pattern" argument may not be None') - if self.route_prefix: + # check for an external route + parsed = urlparse.urlparse(pattern) + if parsed.hostname: + pattern = parsed.path + + original_pregenerator = pregenerator + def external_url_pregenerator(request, elements, kw): + if '_app_url' not in kw: + 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('/') - if pregenerator is None: - parsed = urlparse(pattern) - if parsed.hostname: - pattern = parsed.path - - def external_url_pregenerator(request, elements, kw): - if not '_app_url' in kw: - if '_scheme' in kw and parsed.scheme != kw['_scheme']: - scheme = kw['_scheme'] - elif parsed.scheme: - scheme = parsed.scheme - else: - scheme = request.scheme - kw['_app_url'] = '{0}://{1}'.format( - scheme, parsed.netloc) - return elements, kw - - pregenerator = external_url_pregenerator - mapper = self.get_routes_mapper() introspectables = [] diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 0e006f447..9e4f72c41 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1036,47 +1036,68 @@ class Test_external_static_url_integration(unittest.TestCase): testing.tearDown() def _makeRequest(self): - from pyramid.request import Request + 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'), + 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'), + 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'), + 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.assertEqual(request.route_url('acme', foo='bar', _app_url='http://fakeme.com'), + self.assertEqual( + request.route_url('acme', foo='bar', _app_url='http://fakeme.com'), 'http://fakeme.com/path/bar') - 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.assertEqual(request.route_path('acme', foo='bar'), - '/path/bar') + self.assertEqual(request.route_path('acme', foo='bar'), '/path/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): -- cgit v1.2.3 From d07d167f6dcdc5ef03e8aaca3c953e984a5a5f1a Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 20 Aug 2013 10:13:55 -0400 Subject: raise ValueError instead of generating just path when _app_url is provided to request.route_url and the route has an external pattern --- CHANGES.txt | 6 +++--- docs/narr/urldispatch.rst | 11 ++++++----- pyramid/config/routes.py | 26 +++++++++++++++++--------- pyramid/tests/test_url.py | 7 +++---- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b4fe60085..1eeb0ce7b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -142,9 +142,9 @@ 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. +- 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 f3513624e..62eb89348 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -785,11 +785,12 @@ purposes only and are never considered for matching at request time. >>> request.route_url('youtube', video_id='oHg5SJYRHA0') >>> "https://youtube.com/watch/oHg5SJYRHA0" -All pattern replacements and calls to -:meth:`pyramid.request.Request.route_url` will work as expected. Note that -:meth:`pyramid.request.Request.route_path` will also just return the external -URLs path part. - +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 af7ddd6f3..0ed370c94 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -385,21 +385,29 @@ class RoutesConfiguratorMixin(object): if pattern is None: raise ConfigurationError('"pattern" argument may not be None') - # check for an external route + # 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' not in kw: - 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 '_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( diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 9e4f72c41..6f1ee3bf0 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1067,15 +1067,14 @@ class Test_external_static_url_integration(unittest.TestCase): self.config.add_route('acme', 'http://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry - self.assertEqual( - request.route_url('acme', foo='bar', _app_url='http://fakeme.com'), - 'http://fakeme.com/path/bar') + 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.assertEqual(request.route_path('acme', foo='bar'), '/path/bar') + self.assertRaises(ValueError, request.route_path, 'acme', foo='bar') def test_generate_external_url_with_pregenerator(self): def pregenerator(request, elements, kw): -- cgit v1.2.3