diff options
| -rw-r--r-- | CHANGES.rst | 19 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | docs/glossary.rst | 4 | ||||
| -rw-r--r-- | docs/narr/urldispatch.rst | 18 | ||||
| -rw-r--r-- | src/pyramid/config/__init__.py | 4 | ||||
| -rw-r--r-- | src/pyramid/config/routes.py | 39 | ||||
| -rw-r--r-- | tests/test_config/test_routes.py | 24 |
7 files changed, 104 insertions, 6 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 73562c003..7772ac7e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 --------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 7256b66db..79e4287d2 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -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 diff --git a/docs/glossary.rst b/docs/glossary.rst index 4668efe6d..f42b298df 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -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`. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 52d64891c..3b737b46d 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -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 @@ -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: diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 00c3e6a02..475f0d9a2 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -602,7 +602,9 @@ class Configurator( 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 diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index f9180bd76..52540c935 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -40,6 +40,7 @@ class RoutesConfiguratorMixin(object): path=None, pregenerator=None, static=False, + inherit_slash=None, **predicates ): """ Add a :term:`route configuration` to the current @@ -139,6 +140,27 @@ class RoutesConfiguratorMixin(object): .. 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 @@ -329,6 +351,11 @@ class RoutesConfiguratorMixin(object): 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) @@ -364,7 +391,12 @@ class RoutesConfiguratorMixin(object): 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() @@ -514,9 +546,8 @@ class RoutesConfiguratorMixin(object): @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. diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py index 32a64b4bd..e6540c343 100644 --- a/tests/test_config/test_routes.py +++ b/tests/test_config/test_routes.py @@ -54,6 +54,30 @@ class RoutesConfiguratorMixinTests(unittest.TestCase): 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') |
