Michael Merickel
2013-08-20 c14c8d7c86bd5cad207fab9acc053dc723dd2acf
Merge branch 'feature.issue611-external-static-routes' of tomster/pyramid into feature.external-routes
4 files modified
114 ■■■■■ changed files
CHANGES.txt 4 ●●●● patch | view | raw | blame | history
docs/narr/urldispatch.rst 39 ●●●●● patch | view | raw | blame | history
pyramid/config/routes.py 20 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_url.py 51 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -142,6 +142,10 @@
- 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
---------
docs/narr/urldispatch.rst
@@ -105,6 +105,7 @@
.. _route_pattern_syntax:
Route Pattern Syntax
~~~~~~~~~~~~~~~~~~~~
@@ -126,6 +127,10 @@
.. code-block:: text
   /{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
@@ -754,9 +759,43 @@
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"
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
pyramid/config/routes.py
@@ -1,4 +1,5 @@
import warnings
from urlparse import urlparse
from pyramid.interfaces import (
    IRequest,
@@ -387,6 +388,25 @@
        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 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 = []
pyramid/tests/test_url.py
@@ -1027,6 +1027,57 @@
        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.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')
class DummyContext(object):
    def __init__(self, next=None):
        self.next = next