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