From 855fe374108779e82530da74f1c0fd21ab9033b3 Mon Sep 17 00:00:00 2001 From: Charlie Choiniere Date: Fri, 17 Apr 2015 07:12:11 -0400 Subject: [PATCH 1/6] Added support for a prefixed base url to not contain a trailing slash --- docs/narr/urldispatch.rst | 18 ++++++++++++++++++ pyramid/config/routes.py | 5 ++++- pyramid/tests/test_config/test_routes.py | 12 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 87a962a9a6..f2a94341dd 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -964,6 +964,24 @@ 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 prefixed route that matches requests to the ``route_prefix`` +without a trailing slash set the route ``pattern`` to an empty string. + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def users_include(config): + config.add_route('show_users', '') + + def main(global_config, **settings): + config = Configurator() + config.include(users_include, route_prefix='/users') + +The above configuration will match ``/users`` instead of ``/users/`` if a slash +had been supplied to the :meth:`pyramid.config.Configurator.add_route` function. + 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/pyramid/config/routes.py b/pyramid/config/routes.py index f1463b50b0..971e3fc959 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -333,7 +333,10 @@ def external_url_pregenerator(request, elements, kw): static = True elif self.route_prefix: - pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') + if pattern == '': + pattern = self.route_prefix.rstrip('/') + else: + pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') mapper = self.get_routes_mapper() diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py index 1d2530c02f..778d339382 100644 --- a/pyramid/tests/test_config/test_routes.py +++ b/pyramid/tests/test_config/test_routes.py @@ -50,6 +50,18 @@ def test_add_route_with_route_prefix(self): config.add_route('name', 'path') self._assertRoute(config, 'name', 'root/path') + def test_add_route_with_empty_string_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_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') From de5e4f40e0246d71ba5875c957281fb764da74dd Mon Sep 17 00:00:00 2001 From: Charlie Choiniere Date: Mon, 3 Apr 2017 21:36:03 -0400 Subject: [PATCH 2/6] signed contributors agreement --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d1ac72df57..08d06875e5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -230,3 +230,5 @@ Contributors - Antti Haapala, 2013/11/15 - Amit Mane, 2014/01/23 + +- Charlie Choiniere, 2017/04/03 From ebb56ebe4dd0cbecd6d19b5741ddf52c96a0176d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 4 Nov 2018 13:43:39 -0600 Subject: [PATCH 3/6] add changelog for #3414 --- CHANGES.rst | 15 +++++++++++++++ CONTRIBUTORS.txt | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 73562c003d..53bf0ab60c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,12 @@ Features documentation for more information about why this change was made. See https://github.com/Pylons/pyramid/pull/3413 +- It is now possible to declare a route to not contain a trailing slash + when it is mounted via ``config.include(..., route_prefix=...)`` or via + ``with config.route_prefix_context(...)`` by specifying a ``pattern`` of + ``''``. This change is backward incompatible, see the notes below. + See https://github.com/Pylons/pyramid/pull/3414 + Bug Fixes --------- @@ -68,6 +74,15 @@ Backward Incompatibilities documentation for more information about why this change was made. See https://github.com/Pylons/pyramid/pull/3413 +- Changed the URL generation and matching for a route with a ``pattern`` of + ``''`` when the route is mounted with a ``route_prefix`` via either + ``config.include(..., route_prefix=...)`` or + ``with config.route_prefix_context(...)``. This route will now match a bare + URL without a trailing slash. To preserve the old behavior, set the + ``pattern`` to ``'/'`` instead of the empty string and the URL will contain + a trailing slash. This only affects mounted routes using ``route_prefix``. + See https://github.com/Pylons/pyramid/pull/3414 + Documentation Changes --------------------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 6eb42c18ce..79e4287d2a 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -230,7 +230,6 @@ Contributors - Antti Haapala, 2013/11/15 - Amit Mane, 2014/01/23 -<<<<<<< HEAD - Fenton Travers, 2014/05/06 From dff0798e352d0afeeec2058200ac983692b606c2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 11 Nov 2018 22:39:52 -0600 Subject: [PATCH 4/6] use inherit_slash=True on add_route to opt-in to no trailing slash --- CHANGES.rst | 30 ++++++++++++------------ docs/glossary.rst | 4 ++++ docs/narr/urldispatch.rst | 10 ++++---- src/pyramid/config/__init__.py | 4 +++- src/pyramid/config/routes.py | 39 +++++++++++++++++++++++++++----- tests/test_config/test_routes.py | 16 +++++++++++-- 6 files changed, 74 insertions(+), 29 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 53bf0ab60c..1790bb69f2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,11 +12,22 @@ Features documentation for more information about why this change was made. See https://github.com/Pylons/pyramid/pull/3413 -- It is now possible to declare a route to not contain a trailing slash - when it is mounted via ``config.include(..., route_prefix=...)`` or via - ``with config.route_prefix_context(...)`` by specifying a ``pattern`` of - ``''``. This change is backward incompatible, see the notes below. - See https://github.com/Pylons/pyramid/pull/3414 +- 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:: + + 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 --------- @@ -74,15 +85,6 @@ Backward Incompatibilities documentation for more information about why this change was made. See https://github.com/Pylons/pyramid/pull/3413 -- Changed the URL generation and matching for a route with a ``pattern`` of - ``''`` when the route is mounted with a ``route_prefix`` via either - ``config.include(..., route_prefix=...)`` or - ``with config.route_prefix_context(...)``. This route will now match a bare - URL without a trailing slash. To preserve the old behavior, set the - ``pattern`` to ``'/'`` instead of the empty string and the URL will contain - a trailing slash. This only affects mounted routes using ``route_prefix``. - See https://github.com/Pylons/pyramid/pull/3414 - Documentation Changes --------------------- diff --git a/docs/glossary.rst b/docs/glossary.rst index 4668efe6d8..f42b298df3 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 701326fab9..3b737b46da 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,8 +998,7 @@ 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 prefixed route that matches requests to the ``route_prefix`` -without a trailing slash set the route ``pattern`` to an empty string. +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: @@ -1007,14 +1006,13 @@ without a trailing slash set the route ``pattern`` to an empty string. from pyramid.config import Configurator def users_include(config): - config.add_route('show_users', '') + 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/`` if a slash -had been supplied to the :meth:`pyramid.config.Configurator.add_route` function. +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 diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 00c3e6a024..475f0d9a28 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -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 diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 7bddf4e67a..e7e981df0b 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -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 @@ -139,6 +140,26 @@ 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 + includes a trailing slash, but this argument provides a way to + opt-out if both, you 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 +350,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) @@ -364,10 +390,12 @@ def external_url_pregenerator(request, elements, kw): static = True elif self.route_prefix: - if pattern == '': - pattern = self.route_prefix.rstrip('/') + if pattern == '' and inherit_slash: + pattern = self.route_prefix else: - pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') + pattern = ( + self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') + ) mapper = self.get_routes_mapper() @@ -517,9 +545,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. diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py index 1c1ed67002..e6540c3435 100644 --- a/tests/test_config/test_routes.py +++ b/tests/test_config/test_routes.py @@ -54,10 +54,22 @@ def test_add_route_with_route_prefix(self): config.add_route('name', 'path') self._assertRoute(config, 'name', 'root/path') - def test_add_route_with_empty_string_with_route_prefix(self): + 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', '') + config.add_route('name', '', inherit_slash=True) self._assertRoute(config, 'name', 'root') def test_add_route_with_root_slash_with_route_prefix(self): From c8f88fe53338b2b2675ef4497d0704a1542112ec Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 16:13:38 -0600 Subject: [PATCH 5/6] try to fix docs --- src/pyramid/config/routes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index e7e981df0b..52540c9354 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -144,10 +144,11 @@ def add_route( This argument can only be used when the ``pattern`` is an empty string (``''``). By default, the composed route pattern will always - includes a trailing slash, but this argument provides a way to - opt-out if both, you and the integrator (the developer setting the - :term:`route prefix`), agree that the pattern should not contain - a trailing slash. For example: + 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 @@ -155,7 +156,7 @@ def add_route( 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 + Alternatively, if the route prefix were ``/users/``, then the resulting route pattern would be ``/users/``. .. versionadded:: 2.0 From 2d32ae81abc473fb21b4bc42aec479c656693f1c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 18:43:20 -0600 Subject: [PATCH 6/6] use code blocks --- CHANGES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1790bb69f2..7772ac7e29 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,7 +17,9 @@ Features ``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:: + ``inherit_slash=True``. For example: + + .. code-block:: python with config.route_prefix_context('/users'): config.add_route('users', '', inherit_slash=True)