diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 477554dd5d3..87ccfc0a1bd 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -137,6 +137,7 @@ Paulus Schoutsen Pavel Kamaev Pawel Miech Philipp A. +Pieter van Beek Rafael Viotti Raúl Cumplido Required Field diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 910f346e2c8..f3d683529a4 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -31,6 +31,7 @@ 'StaticResource', 'View') HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$") +ROUTE_RE = re.compile(r'(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})') PATH_SEP = re.escape('/') @@ -330,11 +331,43 @@ def __repr__(self): class DynamicResource(Resource): - def __init__(self, pattern, formatter, *, name=None): + DYN = re.compile(r'\{(?P[_a-zA-Z][_a-zA-Z0-9]*)\}') + DYN_WITH_RE = re.compile( + r'\{(?P[_a-zA-Z][_a-zA-Z0-9]*):(?P.+)\}') + GOOD = r'[^{}/]+' + + def __init__(self, path, *, name=None): super().__init__(name=name) - assert pattern.pattern.startswith(PATH_SEP) + pattern = '' + formatter = '' + for part in ROUTE_RE.split(path): + match = self.DYN.fullmatch(part) + if match: + pattern += '(?P<{}>{})'.format(match.group('var'), self.GOOD) + formatter += '{' + match.group('var') + '}' + continue + + match = self.DYN_WITH_RE.fullmatch(part) + if match: + pattern += '(?P<{var}>{re})'.format(**match.groupdict()) + formatter += '{' + match.group('var') + '}' + continue + + if '{' in part or '}' in part: + raise ValueError("Invalid path '{}'['{}']".format(path, part)) + + path = URL(part).raw_path + formatter += path + pattern += re.escape(path) + + try: + compiled = re.compile(pattern) + except re.error as exc: + raise ValueError( + "Bad pattern '{}': {}".format(pattern, exc)) from None + assert compiled.pattern.startswith(PATH_SEP) assert formatter.startswith('/') - self._pattern = pattern + self._pattern = compiled self._formatter = formatter def add_prefix(self, prefix): @@ -702,11 +735,6 @@ def __contains__(self, route): class UrlDispatcher(AbstractRouter, collections.abc.Mapping): - DYN = re.compile(r'\{(?P[_a-zA-Z][_a-zA-Z0-9]*)\}') - DYN_WITH_RE = re.compile( - r'\{(?P[_a-zA-Z][_a-zA-Z0-9]*):(?P.+)\}') - GOOD = r'[^{}/]+' - ROUTE_RE = re.compile(r'(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})') NAME_SPLIT_RE = re.compile(r'[.:-]') def __init__(self): @@ -781,40 +809,12 @@ def register_resource(self, resource): def add_resource(self, path, *, name=None): if path and not path.startswith('/'): raise ValueError("path should be started with / or be empty") - if not ('{' in path or '}' in path or self.ROUTE_RE.search(path)): + if not ('{' in path or '}' in path or ROUTE_RE.search(path)): url = URL(path) resource = PlainResource(url.raw_path, name=name) self.register_resource(resource) return resource - - pattern = '' - formatter = '' - for part in self.ROUTE_RE.split(path): - match = self.DYN.fullmatch(part) - if match: - pattern += '(?P<{}>{})'.format(match.group('var'), self.GOOD) - formatter += '{' + match.group('var') + '}' - continue - - match = self.DYN_WITH_RE.fullmatch(part) - if match: - pattern += '(?P<{var}>{re})'.format(**match.groupdict()) - formatter += '{' + match.group('var') + '}' - continue - - if '{' in part or '}' in part: - raise ValueError("Invalid path '{}'['{}']".format(path, part)) - - path = URL(part).raw_path - formatter += path - pattern += re.escape(path) - - try: - compiled = re.compile(pattern) - except re.error as exc: - raise ValueError( - "Bad pattern '{}': {}".format(pattern, exc)) from None - resource = DynamicResource(compiled, formatter, name=name) + resource = DynamicResource(path, name=name) self.register_resource(resource) return resource