Skip to content

Commit

Permalink
Allow for regex partial matching
Browse files Browse the repository at this point in the history
  • Loading branch information
ahopkins committed Feb 17, 2021
1 parent c40e169 commit 3dfa590
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 5 deletions.
2 changes: 1 addition & 1 deletion sanic_routing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .route import Route
from .router import BaseRouter

__version__ = "0.2.0"
__version__ = "0.3.0"
__all__ = ("BaseRouter", "Route")
31 changes: 29 additions & 2 deletions sanic_routing/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import defaultdict, namedtuple
from types import SimpleNamespace

from .exceptions import ParameterNameConflicts, RouteExists
from .exceptions import InvalidUsage, ParameterNameConflicts, RouteExists
from .patterns import REGEX_TYPES
from .utils import Immutable, parts_to_path, path_to_parts

Expand Down Expand Up @@ -44,6 +44,7 @@ def __init__(
self.strict: bool = strict
self.unquote: bool = unquote
self.requirements: t.Dict[str, t.Any] = {}
self.labels: t.Optional[t.List[str]] = None

def __repr__(self):
display = (
Expand Down Expand Up @@ -137,6 +138,7 @@ def _finalize_params(self):
raise ParameterNameConflicts(
f"Duplicate named parameters in: {self._raw_path}"
)
self.labels = labels
self.params = params

def _finalize_methods(self):
Expand All @@ -158,7 +160,28 @@ def _compile_regex(self):
name, *_, pattern = self.parse_parameter_string(part)
if not isinstance(pattern, str):
pattern = pattern.pattern.strip("^$")
components.append(f"(?P<{name}>{pattern})")
compiled = re.compile(pattern)
if compiled.groups == 1:
if compiled.groupindex:
if list(compiled.groupindex)[0] != name:
raise InvalidUsage(
f"Named group ({list(compiled.groupindex)[0]})"
f" must match your named parameter ({name})"
)
components.append(pattern)
else:
if pattern.count("(") > 1:
raise InvalidUsage(
f"Could not compile pattern {pattern}. "
"Try using a named group instead: "
f"'(?P<{name}>your_matching_group)'"
)
beginning, end = pattern.split("(")
components.append(f"{beginning}(?P<{name}>{end}")
elif compiled.groups > 1:
raise InvalidUsage(f"Invalid matching pattern {pattern}")
else:
components.append(f"(?P<{name}>{pattern})")
else:
components.append(part)

Expand All @@ -176,6 +199,10 @@ def finalize(self):
def reset(self):
self._reset_handlers()

@property
def raw_path(self):
return self._raw_path

@staticmethod
def _sorting(item) -> int:
try:
Expand Down
5 changes: 4 additions & 1 deletion sanic_routing/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ def _render(self, do_compile: bool = True) -> None:
src += self.tree.render()

if self.regex_routes:
# TODO:
# - we should probably pre-compile the patterns and only
# include them here by reference
src += [
line
for route in self.regex_routes.values()
Expand Down Expand Up @@ -316,7 +319,7 @@ def requires(part):
if not part.startswith("<") or ":" not in part:
return False

_, pattern_type = part[1:-1].split(":")
_, pattern_type = part[1:-1].split(":", 1)

return (
part.endswith(":path>")
Expand Down
14 changes: 13 additions & 1 deletion sanic_routing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ def parse_parameter_basket(route, basket, raw_path=None):
break
elif p.pattern.search(value):
raw_path = p.raw_path
if "(" in p.pattern:
groups = p.pattern.match(value)
value = groups.group(1)
params[p.name] = p.cast(value)
break

Expand All @@ -44,7 +47,16 @@ def parse_parameter_basket(route, basket, raw_path=None):


def path_to_parts(path, delimiter="/"):
regex_path_parts = re.compile(f"(<.*?>|[^{delimiter}]+)")
"""
OK > /foo/<unhashable:[A-Za-z0-9/]+>
OK > /foo/<ext:file\.(?P<ext>txt)>/<ext:[a-z]> # noqa
OK > /foo/<user>/<user:str>
OK > /foo/<ext:[a-z]>/<ext:file\.(?P<ext>txt)d> # noqa
NOT OK > /foo/<ext:file\.(?P<ext>txt)d>/<ext:[a-z]> # noqa
"""
regex_path_parts = re.compile(
f"(<.*?(?<![a-z])>|<[a-z\:]+>|[^{delimiter}]+)" # noqa
)
parts = list(regex_path_parts.findall(unquote(path))) or [""]
if path.endswith(delimiter):
parts += [""]
Expand Down

0 comments on commit 3dfa590

Please sign in to comment.