This commit is contained in:
Suby Raman
2017-02-02 12:21:14 -05:00
parent 6f0b09509e
commit 7c09ec29f7
6 changed files with 369 additions and 12 deletions

View File

@@ -35,6 +35,9 @@ class Blueprint:
# Routes
for future in self.routes:
# attach the blueprint name to the handler so that it can be
# prefixed properly in the router
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
app.route(

View File

@@ -120,6 +120,10 @@ class ServerError(SanicException):
status_code = 500
class URLBuildError(SanicException):
status_code = 500
class FileNotFound(NotFound):
status_code = 404

View File

@@ -4,8 +4,8 @@ from functools import lru_cache
from .exceptions import NotFound, InvalidUsage
from .views import CompositionView
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
Parameter = namedtuple('Parameter', ['name', 'cast'])
Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters', 'name'])
Parameter = namedtuple('Parameter', ['name', 'cast', 'pattern'])
REGEX_TYPES = {
'string': (str, r'[^/]+'),
@@ -59,6 +59,7 @@ class Router:
routes_static = None
routes_dynamic = None
routes_always_check = None
parameter_pattern = re.compile(r'<(.+?)>')
def __init__(self):
self.routes_all = {}
@@ -67,6 +68,19 @@ class Router:
self.routes_always_check = []
self.hosts = None
def parse_parameter_string(self, parameter_string):
# We could receive NAME or NAME:PATTERN
name = parameter_string
pattern = 'string'
if ':' in parameter_string:
name, pattern = parameter_string.split(':', 1)
default = (str, pattern)
# Pull from pre-configured types
_type, pattern = REGEX_TYPES.get(pattern, default)
return name, _type, pattern
def add(self, uri, methods, handler, host=None):
"""
Adds a handler to the route list
@@ -106,14 +120,13 @@ class Router:
def add_parameter(match):
# We could receive NAME or NAME:PATTERN
name = match.group(1)
pattern = 'string'
if ':' in name:
name, pattern = name.split(':', 1)
name, _type, pattern = self.parse_parameter_string(name)
default = (str, pattern)
# Pull from pre-configured types
_type, pattern = REGEX_TYPES.get(pattern, default)
parameter = Parameter(name=name, cast=_type)
# store a regex for matching on a specific parameter
# this will be useful for URL building
specific_parameter_pattern = '^{}$'.format(pattern)
parameter = Parameter(
name=name, cast=_type, pattern=specific_parameter_pattern)
parameters.append(parameter)
# Mark the whole route as unhashable if it has the hash key in it
@@ -125,7 +138,7 @@ class Router:
return '({})'.format(pattern)
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
pattern_string = re.sub(self.parameter_pattern, add_parameter, uri)
pattern = re.compile(r'^{}$'.format(pattern_string))
def merge_route(route, methods, handler):
@@ -169,9 +182,17 @@ class Router:
if route:
route = merge_route(route, methods, handler)
else:
# prefix the handler name with the blueprint name
# if available
if hasattr(handler, '__blueprintname__'):
handler_name = '{}.{}'.format(
handler.__blueprintname__, handler.__name__)
else:
handler_name = handler.__name__
route = Route(
handler=handler, methods=methods, pattern=pattern,
parameters=parameters)
parameters=parameters, name=handler_name)
self.routes_all[uri] = route
if properties['unhashable']:
@@ -208,6 +229,14 @@ class Router:
if clean_cache:
self._get.cache_clear()
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
def find_route_by_view_name(self, view_name):
for uri, route in self.routes_all.items():
if route.name == view_name:
return uri, route
return (None, None)
def get(self, request):
"""
Gets a request handler based on the URL of the request, or raises an

View File

@@ -3,13 +3,15 @@ from asyncio import get_event_loop
from collections import deque
from functools import partial
from inspect import isawaitable, stack, getmodulename
import re
from traceback import format_exc
from urllib.parse import urlencode, urlunparse
import warnings
from .config import Config
from .constants import HTTP_METHODS
from .exceptions import Handler
from .exceptions import ServerError
from .exceptions import ServerError, URLBuildError
from .log import log
from .response import HTTPResponse
from .router import Router
@@ -192,6 +194,63 @@ class Sanic:
DeprecationWarning)
return self.blueprint(*args, **kwargs)
def url_for(self, view_name: str, **kwargs):
uri, route = self.router.find_route_by_view_name(view_name)
if not uri or not route:
raise URLBuildError(
'Endpoint with name `{}` was not found'.format(
view_name))
out = uri
matched_params = re.findall(
self.router.parameter_pattern, uri)
for match in matched_params:
name, _type, pattern = self.router.parse_parameter_string(
match)
specific_pattern = '^{}$'.format(pattern)
supplied_param = None
if kwargs.get(name):
supplied_param = kwargs.get(name)
del kwargs[name]
else:
raise URLBuildError(
'Required parameter `{}` was not passed to url_for'.format(
name))
supplied_param = str(supplied_param)
passes_pattern = re.match(specific_pattern, supplied_param)
if not passes_pattern:
if _type != str:
msg = (
'Value "{}" for parameter `{}` does not '
'match pattern for type `{}`: {}'.format(
supplied_param, name, _type.__name__, pattern))
else:
msg = (
'Value "{}" for parameter `{}` '
'does not satisfy pattern {}'.format(
supplied_param, name, pattern))
raise URLBuildError(msg)
replacement_regex = '(<{}.*?>)'.format(name)
out = re.sub(
replacement_regex, supplied_param, out)
# parse the remainder of the keyword arguments into a querystring
if kwargs:
query_string = urlencode(kwargs)
out = urlunparse((
'', '', out,
'', query_string, ''
))
return out
# -------------------------------------------------------------------- #
# Request Handling
# -------------------------------------------------------------------- #

View File

@@ -64,6 +64,7 @@ class HTTPMethodView:
view.view_class = cls
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__name__ = cls.__name__
return view