Beispiel #1
0
class Router(BaseRouter):

    def __init__(self, routes):
        rules = []
        name_lookups = {}

        for path, name, route in self.walk_routes(routes):
            path_params = [
                item.strip('{}') for item in re.findall('{[^}]*}', path)
            ]
            args = inspect.signature(route.handler).parameters
            for path_param in path_params:
                if path_param.startswith('+'):
                    path = path.replace(
                        '{%s}' % path_param,
                        "<path:%s>" % path_param.lstrip('+')
                    )
                elif path_param in args and args[path_param].annotation is int:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<int:%s>" % path_param
                    )
                elif path_param in args and args[path_param].annotation is float:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<float:%s>" % path_param
                    )
                else:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<string:%s>" % path_param
                    )

            rule = Rule(path, methods=[route.method], endpoint=name)
            rules.append(rule)
            name_lookups[name] = route

        self.adapter = Map(rules).bind('')
        self.name_lookups = name_lookups

        # Use an MRU cache for router lookups.
        self._lookup_cache = dict_type()
        self._lookup_cache_size = 10000

    def walk_routes(self, routes, url_prefix='', name_prefix=''):
        walked = []
        for item in routes:
            if isinstance(item, Route):
                result = (url_prefix + item.url, name_prefix + item.name, item)
                walked.append(result)
            elif isinstance(item, Include):
                result = self.walk_routes(
                    item.routes,
                    url_prefix + item.url,
                    name_prefix + item.name + ':'
                )
                walked.extend(result)
        return walked

    def lookup(self, path: str, method: str):
        lookup_key = method + ' ' + path
        try:
            return self._lookup_cache[lookup_key]
        except KeyError:
            pass

        try:
            name, path_params = self.adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound() from None
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed() from None
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path) from None

        route = self.name_lookups[name]

        self._lookup_cache[lookup_key] = (route, path_params)
        if len(self._lookup_cache) > self._lookup_cache_size:
            self._lookup_cache.pop(next(iter(self._lookup_cache)))

        return (route, path_params)

    def reverse_url(self, name: str, **params) -> str:
        try:
            return self.adapter.build(name, params)
        except werkzeug.routing.BuildError as exc:
            raise exceptions.NoReverseMatch(str(exc)) from None
Beispiel #2
0
class WerkzeugRouter(Router):
    def __init__(self, routes: RouteConfig) -> None:
        rules = []  # type: typing.List[Rule]
        views = {}  # type: typing.Dict[str, typing.Callable]

        for path, method, view, name in flatten_routes(routes):
            if name in views:
                msg = ('Route wtih name "%s" exists more than once. Use an '
                       'explicit name="..." on the Route to avoid a conflict.'
                       ) % name
                raise exceptions.ConfigurationError(msg)

            template = uritemplate.URITemplate(path)
            werkzeug_path = str(path)

            parameters = inspect.signature(view).parameters

            for arg in template.variable_names:
                converter = self._get_converter(parameters, arg, view)
                template_format = '{%s}' % arg
                werkzeug_format = '<%s:%s>' % (converter, arg)
                werkzeug_path = werkzeug_path.replace(template_format,
                                                      werkzeug_format)

            rule = Rule(werkzeug_path, methods=[method], endpoint=name)
            rules.append(rule)
            views[name] = view

        self._routes = routes
        self._adapter = Map(rules).bind('')
        self._views = views

        # Use an MRU cache for router lookups.
        self._lookup_cache = collections.OrderedDict(
        )  # type: collections.OrderedDict
        self._lookup_cache_size = 10000

    def _get_converter(self, parameters: typing.Mapping[str,
                                                        inspect.Parameter],
                       arg: str, view: typing.Callable) -> str:
        if arg not in parameters:
            msg = 'URL Argument "%s" missing from view "%s".' % (arg, view)
            raise exceptions.ConfigurationError(msg)

        annotation = parameters[arg].annotation

        if annotation is inspect.Parameter.empty:
            return 'string'
        elif issubclass(annotation, PathWildcard):
            return 'path'
        elif issubclass(annotation, str):
            return 'string'
        elif issubclass(annotation, int):
            return 'int'
        elif issubclass(annotation, float):
            return 'float'

        msg = 'Invalid type for path parameter "%s" in view "%s".' % (
            parameters[arg], view)
        raise exceptions.ConfigurationError(msg)

    def lookup(self, path: str, method: str) -> HandlerLookup:
        lookup_key = method + ' ' + path
        try:
            return self._lookup_cache[lookup_key]
        except KeyError:
            pass

        try:
            name, kwargs = self._adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound() from None
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed() from None
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path) from None

        view = self._views[name]

        self._lookup_cache[lookup_key] = (view, kwargs)
        if len(self._lookup_cache) > self._lookup_cache_size:
            self._lookup_cache.pop(next(iter(
                self._lookup_cache)))  # pragma: nocover

        return (view, kwargs)

    def reverse_url(self, identifier: str, values: dict = None) -> str:
        try:
            return self._adapter.build(identifier, values)
        except werkzeug.routing.BuildError as exc:
            raise exceptions.NoReverseMatch(str(exc)) from None