Skip to content

Commit

Permalink
get rid of remainder_name on route, and just default to passing trave…
Browse files Browse the repository at this point in the history
…rse; add route_remainder_name argument to resource_url
  • Loading branch information
mcdonc committed Aug 30, 2013
1 parent 06f57f4 commit c29603e
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 101 deletions.
8 changes: 4 additions & 4 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ Features
--------

- You can now generate "hybrid" urldispatch/traversal URLs more easily
by using the new ``route_name`` and ``route_kw`` arguments to
``request.resource_url`` and ``request.resource_path``. See the new section
of the "Combining Traversal and URL Dispatch" documentation chapter entitled
"Hybrid URL Generation".
by using the new ``route_name``, ``route_kw`` and ``route_remainder_name``
arguments to ``request.resource_url`` and ``request.resource_path``. See
the new section of the "Combining Traversal and URL Dispatch" documentation
chapter entitled "Hybrid URL Generation".

- It is now possible to escape double braces in Pyramid scaffolds (unescaped,
these represent replacement values). You can use ``\{\{a\}\}`` to
Expand Down
30 changes: 26 additions & 4 deletions docs/narr/hybrid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,8 @@ Generating Hybrid URLs
The :meth:`pyramid.request.Request.resource_url` method and the
:meth:`pyramid.request.Request.resource_path` method both accept optional
keyword arguments that make it easier to generate route-prefixed URLs that
contain paths to traversal resources:``route_name`` and ``route_kw``.
contain paths to traversal resources:``route_name``, ``route_kw``, and
``route_remainder_name``.

Any route that has a pattern that contains a ``*remainder`` pattern (any
stararg remainder pattern, such as ``*traverse`` or ``*subpath`` or ``*fred``)
Expand Down Expand Up @@ -615,15 +616,36 @@ You can pass ``route_kw`` in to fill in ``{id}`` above:
If you pass ``route_kw`` but do not pass ``route_name``, ``route_kw`` will
be ignored.

All other values that are normally passable to ``resource_path`` and
``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, etc) work
as you might expect in this configuration too.
By default this feature works by calling ``route_url`` under the hood,
and passing the value of the resource path to that function as ``traverse``.
If your route has a different ``*stararg`` remainder name (such as
``*subpath``), you can tell ``resource_url`` or ``resource_path`` to use that
instead of ``traverse`` by passing ``route_remainder_name``. For example,
if you have the following route:

.. code-block:: python
config.add_route('mysection', '/mysection*subpath')
You can fill in the ``*subpath`` value using ``resource_url`` by doing:

.. code-block:: python
request.resource_path(a, route_name='mysection',
route_remainder_name='subpath')
If you pass ``route_remainder_name`` but do not pass ``route_name``,
``route_remainder_name`` will be ignored.

If you try to use ``resource_path`` or ``resource_url`` when the ``route_name``
argument points at a route that does not have a remainder stararg, an error
will not be raised, but the generated URL will not contain any remainder
information either.

All other values that are normally passable to ``resource_path`` and
``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, and
positional elements) work as you might expect in this configuration.

Note that this feature is incompatible with the ``__resource_url__`` feature
(see :ref:`overriding_resource_url_generation`) implemented on resource
objects. Any ``__resource_url__`` supplied by your resource will be ignored
Expand Down
9 changes: 0 additions & 9 deletions pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,15 +692,6 @@ class IRoute(Interface):
pregenerator = Attribute('This attribute should either be ``None`` or '
'a callable object implementing the '
'``IRoutePregenerator`` interface')
remainder_name = Attribute(
'The name of any stararg remainder that is present at the end of '
'the pattern. For example, if the pattern is ``/foo*bar``, the '
'``remainder_name`` will be ``bar``; if the pattern is ` '
'`/foo*traverse``, the ``remainder_name`` will be ``traverse``. '
'If the route does not have a stararg remainder name in its pattern, '
'the value of ``remainder_name`` will be ``None``. This attribute '
'is new as of Pyramid 1.5.'
)

def match(path):
"""
Expand Down
62 changes: 25 additions & 37 deletions pyramid/tests/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,12 @@ def test_resource_url_with_route_name_no_remainder_on_adapter(self):
# no virtual_path_tuple on adapter
adapter.virtual_path = '/a/b/c/'
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, route_name='foo')
self.assertEqual(result, 'http://example.com:5432/1/2/3')
self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')})
self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')})

def test_resource_url_with_route_name_remainder_on_adapter(self):
from pyramid.interfaces import IRoutesMapper
Expand All @@ -289,13 +288,12 @@ def test_resource_url_with_route_name_remainder_on_adapter(self):
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, route_name='foo')
self.assertEqual(result, 'http://example.com:5432/1/2/3')
self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')})
self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')})

def test_resource_url_with_route_name_and_app_url(self):
from pyramid.interfaces import IRoutesMapper
Expand All @@ -309,13 +307,12 @@ def test_resource_url_with_route_name_and_app_url(self):
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, route_name='foo', app_url='app_url')
self.assertEqual(result, 'app_url/1/2/3')
self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')})
self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')})

def test_resource_url_with_route_name_and_scheme_host_port_etc(self):
from pyramid.interfaces import IRoutesMapper
Expand All @@ -329,15 +326,14 @@ def test_resource_url_with_route_name_and_scheme_host_port_etc(self):
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, route_name='foo', scheme='scheme',
host='host', port='port', query={'a':'1'},
anchor='anchor')
self.assertEqual(result, 'scheme://host:port/1/2/3?a=1#anchor')
self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')})
self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')})

def test_resource_url_with_route_name_and_route_kwargs(self):
from pyramid.interfaces import IRoutesMapper
Expand All @@ -351,7 +347,6 @@ def test_resource_url_with_route_name_and_route_kwargs(self):
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
Expand All @@ -360,7 +355,7 @@ def test_resource_url_with_route_name_and_route_kwargs(self):
self.assertEqual(result, 'http://example.com:5432/1/2/3')
self.assertEqual(
route.kw,
{'fred': ('', 'a', 'b', 'c', ''),
{'traverse': ('', 'a', 'b', 'c', ''),
'a':'1',
'b':'2'}
)
Expand All @@ -377,12 +372,31 @@ def test_resource_url_with_route_name_and_elements(self):
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, 'e1', 'e2', route_name='foo')
self.assertEqual(result, 'http://example.com:5432/1/2/3/e1/e2')
self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')})

def test_resource_url_with_route_name_and_remainder_name(self):
from pyramid.interfaces import IRoutesMapper
environ = {
'wsgi.url_scheme':'http',
'SERVER_PORT':'8080',
'SERVER_NAME':'example.com',
}
request = self._makeOne(environ)
adapter = self._registerResourceURL(request.registry)
# virtual_path_tuple on adapter
adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '')
route = DummyRoute('/1/2/3')
mapper = DummyRoutesMapper(route)
request.registry.registerUtility(mapper, IRoutesMapper)
root = DummyContext()
result = request.resource_url(root, route_name='foo',
route_remainder_name='fred')
self.assertEqual(result, 'http://example.com:5432/1/2/3')
self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')})

def test_resource_path(self):
Expand Down Expand Up @@ -570,31 +584,6 @@ def test_route_url_with_anchor_app_url_elements_and_query(self):
self.assertEqual(result,
'http://example2.com/1/2/3/element1?q=1#anchor')

def test_route_url_with_remainder(self):
from pyramid.interfaces import IRoutesMapper
request = self._makeOne()
route = DummyRoute('/1/2/3/')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route=route)
request.registry.registerUtility(mapper, IRoutesMapper)
result = request.route_url('flub', _remainder='abc')
self.assertEqual(result,
'http://example.com:5432/1/2/3/')
self.assertEqual(route.kw['fred'], 'abc')
self.assertFalse('_remainder' in route.kw)

def test_route_url_with_remainder_name_already_in_kw(self):
from pyramid.interfaces import IRoutesMapper
request = self._makeOne()
route = DummyRoute('/1/2/3/')
route.remainder_name = 'fred'
mapper = DummyRoutesMapper(route=route)
request.registry.registerUtility(mapper, IRoutesMapper)
self.assertRaises(
ValueError,
request.route_url, 'flub', _remainder='abc', fred='foo'
)

def test_route_url_integration_with_real_request(self):
# to try to replicate https://github.com/Pylons/pyramid/issues/213
from pyramid.interfaces import IRoutesMapper
Expand Down Expand Up @@ -1268,7 +1257,6 @@ def get_route(self, route_name):
class DummyRoute:
pregenerator = None
name = 'route'
remainder_name = None
def __init__(self, result='/1/2/3'):
self.result = result

Expand Down
11 changes: 0 additions & 11 deletions pyramid/tests/test_urldispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,17 +522,6 @@ def test_generator_functional_oldstyle(self):
self.generates('/foo/:_abc', {'_abc':'20'}, '/foo/20')
self.generates('/foo/:abc_def', {'abc_def':'20'}, '/foo/20')

class Test_get_remainder_name(unittest.TestCase):
def _callFUT(self, pattern):
from pyramid.urldispatch import get_remainder_name
return get_remainder_name(pattern)

def test_it_nostararg(self):
self.assertEqual(self._callFUT('/bob'), None)

def test_it_withstararg(self):
self.assertEqual(self._callFUT('/bob*dean'), 'dean')

class DummyContext(object):
""" """

Expand Down
55 changes: 26 additions & 29 deletions pyramid/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,6 @@ def route_url(self, route_name, *elements, **kw):
are passed, ``_app_url`` takes precedence and any values passed for
``_scheme``, ``_host``, and ``_port`` will be ignored.
If a ``_remainder`` keyword argument is supplied, it will be used to
replace *any* ``*remainder`` stararg at the end of the route pattern.
For example, if the route pattern is ``/foo/*traverse``, and you pass
``_remainder=('a', 'b', 'c')``, it is entirely equivalent to passing
``traverse=('a', 'b', 'c')``, and in either case the generated path
will be ``/foo/a/b/c``. It is an error to pass both ``*remainder`` and
the explicit value for a remainder name; a :exc:`ValueError` will be
raised. This feature was added in Pyramid 1.5.
This function raises a :exc:`KeyError` if the URL cannot be
generated due to missing replacement names. Extra replacement
names are ignored.
Expand All @@ -222,7 +213,6 @@ def route_url(self, route_name, *elements, **kw):
if route.pregenerator is not None:
elements, kw = route.pregenerator(self, elements, kw)

remainder_name = route.remainder_name
anchor = ''
qs = ''
app_url = None
Expand Down Expand Up @@ -258,16 +248,6 @@ def route_url(self, route_name, *elements, **kw):
else:
app_url = self.application_url

remainder = kw.pop('_remainder', None)

if remainder and remainder_name:
if remainder_name in kw:
raise ValueError(
'Cannot pass both "%s" and "_remainder", '
'these conflict for this route' % remainder_name
)
kw[remainder_name] = remainder

path = route.generate(kw) # raises KeyError if generate fails

if elements:
Expand Down Expand Up @@ -426,7 +406,7 @@ def resource_url(self, resource, *elements, **kw):
:ref:`overriding_resource_url_generation`.
.. versionadded:: 1.5
``route_name`` and ``route_kw``
``route_name``, ``route_kw``, and ``route_remainder_name``
If ``route_name`` is passed, this function will delegate its URL
production to the ``route_url`` function. Calling
Expand All @@ -439,32 +419,47 @@ def resource_url(self, resource, *elements, **kw):
'element1',
'element2',
_query={'a':'1'},
_remainder=remainder_path,
traverse=traversal_path,
)
It is only sensible to pass ``route_name`` if the route being named has
a ``*remainder`` stararg value such as ``*traverse``. The remainder
value will be ignored in the output otherwise.
By default, the resource path value will be passed as the name
``traverse`` when ``route_url`` is called. You can influence this by
passing a different ``route_remainder_name`` value if the route has a
different ``*stararg`` value at its end. For example if the route
pattern you want to replace has a ``*subpath`` stararg ala
``/foo*subpath``::
request.resource_url(
resource,
route_name='myroute',
route_remainder_name='subpath'
)
If ``route_name`` is passed, it is also permissible to pass
``route_kw``, which will passed as additional keyword arguments to
``route_url``. Saying ``resource_url(someresource, 'element1',
'element2', route_name='blogentry', route_kw={'id':'4'},
_query={'a':'1'})`` is equivalent to::
_query={'a':'1'})`` is roughly equivalent to::
remainder_path = request.resource_path_tuple(someobject)
kw = {'id':'4', '_query':{'a':'1'}, '_remainder':remainder_path}
kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path}
url = request.route_url(
'blogentry',
'element1',
'element2',
**kw,
)
If ``route_kw`` is passed, but ``route_name`` is not passed,
``route_kw`` will be ignored. If ``route_name`` is passed, the
``__resource_url__`` method of the resource passed is ignored
unconditionally.
If ``route_kw`` or ``route_remainder_name`` is passed, but
``route_name`` is not passed, both ``route_kw`` and
``route_remainder_name`` will be ignored. If ``route_name``
is passed, the ``__resource_url__`` method of the resource passed is
ignored unconditionally. This feature is incompatible with
resources which generate their own URLs.
.. note::
Expand Down Expand Up @@ -527,7 +522,8 @@ def resource_url(self, resource, *elements, **kw):
# older user-supplied IResourceURL adapter without 1.5
# virtual_path_tuple
remainder = tuple(url_adapter.virtual_path.split('/'))
newkw['_remainder'] = remainder
remainder_name = kw.get('route_remainder_name', 'traverse')
newkw[remainder_name] = remainder

for name in (
'app_url', 'scheme', 'host', 'port', 'query', 'anchor'
Expand All @@ -540,6 +536,7 @@ def resource_url(self, resource, *elements, **kw):
route_kw = kw.get('route_kw')
if route_kw is not None:
newkw.update(route_kw)

return self.route_url(route_name, *elements, **newkw)

if 'app_url' in kw:
Expand Down
7 changes: 0 additions & 7 deletions pyramid/urldispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def __init__(self, name, pattern, factory=None, predicates=(),
self.pattern = pattern
self.path = pattern # indefinite b/w compat, not in interface
self.match, self.generate = _compile_route(pattern)
self.remainder_name = get_remainder_name(pattern)
self.name = name
self.factory = factory
self.predicates = predicates
Expand Down Expand Up @@ -234,9 +233,3 @@ def generator(dict):
return result

return matcher, generator

def get_remainder_name(pattern):
match = star_at_end.search(pattern)
if match:
return match.groups()[0]

0 comments on commit c29603e

Please sign in to comment.