Skip to content

Commit 0bd12d1

Browse files
authored
Support star pattern in URL router (#218)
1 parent e3d0866 commit 0bd12d1

File tree

6 files changed

+50
-26
lines changed

6 files changed

+50
-26
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Using the following categories, list your changes in this order:
3737
### Added
3838

3939
- An "offline component" can now be displayed when the client disconnects from the server.
40+
- URL router now supports a `*` wildcard to create default routes.
4041

4142
## [3.6.0] - 2024-01-10
4243

Diff for: docs/examples/python/django-router.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ def my_component():
1313
route("/router/slug/<slug:value>/", html.div("Example 5")),
1414
route("/router/string/<str:value>/", html.div("Example 6")),
1515
route("/router/uuid/<uuid:value>/", html.div("Example 7")),
16-
route("/router/two_values/<int:value>/<str:value2>/", html.div("Example 9")),
16+
route("/router/two_values/<int:value>/<str:value2>/", html.div("Example 8")),
17+
route("/router/*", html.div("Fallback")),
1718
)

Diff for: src/reactpy_django/router/__init__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
from reactpy_django.router.components import django_router
1+
from reactpy_router.core import create_router
22

3-
__all__ = ["django_router"]
3+
from reactpy_django.router.resolvers import DjangoResolver
4+
5+
django_router = create_router(DjangoResolver)

Diff for: src/reactpy_django/router/components.py renamed to src/reactpy_django/router/resolvers.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import re
44
from typing import Any
55

6-
from reactpy_router.core import create_router
76
from reactpy_router.simple import ConverterMapping
87
from reactpy_router.types import Route
98

@@ -33,6 +32,7 @@ def resolve(self, path: str) -> tuple[Any, dict[str, Any]] | None:
3332

3433
# TODO: Make reactpy_router's parse_path generic enough to where we don't have to define our own
3534
def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]:
35+
# Convert path to regex pattern, and make sure to interpret the registered converters (ex. <int:foo>)
3636
pattern = "^"
3737
last_match_end = 0
3838
converters: ConverterMapping = {}
@@ -50,7 +50,9 @@ def parse_path(path: str) -> tuple[re.Pattern[str], ConverterMapping]:
5050
converters[param_name] = param_conv["func"]
5151
last_match_end = match.end()
5252
pattern += f"{re.escape(path[last_match_end:])}$"
53-
return re.compile(pattern), converters
5453

54+
# Replace literal `*` with "match anything" regex pattern, if it's at the end of the path
55+
if pattern.endswith("\*$"):
56+
pattern = f"{pattern[:-3]}.*$"
5557

56-
django_router = create_router(DjangoResolver)
58+
return re.compile(pattern), converters

Diff for: tests/test_app/router/components.py

+23-20
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@
44

55

66
@component
7-
def display_params(*args):
8-
params = use_params()
9-
return html._(
10-
html.div(f"Params: {params}"),
11-
*args,
12-
)
13-
14-
15-
@component
16-
def main():
7+
def display_params(string: str):
178
location = use_location()
189
query = use_query()
10+
params = use_params()
1911

20-
route_info = html._(
12+
return html._(
13+
html.div({"id": "router-string"}, string),
14+
html.div(f"Params: {params}"),
2115
html.div(
2216
{"id": "router-path", "data-path": location.pathname},
2317
f"Path Name: {location.pathname}",
@@ -26,17 +20,26 @@ def main():
2620
html.div(f"Query: {query}"),
2721
)
2822

23+
24+
@component
25+
def main():
2926
return django_router(
30-
route("/router/", html.div("Path 1", route_info)),
31-
route("/router/any/<value>/", display_params("Path 2", route_info)),
32-
route("/router/integer/<int:value>/", display_params("Path 3", route_info)),
33-
route("/router/path/<path:value>/", display_params("Path 4", route_info)),
34-
route("/router/slug/<slug:value>/", display_params("Path 5", route_info)),
35-
route("/router/string/<str:value>/", display_params("Path 6", route_info)),
36-
route("/router/uuid/<uuid:value>/", display_params("Path 7", route_info)),
37-
route("/router/", None, route("abc/", display_params("Path 8", route_info))),
27+
route("/router/", display_params("Path 1")),
28+
route("/router/any/<value>/", display_params("Path 2")),
29+
route("/router/integer/<int:value>/", display_params("Path 3")),
30+
route("/router/path/<path:value>/", display_params("Path 4")),
31+
route("/router/slug/<slug:value>/", display_params("Path 5")),
32+
route("/router/string/<str:value>/", display_params("Path 6")),
33+
route("/router/uuid/<uuid:value>/", display_params("Path 7")),
34+
route("/router/", None, route("abc/", display_params("Path 8"))),
3835
route(
3936
"/router/two/<int:value>/<str:value2>/",
40-
display_params("Path 9", route_info),
37+
display_params("Path 9"),
38+
),
39+
route(
40+
"/router/star/",
41+
None,
42+
route("one/", display_params("Path 11")),
43+
route("*", display_params("Path 12")),
4144
),
4245
)

Diff for: tests/test_app/tests/test_components.py

+15
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,21 @@ def test_url_router(self):
606606
path = new_page.wait_for_selector("#router-path")
607607
self.assertIn("/router/two/123/abc/", path.get_attribute("data-path"))
608608

609+
new_page.goto(f"{self.live_server_url}/router/star/one/")
610+
path = new_page.wait_for_selector("#router-path")
611+
self.assertIn("/router/star/one/", path.get_attribute("data-path"))
612+
613+
new_page.goto(
614+
f"{self.live_server_url}/router/star/adslkjgklasdjhfah/6789543256/"
615+
)
616+
path = new_page.wait_for_selector("#router-path")
617+
self.assertIn(
618+
"/router/star/adslkjgklasdjhfah/6789543256/",
619+
path.get_attribute("data-path"),
620+
)
621+
string = new_page.query_selector("#router-string")
622+
self.assertEquals("Path 12", string.text_content())
623+
609624
finally:
610625
new_page.close()
611626

0 commit comments

Comments
 (0)