Skip to content

Commit 540d0ce

Browse files
committed
rework based on feedback
1 parent 54d6776 commit 540d0ce

File tree

6 files changed

+51
-25
lines changed

6 files changed

+51
-25
lines changed

idom_router/__init__.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
# the version is statically loaded by setup.py
22
__version__ = "0.0.1"
33

4-
from idom_router.types import Route, RouteCompiler, RoutePattern
4+
from idom_router.types import Route, RouteCompiler, RouteResolver
55

6-
from .router import link, router, use_params, use_query
6+
from .core import link, create_router, router_component, use_params, use_query
77

88
__all__ = [
9+
"create_router",
910
"link",
1011
"Route",
1112
"RouteCompiler",
12-
"RoutePattern",
13-
"router",
13+
"router_component",
14+
"RouteResolver",
1415
"use_location",
1516
"use_params",
1617
"use_query",

idom_router/router.py renamed to idom_router/core.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,32 @@
1919
from idom.types import ComponentType, Context, Location
2020
from idom.web.module import export, module_from_file
2121

22-
from idom_router.compilers import compile_starlette_route
23-
from idom_router.types import Route, RouteCompiler, RoutePattern
22+
from idom_router.types import Route, RouteCompiler, RouteResolver, Router
2423

2524
R = TypeVar("R", bound=Route)
2625

2726

27+
def create_router(compiler: RouteCompiler[R]) -> Router[R]:
28+
"""A decorator that turns a route compiler into a router"""
29+
30+
def wrapper(*routes: R) -> ComponentType:
31+
return router_component(*routes, compiler=compiler)
32+
33+
return wrapper
34+
35+
2836
@component
29-
def router(
37+
def router_component(
3038
*routes: R,
31-
compiler: RouteCompiler[R] = compile_starlette_route,
39+
compiler: RouteCompiler[R],
3240
) -> ComponentType | None:
3341
old_conn = use_connection()
3442
location, set_location = use_state(old_conn.location)
43+
router_state = use_context(_route_state_context)
44+
45+
46+
if router_state is not None:
47+
raise RuntimeError("Another router is already active in this context")
3548

3649
# Memoize the compiled routes and the match separately so that we don't
3750
# recompile the routes on renders where only the location has changed
@@ -87,7 +100,7 @@ def use_query(
87100

88101
def _compile_routes(
89102
routes: Sequence[R], compiler: RouteCompiler[R]
90-
) -> list[tuple[Any, RoutePattern]]:
103+
) -> list[tuple[Any, RouteResolver]]:
91104
return [(r, compiler(r)) for r in _iter_routes(routes)]
92105

93106

@@ -99,7 +112,7 @@ def _iter_routes(routes: Sequence[R]) -> Iterator[R]:
99112

100113

101114
def _match_route(
102-
compiled_routes: list[tuple[R, RoutePattern]], location: Location
115+
compiled_routes: list[tuple[R, RouteResolver]], location: Location
103116
) -> tuple[R, dict[str, Any]] | None:
104117
for route, pattern in compiled_routes:
105118
params = pattern.match(location.pathname)

idom_router/routers/__init__.py

Whitespace-only changes.

tests/utils.py renamed to idom_router/routers/regex.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
from __future__ import annotations
22

33
import re
4+
from uuid import UUID
45
from typing import Any, Callable
56

67
from idom_router import Route
78

89

9-
def compile_simple_regex_route(route: Route) -> RegexRoutePattern:
10+
def compile_regex_route(route: Route) -> RegexRoutePattern:
1011
"""Compile simple regex route.
1112
1213
Named regex groups can end with a `__type` suffix to specify a type converter
1314
1415
For example, `(?P<id__int>[0-9]+)` will convert the `id` parameter to an `int`.
1516
16-
Supported types are `int`, `float`, and `list` where `list` will split on `,`.
17+
Supported types are `int`, `float`, and `uuid`.
1718
"""
1819
pattern = re.compile(route.path)
1920
return RegexRoutePattern(pattern)
@@ -41,7 +42,7 @@ def match(self, path: str) -> dict[str, str] | None:
4142
CONVERTERS: dict[str, Callable[[str], Any]] = {
4243
"int": int,
4344
"float": float,
44-
"list": lambda s: s.split(","),
45+
"uuid": UUID,
4546
}
4647

4748

idom_router/compilers.py renamed to idom_router/routers/starlette.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
from typing import Any
55

66
from starlette.convertors import Convertor
7-
from starlette.routing import compile_path as _compile_starlette_path
7+
from starlette.routing import compile_path
88

99
from idom_router.types import Route
10+
from idom_router.core import create_router
1011

1112

12-
def compile_starlette_route(route: Route) -> StarletteRoutePattern:
13-
pattern, _, converters = _compile_starlette_path(route.path)
14-
return StarletteRoutePattern(pattern, converters)
13+
def compile_starlette_route(route: Route) -> StarletteRouteResolver:
14+
pattern, _, converters = compile_path(route.path)
15+
return StarletteRouteResolver(pattern, converters)
1516

1617

17-
class StarletteRoutePattern:
18+
class StarletteRouteResolver:
1819
def __init__(
1920
self,
2021
pattern: re.Pattern[str],
@@ -32,3 +33,6 @@ def match(self, path: str) -> dict[str, Any] | None:
3233
for k, v in match.groupdict().items()
3334
}
3435
return None
36+
37+
38+
starlette_router = create_router(compile_starlette_route)

idom_router/types.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from typing import Any, Sequence, TypeVar
55

6-
from idom.types import Key
6+
from idom.types import Key, ComponentType
77
from typing_extensions import Protocol, Self
88

99

@@ -26,18 +26,25 @@ def __init__(
2626
self.routes = (*routes_, *routes)
2727

2828

29+
class Router(Protocol):
30+
def __call__(self, *routes: Route) -> ComponentType:
31+
"""Return a component that renders the first matching route"""
32+
33+
2934
R = TypeVar("R", bound=Route, contravariant=True)
3035

3136

3237
class RouteCompiler(Protocol[R]):
33-
def __call__(self, route: R) -> RoutePattern:
34-
"""Compile a route into a pattern that can be matched against a path"""
38+
def __call__(self, route: R) -> RouteResolver:
39+
"""Compile a route into a resolver that can be matched against a path"""
40+
3541

42+
class RouteResolver(Protocol):
43+
"""A compiled route that can be matched against a path"""
3644

37-
class RoutePattern(Protocol):
3845
@property
3946
def key(self) -> Key:
40-
"""Uniquely identified this pattern"""
47+
"""Uniquely identified this resolver"""
4148

42-
def match(self, path: str) -> dict[str, Any] | None:
43-
"""Returns otherwise a dict of path parameters if the path matches, else None"""
49+
def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None:
50+
"""Return the path's associated element and path params or None"""

0 commit comments

Comments
 (0)