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

fix route_prefix leading slash handling #3759

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ env*/
venv/
.cache/
.python-version
pyramid-*/
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Features
Bug Fixes
---------

- Fix ``pyramid.config.routes.RoutesConfiguratorMixin.route_prefix_context``
to avoid removal of leading ``/`` provided in the prefix.

- Fix issues where permissions may be checked on exception views. This is not
supposed to happen in normal circumstances.

Expand Down
9 changes: 5 additions & 4 deletions src/pyramid/config/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,11 +595,12 @@ def route_prefix_context(self, route_prefix):
if old_route_prefix is None:
old_route_prefix = ''

route_prefix = '{}/{}'.format(
old_route_prefix.rstrip('/'), route_prefix.lstrip('/')
)
if old_route_prefix.strip('/'):
route_prefix = '{}/{}'.format(
old_route_prefix, route_prefix.strip('/')
)

route_prefix = route_prefix.strip('/')
route_prefix = route_prefix.rstrip('/')

if not route_prefix:
route_prefix = None
Expand Down
2 changes: 2 additions & 0 deletions tests/pkgs/include_routeprefix_app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def includeme(config):
config.include('tests.pkgs.include_routeprefix_app.root.configure')
70 changes: 70 additions & 0 deletions tests/pkgs/include_routeprefix_app/nested.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from pyramid.response import Response


def aview(request):
return Response(request.path)


def configure(config):
# note:
# In order to reuse similar routes for prefix and non-prefixed includes,
# we must add a prefix to the names as well to avoid conflicts.
current_prefix = config.route_prefix or ''

# note:
# The following definition is equivalent to doing an 'add.route'
# followed by 'config.add_view' with an empty pattern or only '/'
# (i.e.: exactly like the next block definition).
# However, the resolved path will depend on the parent including this
# configuration. If it contains a 'route_prefix', it will be equal to it.
# Otherwise, this would become the equal to the 'root' path.
name = current_prefix + 'nested_root_named_view'
config.add_view(aview, name=name)

name = current_prefix + 'nested_route_simple'
config.add_route(name, pattern='nested_route_simple')
config.add_view(aview, route_name=name)

name = current_prefix + 'nested_route_slash'
config.add_route(name, pattern='/nested_route_slash')
config.add_view(aview, route_name=name)

config.commit()

with config.route_prefix_context(route_prefix='nested_ctx_simple'):
current_prefix = config.route_prefix or ''

# note:
# Since the following pattern is empty and is always within a context,
# it is equivalent to simply doing 'config.add_view(view, name=...)'.
name = current_prefix + 'nested_ctx_simple_view'
config.add_route(name, pattern='')
config.add_view(aview, route_name=name)

name = current_prefix + 'nested_ctx_route_simple'
config.add_route(name, pattern='nested_ctx_route_simple')
config.add_view(aview, route_name=name)

name = current_prefix + 'nested_ctx_route_slash'
config.add_route(name, pattern='/nested_ctx_route_slash')
config.add_view(aview, route_name=name)
config.commit()

with config.route_prefix_context(route_prefix='/nested_ctx_slash'):
current_prefix = config.route_prefix or ''

# note:
# Since the following pattern is empty and is always within a context,
# it is equivalent to simply doing 'config.add_view(view, name=...)'.
name = current_prefix + 'nested_ctx_slash_view'
config.add_route(name, pattern='/')
config.add_view(aview, route_name=name)

name = current_prefix + 'nested_ctx_route_simple'
config.add_route(name, pattern='nested_ctx_route_simple')
config.add_view(aview, route_name=name)

name = current_prefix + 'nested_ctx_route_slash'
config.add_route(name, pattern='/nested_ctx_route_slash')
config.add_view(aview, route_name=name)
config.commit()
50 changes: 50 additions & 0 deletions tests/pkgs/include_routeprefix_app/root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from pyramid.response import Response


def aview(request):
return Response(request.path)


def configure(config):
# note:
# Since the tests using this application evaluate various path
# combinations with or without slashes (notably for routes using
# pattern='' or pattern='/' explicitly), automatically redirect to any
# corresponding trailing slash variation to simplify HTTP requests.
# This does not affect how *leading* slashes of 'route_prefix' are
# evaluated, since an HTTP request that doesn't start with a '/' path
# is immediately rejected.
config.add_notfound_view(append_slash=True)

config.add_route('', pattern='/')
config.add_view(aview, route_name='')
config.add_view(aview, name='named_view')
config.commit()

with config.route_prefix_context(route_prefix='root_ctx_simple'):
config.add_view(aview, name='root_ctx_simple_named_view')
config.add_route('root_ctx_simple', pattern='')
config.add_view(aview, 'root_ctx_simple')
config.add_route('ctx_simple_view', pattern=config.route_prefix)
config.add_view(aview, route_name='ctx_simple_view')
config.include('tests.pkgs.include_routeprefix_app.nested.configure')
config.commit()

with config.route_prefix_context(route_prefix='/root_ctx_slash'):
config.add_view(aview, name='root_ctx_slash_named_view')
config.add_route('root_ctx_slash', pattern='/')
config.add_view(aview, 'root_ctx_slash')
config.add_route('ctx_slash_view', pattern=config.route_prefix)
config.add_view(aview, route_name='ctx_slash_view')
config.include('tests.pkgs.include_routeprefix_app.nested.configure')
config.commit()

config.include('tests.pkgs.include_routeprefix_app.nested.configure')
config.include(
'tests.pkgs.include_routeprefix_app.nested.configure',
route_prefix='prefix_simple',
)
config.include(
'tests.pkgs.include_routeprefix_app.nested.configure',
route_prefix='/prefix_slash',
)
44 changes: 44 additions & 0 deletions tests/test_config/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,30 @@ def dummy_subapp(config):

root_config.include(dummy_subapp, route_prefix='root')

def test_include_with_route_prefix_no_trailing_slash(self):
root_config = self._makeOne(autocommit=True)

def dummy_subapp(config):
self.assertEqual(config.route_prefix, '/root')

root_config.include(dummy_subapp, route_prefix='/root/')

def test_include_with_route_prefix_with_leading_slash(self):
root_config = self._makeOne(autocommit=True)

def dummy_subapp(config):
self.assertEqual(config.route_prefix, 'root')

root_config.include(dummy_subapp, route_prefix='root/')

def test_include_with_route_prefix_with_leading_no_trailing_slash(self):
root_config = self._makeOne(autocommit=True)

def dummy_subapp(config):
self.assertEqual(config.route_prefix, '/root')

root_config.include(dummy_subapp, route_prefix='/root/')

def test_include_with_nested_route_prefix(self):
root_config = self._makeOne(autocommit=True, route_prefix='root')

Expand All @@ -924,6 +948,26 @@ def dummy_subapp(config):

root_config.include(dummy_subapp, route_prefix='nested')

def test_include_with_nested_route_prefix_with_leading_slash(self):
root_config = self._makeOne(autocommit=True, route_prefix='/root')

def dummy_subapp2(config):
self.assertEqual(config.route_prefix, '/root/nested')

def dummy_subapp3(config):
self.assertEqual(config.route_prefix, '/root/nested/nested2')
config.include(dummy_subapp4)

def dummy_subapp4(config):
self.assertEqual(config.route_prefix, '/root/nested/nested2')

def dummy_subapp(config):
self.assertEqual(config.route_prefix, '/root/nested')
config.include(dummy_subapp2)
config.include(dummy_subapp3, route_prefix='/nested2')

root_config.include(dummy_subapp, route_prefix='/nested')

def test_include_with_missing_source_file(self):
import inspect

Expand Down
Loading