Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support inherit_slash=True on add_route #3420

Merged
merged 7 commits into from
Nov 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ Features
documentation for more information about why this change was made.
See https://github.com/Pylons/pyramid/pull/3413

- It is now possible to control whether a route pattern contains a trailing
slash when it is composed with a route prefix using
``config.include(..., route_prefix=...)`` or
``with config.route_prefix_context(...)``. This can be done by specifying
an empty pattern and setting the new argument
``inherit_slash=True``. For example:

.. code-block:: python

with config.route_prefix_context('/users'):
config.add_route('users', '', inherit_slash=True)

In the example, the resulting pattern will be ``/users``. Similarly, if the
route prefix were ``/users/`` then the final pattern would be ``/users/``.
If the ``pattern`` was ``'/'``, then the final pattern would always be
``/users/``. This new setting is only available if the pattern supplied
to ``add_route`` is the empty string (``''``).
See https://github.com/Pylons/pyramid/pull/3420

Bug Fixes
---------

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ Contributors

- Kirill Kuzminykh, 2017/03/01

- Charlie Choiniere, 2017/04/03

- Aleph Melo, 2017/04/16

- Jeremy(Ching-Rui) Chen, 2017/04/19
Expand Down
4 changes: 4 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1203,3 +1203,7 @@ Glossary
A media type is a nested structure containing a top-level type and a subtype.
Optionally, a media type can also contain parameters specific to the type.
See :rfc:`6838` for more information about media types.

route prefix
A route prefix is a path prefix that is prepended to any routes that are configured while it is active.
A route prefix can be set via :meth:`pyramid.config.Configurator.include` or :meth:`pyramid.config.Configurator.route_prefix_context`.
18 changes: 17 additions & 1 deletion docs/narr/urldispatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ application from small and potentially reusable components.
The :meth:`pyramid.config.Configurator.include` method accepts an argument
named ``route_prefix`` which can be useful to authors of URL-dispatch-based
applications. If ``route_prefix`` is supplied to the include method, it must
be a string. This string represents a route prefix that will be prepended to
be a string. This string represents a :term:`route prefix` that will be prepended to
all route patterns added by the *included* configuration. Any calls to
:meth:`pyramid.config.Configurator.add_route` within the included callable will
have their pattern prefixed with the value of ``route_prefix``. This can be
Expand All @@ -998,6 +998,22 @@ then only match if the URL path is ``/users/show``, and when the
:meth:`pyramid.request.Request.route_url` function is called with the route
name ``show_users``, it will generate a URL with that same path.

To create a route that matches requests to the ``route_prefix`` without a trailing slash, pass ``inherit_slash=True`` to the call to ``add_route``.

.. code-block:: python
:linenos:

from pyramid.config import Configurator

def users_include(config):
config.add_route('show_users', '', inherit_slash=True)

def main(global_config, **settings):
config = Configurator()
config.include(users_include, route_prefix='/users')

The above configuration will match ``/users`` instead of ``/users/``.

Route prefixes are recursive, so if a callable executed via an include itself
turns around and includes another callable, the second-level route prefix will
be prepended with the first:
Expand Down
4 changes: 3 additions & 1 deletion src/pyramid/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,9 @@ def main(global_config, **settings):
configuration conflict by registering something with the same
configuration parameters.

If the ``route_prefix`` is supplied, it must be a string. Any calls
If the ``route_prefix`` is supplied, it must be a string and will
have a similar effect to using
:meth:`pyramid.config.Configurator.route_prefix_context`. Any calls
to :meth:`pyramid.config.Configurator.add_route` within the included
callable will have their pattern prefixed with the value of
``route_prefix``. This can be used to help mount a set of routes at a
Expand Down
39 changes: 35 additions & 4 deletions src/pyramid/config/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def add_route(
path=None,
pregenerator=None,
static=False,
inherit_slash=None,
**predicates
):
""" Add a :term:`route configuration` to the current
Expand Down Expand Up @@ -139,6 +140,27 @@ def add_route(

.. versionadded:: 1.1

inherit_slash

This argument can only be used when the ``pattern`` is an empty
string (``''``). By default, the composed route pattern will always
include a trailing slash, but this argument provides a way to
opt-out if both, you (the developer invoking ``add_route``) and the
integrator (the developer setting the :term:`route prefix`),
agree that the pattern should not contain a trailing slash.
For example:

.. code-block:: python

with config.route_prefix_context('/users'):
config.add_route('users', '', inherit_slash=True)

In this example, the resulting route pattern will be ``/users``.
Alternatively, if the route prefix were ``/users/``, then the
resulting route pattern would be ``/users/``.

.. versionadded:: 2.0

Predicate Arguments

pattern
Expand Down Expand Up @@ -329,6 +351,11 @@ def add_route(
if pattern is None:
raise ConfigurationError('"pattern" argument may not be None')

if inherit_slash and pattern != '':
raise ConfigurationError(
'"inherit_slash" may only be used with an empty pattern'
)

# 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)
Expand Down Expand Up @@ -364,7 +391,12 @@ def external_url_pregenerator(request, elements, kw):
static = True

elif self.route_prefix:
pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/')
if pattern == '' and inherit_slash:
pattern = self.route_prefix
else:
pattern = (
self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/')
)

mapper = self.get_routes_mapper()

Expand Down Expand Up @@ -514,9 +546,8 @@ def get_routes_mapper(self):

@contextlib.contextmanager
def route_prefix_context(self, route_prefix):
""" Return this configurator with the
:attr:`pyramid.config.Configurator.route_prefix` attribute mutated to
include the new ``route_prefix``.
"""
Return this configurator with a :term:`route prefix` temporarily set.

When the context exits, the ``route_prefix`` is reset to the original.

Expand Down
24 changes: 24 additions & 0 deletions tests/test_config/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ def test_add_route_with_route_prefix(self):
config.add_route('name', 'path')
self._assertRoute(config, 'name', 'root/path')

def test_add_route_with_inherit_errors(self):
from pyramid.exceptions import ConfigurationError

config = self._makeOne(autocommit=True)
self.assertRaises(
ConfigurationError,
config.add_route,
'name',
'/',
inherit_slash=True,
)

def test_add_route_with_route_prefix_with_inherit_slash(self):
config = self._makeOne(autocommit=True)
config.route_prefix = 'root'
config.add_route('name', '', inherit_slash=True)
self._assertRoute(config, 'name', 'root')

def test_add_route_with_root_slash_with_route_prefix(self):
config = self._makeOne(autocommit=True)
config.route_prefix = 'root'
config.add_route('name', '/')
self._assertRoute(config, 'name', 'root/')

def test_add_route_discriminator(self):
config = self._makeOne()
config.add_route('name', 'path')
Expand Down