Search code examples
google-app-enginewebapp2

Advanced Webapp2 Routing


In old school webapp, app.yaml based routing allowed you to check for regular expressions and if matched, process a request by a handler whose name is based on the found pattern. For example \1_handler.py would dispatch to user_handler.py if the matched pattern was 'user'.

Is there a way to do the same thing with webapp2.Route? Can the lazy handler or the method_handler parameters be based on the matched patterns in template?


Solution

  • webapp2 does not allow to route like that. I think the most reasonable solution is to write a custom dispatcher for webapp2.Router.

    It is possible to set a custom dispatcher like this:

    app = WSGIApplication(...)
    app.router.set_dispatcher(custom_dispatcher)
    

    Here is a not tested sketch for dispatcher, code is based on webapp2.Router.default_dispatcher:

    from webapp2 import import_string
    
    def custom_dispatcher(router, request, response):
        route, args, kwargs = rv = router.match(request)
        request.route, request.route_args, request.route_kwargs = rv
    
        handler = route.handler
        if isinstance(handler, basestring):
    
            handler, args, kwargs = _parse_handler_template(handler, args, kwargs)
    
            if handler not in self.handlers:
                router.handlers[handler] = handler = import_string(handler)
            else:
                handler = router.handlers[handler]
    
        return router.adapt(handler)(request, response)
    
    def _parse_handler_template(handler, args, kwargs):
        """replace {key} in `handler` with values from `args` or `kwargs`.
        Replaced values are removed from args/kwargs."""
        args = list(args)
        kwargs = dict(kwargs)
        def sub(match):
            if kwargs:
                return kwargs.pop(match.group().strip('{}'))
            else:
                return args.pop(int(match.group().strip('{}'))
        return re.sub('{.*?}', sub, handler), args, kwargs
    

    This code should allow to register a rules like this:

     app = WSGIApplication([
         (r'module/<module>/<action>/<argument>', 'modules.{module}.action_{action}'),
     ])
    

    This example does not allow to use variables from pattern in method name, for example: module.Class:action_{method}. In Route class this endpoint is split by semicolon and values stored in route.method_name and route.handler.