Source code for glass.routing

import re
from urllib.parse import (
    urlparse, urlunparse, urlencode,quote as urlquote)
import types
from glass.exception import HTTP404, MethodNotAllow
from glass.requests import request
from ._helpers import current_app as app

RULE_REGEX = re.compile(r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>\w+)>')

CONVERTERS_REGEX = {'int': r'\d+', 'path': r'.+', 'str': r'[^/]+'}

CONVERTERS = {'int': int, 'str': str, 'path': str}


class ParamConverter:
    def __init__(self, name, converter):
        self.param_name = name
        regex = CONVERTERS_REGEX[converter]
        self.regex = re.compile(regex)
        self.func = CONVERTERS[converter]
        self.converter_name = converter

    def __call__(self, *args):
        return self.func(*args)

    def __repr__(self):
        return '<Param %s --> %s' % (self.param_name, self.func)


class Rule:
    def __init__(self, rule, callback=None, methods=None, **kw):
        self.url_rule = rule
        self.callback = callback
        self.methods = methods or []
        self.converter = {}
        self.params = {}
        self.regex = ''

    def __repr__(self):
        return '<Rule %s --> %s, {%s}' % (self.url_rule, self.callback,
                                          ', '.join(self.methods))

    def get_callback(self, request_method=''):
        if not self.methods or not request_method:
            return self.callback
        if request_method not in self.methods:
            raise MethodNotAllow()
        return self.callback

    def __call__(self, *kwargs):
        return self.callback(**kwargs)

    def build(self, **kwargs):
        if not self.regex:
            raise TypeError
        out = self.url_rule
        missing_args = set(self.params) - set(kwargs)
        if missing_args:
            raise TypeError("Rule (%s) missing required parameters %s " %
                            (self.url_rule, missing_args))
        for _, param_converter in self.params.items():
            try:
                value = str(kwargs.pop(param_converter.param_name))
            except KeyError:
                raise
            # if not param_converter.regex.match(value):
            #    pass
            sub = '<(%s:)?%s>' % (param_converter.converter_name,
                                  param_converter.param_name)
            pattern = re.compile(sub)
            out = pattern.sub(value, out)
        return out


class Router:
    def __init__(self, app=None):
        self.rules = []
        self._url_caches = {}

    def compile(self, rule):
        '''compile url rule to regex
        return the rule regex and converters

        credit: github.com/django/django

        '''
        original_route = rule
        parts = ['^']
        converters = {}
        while True:
            match = RULE_REGEX.search(rule)
            if not match:
                parts.append(re.escape(rule))
                break
            parts.append(re.escape(rule[:match.start()]))
            rule = rule[match.end():]
            parameter = match.group('parameter')
            if not parameter.isidentifier():
                raise TypeError('invalid identifier (%s) in URL rule %s' %
                                (parameter, original_route))
            converter = match.group('converter')
            if converter is None:
                # no converter, default is str
                converter = 'str'
            try:
                regex = CONVERTERS_REGEX[converter]
            except KeyError:
                raise TypeError("unknown converter %s for the rule '%s'" %
                                (converter, original_route))
            param_converter = ParamConverter(parameter, converter)
            converters[parameter] = param_converter
            parts.append('(?P<' + parameter + '>' + regex + ')')
        if original_route.endswith('/'):
            parts.append('?')
        parts.append('$')
        return ''.join(parts), converters

    def add(self, rule):
        '''add new url rule'''
        regex, params = self.compile(rule.url_rule)
        rule.converter = dict((k, v.func) for k, v in params.items())
        regex = re.compile(regex)
        rule.regex = regex
        rule.params = params
        self.rules.append((rule, regex))

    def match(self, environ):
        path = environ["PATH_INFO"]
        rule, view_kwargs = self._url_caches.get(path, (None, None))
        if rule:
            return rule, view_kwargs
        for rule, regex in self.rules:
            match = regex.match(path)
            if match:
                kwargs = match.groupdict()
                kwargs = self.apply_converter(kwargs, rule.converter)
                if not kwargs:
                    # static url rule,
                    # example: /login/,/user/reset/ ...
                    # cache it to avoid searching next time
                    self._url_caches[path] = (rule, kwargs)

                return rule, kwargs
        raise HTTP404()

    def apply_converter(self, view_kwargs, converters):
        '''apply converter to url rule
        if the url rule == '/<str:user>/<int:user_id>'
        for this url '/horlar/1',
        the url parameter and value is
        {'user':'horlar','user_id':'1'}
        converters map : {'user_id':int,'user':str}
        when the converters are applied
        the parameter and value now seem to be
        {'user':str('horlar'),'user_id':int('1')}
        '''
        applied = {}
        for param, value in view_kwargs.items():
            func = converters.get(param)
            if not func:
                applied[param] = value
                continue
            applied[param] = func(value)
        return applied

    def add_converter(self, name, regex, func):
        try:
            re.compile(regex)
        except re.error:
            raise ValueError('bad re syntax %s' % regex)
        CONVERTERS_REGEX[name] = regex
        CONVERTERS[name] = func


[docs]def url_for(view_name, **kwargs): '''Build url for a view :: @app.route('/u/login') def login(): return "Hello" # url_for('login') :param view_name: name of the url view Required arguments. arguments for the target url. ``/u/<id>/<username>/`` url_for('view',id=58,username='user') Optional arguments. Note, optional arguments start with ``_`` :param _scheme: url scheme (``http``, ``https`` or other) if not given, scheme is determined in this order. 1 . from app configuration, app.config['SERVER_NAME'] = ``'http://domain.com'`` 2. default to 'http'. :param _fragment: value after ``#`` in url ``http://domai.com/a/a#target``. default to '',no fragment. :param _target: same as ``_fragment`` Other arguments provided will be used as query string for the url. :: path = url_for('login',q="1",x="2",y="3")`` # /u/login?q=1&x=2&y=3 .. versionadded:: 0.0.3 ''' if isinstance(view_name, types.FunctionType): view_name = view_name.__name__ rule = app.view_func.get(view_name) if not rule: raise LookupError('Endpoint with view name "%s" not found' % view_name) path = rule.build(**kwargs) for param in rule.params: kwargs.pop(param, None) server_name = app.config['SERVER_NAME'] if server_name is None: server_name = '' fragment = kwargs.pop('_fragment', '') if not fragment: fragment = kwargs.pop('_target', '') scheme = kwargs.pop('_scheme', '') uri = urlparse(server_name) netloc = uri.netloc scheme = scheme or uri.scheme or 'http' if not netloc and uri.path: if not uri.path.startswith('/'): # /www.domain.com is consider as path netloc = uri.path[:-1] if uri.path.endswith('/') else uri.path # urlparse('www.domain.com') # urllib parse this as path and not netloc if not netloc and scheme: scheme = '' query_string = urlencode(kwargs) url = urlunparse((scheme, netloc, urlquote(path), '', query_string, fragment)) return url