Add router subrouters #2
@@ -12,7 +12,7 @@ from .interfaces.serialize_selector import SerializeSelector
|
|||||||
from .interfaces.serialized import SerializedResponse, SerializedRequest
|
from .interfaces.serialized import SerializedResponse, SerializedRequest
|
||||||
from .interfaces.serialized.text import TextSerializedResponse
|
from .interfaces.serialized.text import TextSerializedResponse
|
||||||
from .req_schema import UnwrappedRequest
|
from .req_schema import UnwrappedRequest
|
||||||
from .router import Router
|
from .router import Router, Route
|
||||||
from .types import HandlerType, InternalHandlerType, ContentType
|
from .types import HandlerType, InternalHandlerType, ContentType
|
||||||
from .internal_types import Scope, Receive, Send, MethodType, QTYPE, BTYPE, PTYPE
|
from .internal_types import Scope, Receive, Send, MethodType, QTYPE, BTYPE, PTYPE
|
||||||
from breakshaft.convertor import ConvRepo
|
from breakshaft.convertor import ConvRepo
|
||||||
@@ -26,6 +26,9 @@ class ASGIApp(Protocol):
|
|||||||
def route(self, method: MethodType, path_pattern: str):
|
def route(self, method: MethodType, path_pattern: str):
|
||||||
raise RuntimeError('stub!')
|
raise RuntimeError('stub!')
|
||||||
|
|
||||||
|
def add_subroute(self, subr: Route | Router, basepath: str) -> None:
|
||||||
|
self.router.add_subroute(subr, basepath)
|
||||||
|
|
||||||
|
|
||||||
class HTTPApp(ASGIApp):
|
class HTTPApp(ASGIApp):
|
||||||
serialize_selector: SerializeSelector
|
serialize_selector: SerializeSelector
|
||||||
|
|||||||
@@ -15,10 +15,19 @@ class Route:
|
|||||||
regexp_subroutes: list[tuple[Pattern, list[str], Route]]
|
regexp_subroutes: list[tuple[Pattern, list[str], Route]]
|
||||||
handler: dict[MethodType, InternalHandlerType]
|
handler: dict[MethodType, InternalHandlerType]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self,
|
||||||
self.static_subroutes = {}
|
static_subroutes: Optional[dict[str, Route]] = None,
|
||||||
self.regexp_subroutes = []
|
regexp_subroutes: Optional[list[tuple[Pattern, list[str], Route]]] = None,
|
||||||
self.handler = {}
|
handler: Optional[dict[MethodType, InternalHandlerType]] = None) -> None:
|
||||||
|
if static_subroutes is None:
|
||||||
|
static_subroutes = {}
|
||||||
|
if regexp_subroutes is None:
|
||||||
|
regexp_subroutes = []
|
||||||
|
if handler is None:
|
||||||
|
handler = {}
|
||||||
|
self.static_subroutes = static_subroutes
|
||||||
|
self.regexp_subroutes = regexp_subroutes
|
||||||
|
self.handler = handler
|
||||||
|
|
||||||
def _find_regexp_subroute(self, p: Pattern) -> Optional[tuple[list[str], Route]]:
|
def _find_regexp_subroute(self, p: Pattern) -> Optional[tuple[list[str], Route]]:
|
||||||
for _p, _n, _r in self.regexp_subroutes:
|
for _p, _n, _r in self.regexp_subroutes:
|
||||||
@@ -31,10 +40,9 @@ class Route:
|
|||||||
self.regexp_subroutes.append((p, names, r))
|
self.regexp_subroutes.append((p, names, r))
|
||||||
self.regexp_subroutes.sort(key=lambda x: len(x[0].pattern), reverse=True)
|
self.regexp_subroutes.sort(key=lambda x: len(x[0].pattern), reverse=True)
|
||||||
|
|
||||||
def add(self, method: MethodType, sequence: Sequence[str], handler: InternalHandlerType) -> None:
|
def walk_into_route(self, sequence: Sequence[str]) -> Route:
|
||||||
if len(sequence) == 0:
|
if len(sequence) == 0:
|
||||||
self.handler[method] = handler
|
return self
|
||||||
return
|
|
||||||
|
|
||||||
part = sequence[0]
|
part = sequence[0]
|
||||||
|
|
||||||
@@ -57,8 +65,18 @@ class Route:
|
|||||||
if subroute is None:
|
if subroute is None:
|
||||||
subroute = Route()
|
subroute = Route()
|
||||||
self.static_subroutes[part] = subroute
|
self.static_subroutes[part] = subroute
|
||||||
|
return subroute.walk_into_route(sequence[1:])
|
||||||
|
|
||||||
subroute.add(method, sequence[1:], handler)
|
def add(self,
|
||||||
|
method: MethodType,
|
||||||
|
sequence: Sequence[str],
|
||||||
|
handler: InternalHandlerType) -> None:
|
||||||
|
if len(sequence) == 0:
|
||||||
|
self.handler[method] = handler
|
||||||
|
return
|
||||||
|
|
||||||
|
subroute = self.walk_into_route(sequence)
|
||||||
|
subroute.handler[method] = handler
|
||||||
|
|
||||||
def get(self, method: MethodType, sequence: Sequence[str]) -> tuple[dict[str, str], Optional[InternalHandlerType]]:
|
def get(self, method: MethodType, sequence: Sequence[str]) -> tuple[dict[str, str], Optional[InternalHandlerType]]:
|
||||||
if len(sequence) == 0:
|
if len(sequence) == 0:
|
||||||
@@ -90,6 +108,32 @@ class Route:
|
|||||||
submatches |= matches
|
submatches |= matches
|
||||||
return submatches, handler
|
return submatches, handler
|
||||||
|
|
||||||
|
def compose_routes(self, other: Route) -> Route:
|
||||||
|
new_static_subroutes = self.static_subroutes.copy()
|
||||||
|
for k, v in other.static_subroutes.items():
|
||||||
|
old_r = new_static_subroutes.get(k)
|
||||||
|
if old_r is not None:
|
||||||
|
v = old_r.compose_routes(v)
|
||||||
|
new_static_subroutes[k] = v
|
||||||
|
|
||||||
|
new_regexp_subroutes = self.regexp_subroutes.copy()
|
||||||
|
|
||||||
|
for p, n, r in other.regexp_subroutes:
|
||||||
|
found = False
|
||||||
|
for i, (_p, _n, _r) in enumerate(new_regexp_subroutes):
|
||||||
|
if p == _p:
|
||||||
|
new_regexp_subroutes[i] = (_p, n, _r.compose_routes(r))
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
new_regexp_subroutes.append((p, n, r))
|
||||||
|
|
||||||
|
return Route(
|
||||||
|
new_static_subroutes,
|
||||||
|
new_regexp_subroutes,
|
||||||
|
self.handler | other.handler
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Router:
|
class Router:
|
||||||
_root: Route
|
_root: Route
|
||||||
@@ -132,3 +176,25 @@ class Router:
|
|||||||
if h is None:
|
if h is None:
|
||||||
raise NotFoundException(path or '/')
|
raise NotFoundException(path or '/')
|
||||||
return h
|
return h
|
||||||
|
|
||||||
|
def add_subroute(self, subr: Route | Router, basepath: str):
|
||||||
|
if isinstance(subr, Router):
|
||||||
|
subr = subr._root
|
||||||
|
|
||||||
|
segments = basepath.split('/')
|
||||||
|
while len(segments) > 0 and len(segments[0]) == 0:
|
||||||
|
segments = segments[1:]
|
||||||
|
if len(segments) > 1:
|
||||||
|
tgt = self._root.walk_into_route(segments[:-1])
|
||||||
|
old_tail = tgt.static_subroutes.get(segments[-1])
|
||||||
|
if old_tail is not None:
|
||||||
|
subr = old_tail.compose_routes(subr)
|
||||||
|
|
||||||
|
tgt.static_subroutes[segments[-1]] = subr
|
||||||
|
elif len(segments) == 1:
|
||||||
|
old_tail = self._root.static_subroutes.get(segments[-1])
|
||||||
|
if old_tail is not None:
|
||||||
|
subr = old_tail.compose_routes(subr)
|
||||||
|
self._root.static_subroutes[segments[0]] = subr
|
||||||
|
else:
|
||||||
|
self._root = self._root.compose_routes(subr)
|
||||||
|
|||||||
@@ -79,3 +79,32 @@ def test_router_pattern_match():
|
|||||||
|
|
||||||
with pytest.raises(NotFoundException, match='404\tNot Found: asd'):
|
with pytest.raises(NotFoundException, match='404\tNot Found: asd'):
|
||||||
r.match('GET', 'asd/basdf')
|
r.match('GET', 'asd/basdf')
|
||||||
|
|
||||||
|
|
||||||
|
def test_subroutes():
|
||||||
|
async def f(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def d(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
r1 = Router()
|
||||||
|
r2 = Router()
|
||||||
|
|
||||||
|
r1.add('GET', '/asdf', f)
|
||||||
|
r2.add('GET', '/asdf/a', d)
|
||||||
|
r1.add_subroute(r2, '')
|
||||||
|
|
||||||
|
assert r1.match('GET', '/asdf') == ({}, f)
|
||||||
|
assert r1.match('GET', '/asdf/a') == ({}, d)
|
||||||
|
r1.add_subroute(r2, '/asdf')
|
||||||
|
assert r1.match('GET', '/asdf/asdf/a') == ({}, d)
|
||||||
|
|
||||||
|
r1.add_subroute(r2, '/asdf' * 5)
|
||||||
|
assert r1.match('GET', '/asdf' * 5 + '/asdf/a') == ({}, d)
|
||||||
|
|
||||||
|
with pytest.raises(NotFoundException):
|
||||||
|
r1.match('GET', '/asdf/' * 5 + '/asdf/a')
|
||||||
|
|
||||||
|
r1.add_subroute(r2, '/asdf/' * 5)
|
||||||
|
assert r1.match('GET', '/asdf/' * 5 + '/asdf/a') == ({}, d)
|
||||||
|
|||||||
Reference in New Issue
Block a user