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

Enhancement: Route-Prefixing re Issue #406 #414

Closed
wants to merge 10 commits into from
60 changes: 43 additions & 17 deletions pyramid/config/__init__.py
Expand Up @@ -71,6 +71,7 @@
from pyramid.config.util import (
action_method,
ActionInfo,
route_pattern,
)
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
Expand Down Expand Up @@ -105,7 +106,8 @@ class Configurator(
``authorization_policy``, ``renderers``, ``debug_logger``,
``locale_negotiator``, ``request_factory``, ``renderer_globals_factory``,
``default_permission``, ``session_factory``, ``default_view_mapper``,
``autocommit``, ``exceptionresponse_view`` and ``route_prefix``.
``autocommit``, ``exceptionresponse_view``, ``route_prefix``, and
``route_suffix``.

If the ``registry`` argument is passed as a non-``None`` value, it must
be an instance of the :class:`pyramid.registry.Registry` class
Expand Down Expand Up @@ -239,6 +241,11 @@ class Configurator(
:meth:`pyramid.config.Configurator.add_route` will have the specified path
prepended to their pattern. This parameter is new in Pyramid 1.2.

If ``route_suffix`` is passed, all routes added with
:meth:`pyramid.config.Configurator.add_route` will have the specified path
appended to their pattern. The ``route_suffix`` parameter is new in
Pyramid X.X.

If ``introspector`` is passed, it must be an instance implementing the
attributes and methods of :class:`pyramid.interfaces.IIntrospector`. If
``introspector`` is not passed (or is passed as ``None``), the default
Expand Down Expand Up @@ -272,6 +279,7 @@ def __init__(self,
autocommit=False,
exceptionresponse_view=default_exceptionresponse_view,
route_prefix=None,
route_suffix=None,
introspector=None,
):
if package is None:
Expand All @@ -283,6 +291,7 @@ def __init__(self,
self.registry = registry
self.autocommit = autocommit
self.route_prefix = route_prefix
self.route_suffix = route_suffix
if registry is None:
registry = Registry(self.package_name)
self.registry = registry
Expand Down Expand Up @@ -589,7 +598,7 @@ def commit(self):
self.action_state.execute_actions(introspector=self.introspector)
self.action_state = ActionState() # old actions have been processed

def include(self, callable, route_prefix=None):
def include(self, callable, route_prefix=None, route_suffix=None):
"""Include a configuration callables, to support imperative
application extensibility.

Expand Down Expand Up @@ -681,25 +690,39 @@ def main(global_config, **settings):
pattern.

The ``route_prefix`` parameter is new as of Pyramid 1.2.

When the ``route_prefix`` parameter is provided to the outer-most
``include`` it has a special configurative property. If ``route_prefix``
ends with a ``/`` then the route will end with a ``/``. If the
``route_prefix`` does not end with a ``/`` then the route created will
also not end with a ``/``. In this way implementers may mount existing
callables or third-party modules with a slash-appended-style that
matches the rest of their application.

The above configurative behaviour of ``route_prefix`` is new as of
Pyramid 1.X.

The ``route_suffix`` parameter complements ``route_prefix``, allowing a
suffix to be appended to included routes. However ``route_suffix`` does
not have the same configurative property as ``route_prefix``, since
``route_prefix`` ultimately decides whether the mounted routes
(including the suffix) will be slash-appended or not.

The ``route_suffix`` parameter is new as of Pyramid 1.X.

"""
# """ <-- emacs

action_state = self.action_state

if route_prefix is None:
route_prefix = ''

old_route_prefix = self.route_prefix
if old_route_prefix is None:
old_route_prefix = ''

route_prefix = '%s/%s' % (
old_route_prefix.rstrip('/'),
route_prefix.lstrip('/')
)
route_prefix = route_prefix.strip('/')
if not route_prefix:
route_prefix = None
if not route_prefix is None:
route_prefix = route_pattern(
(self.route_prefix or []) + [route_prefix]
)
if not route_suffix is None:
route_suffix = route_pattern(
[route_suffix] + (self.route_suffix or [])
)

c = self.maybe_dotted(callable)
module = inspect.getmodule(c)
Expand All @@ -720,6 +743,7 @@ def main(global_config, **settings):
package=package_of(module),
autocommit=self.autocommit,
route_prefix=route_prefix,
route_suffix=route_suffix,
)
configurator.basepath = os.path.dirname(sourcefile)
configurator.includepath = self.includepath + (spec,)
Expand Down Expand Up @@ -780,7 +804,8 @@ def with_context(cls, context):
registry=context.registry,
package=context.package,
autocommit=context.autocommit,
route_prefix=context.route_prefix
route_prefix=context.route_prefix,
route_suffix=context.route_suffix,
)
configurator.basepath = context.basepath
configurator.includepath = context.includepath
Expand All @@ -798,6 +823,7 @@ def with_package(self, package):
package=package,
autocommit=self.autocommit,
route_prefix=self.route_prefix,
route_suffix=self.route_suffix,
)
configurator.basepath = self.basepath
configurator.includepath = self.includepath
Expand Down
8 changes: 7 additions & 1 deletion pyramid/config/routes.py
Expand Up @@ -15,6 +15,7 @@
action_method,
make_predicates,
as_sorted_tuple,
route_pattern,
)

class RoutesConfiguratorMixin(object):
Expand Down Expand Up @@ -368,8 +369,13 @@ def add_route(self,
if pattern is None:
raise ConfigurationError('"pattern" argument may not be None')

pattern = route_pattern(
(self.route_prefix or []) + [pattern] + (self.route_suffix or [])
)
if self.route_prefix:
pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/')
pattern.match_slash_style = True

pattern = str(pattern)

mapper = self.get_routes_mapper()

Expand Down
26 changes: 26 additions & 0 deletions pyramid/config/util.py
Expand Up @@ -291,3 +291,29 @@ def as_sorted_tuple(val):
val = tuple(sorted(val))
return val

class route_pattern(list):
""" Utility: centralise route-pattern operations.

If ``match_slash_style`` is ``True`` then the slash-style of the first-item
determines the slash-style of the route-pattern; if the first-item is
slash-appended then the route-pattern will be slash-appended, otherwise the
route-pattern will be non-slash-appended. """

def __init__(self, *args):
super(route_pattern, self).__init__(*args)
self.match_slash_style = False

def __str__(self):
l = None
for r in self:
if l is not None and self.match_slash_style:
r = r.rstrip('/')
if l.endswith('/'):
r = '%s/' % r
if l is None:
l = ''
if l and r:
l = '%s/%s' % (l.rstrip('/'), r.lstrip('/'))
else:
l = l + r
return l