-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REF-2643]Throw Errors for duplicate Routes #3155
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
import multiprocessing | ||
import os | ||
import platform | ||
import re | ||
from typing import ( | ||
Any, | ||
AsyncIterator, | ||
|
@@ -63,8 +64,6 @@ | |
DECORATED_PAGES, | ||
) | ||
from reflex.route import ( | ||
catchall_in_route, | ||
catchall_prefix, | ||
get_route_args, | ||
verify_route_validity, | ||
) | ||
|
@@ -432,6 +431,9 @@ def add_page( | |
on_load: The event handler(s) that will be called each time the page load. | ||
meta: The metadata of the page. | ||
script_tags: List of script tags to be added to component | ||
|
||
Raises: | ||
ValueError: When the specified route name already exists. | ||
""" | ||
# If the route is not set, get it from the callable. | ||
if route is None: | ||
|
@@ -446,6 +448,23 @@ def add_page( | |
# Check if the route given is valid | ||
verify_route_validity(route) | ||
|
||
if route in self.pages and os.getenv(constants.RELOAD_CONFIG): | ||
# when the app is reloaded(typically for app harness tests), we should maintain | ||
# the latest render function of a route.This applies typically to decorated pages | ||
# since they are only added when app._compile is called. | ||
self.pages.pop(route) | ||
|
||
if route in self.pages: | ||
route_name = ( | ||
f"`{route}` or `/`" | ||
if route == constants.PageNames.INDEX_ROUTE | ||
else f"`{route}`" | ||
) | ||
raise ValueError( | ||
f"Duplicate page route {route_name} already exists. Make sure you do not have two" | ||
f" pages with the same route" | ||
) | ||
|
||
# Setup dynamic args for the route. | ||
# this state assignment is only required for tests using the deprecated state kwarg for App | ||
state = self.state if self.state else State | ||
|
@@ -537,27 +556,67 @@ def _check_routes_conflict(self, new_route: str): | |
Args: | ||
new_route: the route being newly added. | ||
""" | ||
newroute_catchall = catchall_in_route(new_route) | ||
if not newroute_catchall: | ||
return | ||
|
||
for route in self.pages: | ||
route = "" if route == "index" else route | ||
|
||
if new_route.startswith(f"{route}/[[..."): | ||
raise ValueError( | ||
f"You cannot define a route with the same specificity as a optional catch-all route ('{route}' and '{new_route}')" | ||
) | ||
def replace_brackets_with_keywords(input_string): | ||
# /posts -> /post | ||
# /posts/[slug] -> /posts/__SINGLE_SEGMENT__ | ||
# /posts/[slug]/comments -> /posts/__SINGLE_SEGMENT__/comments | ||
# /posts/[[slug]] -> /posts/__DOUBLE_SEGMENT__ | ||
# / posts/[[...slug2]]-> /posts/__DOUBLE_CATCHALL_SEGMENT__ | ||
# /posts/[...slug3]-> /posts/__SINGLE_CATCHALL_SEGMENT__ | ||
|
||
# Replace [[...<slug>]] with __DOUBLE_CATCHALL_SEGMENT__ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if as a short-circuit we can first check if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, i actually had that logic right below this function which checks for the presence of a replaced keyword, but I can move the logic up so the |
||
output_string = re.sub( | ||
r"\[\[\.\.\..+?\]\]", | ||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT, | ||
input_string, | ||
) | ||
# Replace [...<slug>] with __SINGLE_CATCHALL_SEGMENT__ | ||
output_string = re.sub( | ||
r"\[\.\.\..+?\]", | ||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT, | ||
output_string, | ||
) | ||
# Replace [[<slug>]] with __DOUBLE_SEGMENT__ | ||
output_string = re.sub( | ||
r"\[\[.+?\]\]", constants.RouteRegex.DOUBLE_SEGMENT, output_string | ||
) | ||
# Replace [<slug>] with __SINGLE_SEGMENT__ | ||
output_string = re.sub( | ||
r"\[.+?\]", constants.RouteRegex.SINGLE_SEGMENT, output_string | ||
) | ||
return output_string | ||
|
||
route_catchall = catchall_in_route(route) | ||
if ( | ||
route_catchall | ||
and newroute_catchall | ||
and catchall_prefix(route) == catchall_prefix(new_route) | ||
new_replaced_route = replace_brackets_with_keywords(new_route) | ||
if ( | ||
constants.RouteRegex.SINGLE_SEGMENT not in new_replaced_route | ||
and constants.RouteRegex.DOUBLE_SEGMENT not in new_replaced_route | ||
and constants.RouteRegex.SINGLE_CATCHALL_SEGMENT not in new_replaced_route | ||
and constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT not in new_replaced_route | ||
): | ||
return | ||
segments = ( | ||
constants.RouteRegex.SINGLE_SEGMENT, | ||
constants.RouteRegex.DOUBLE_SEGMENT, | ||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT, | ||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT, | ||
) | ||
for route in self.pages: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will end up doing n^2 comparisons - probably alright, but maybe we should save the hash value along with it when we add page? |
||
replaced_route = replace_brackets_with_keywords(route) | ||
for rw, r, nr in zip( | ||
replaced_route.split("/"), route.split("/"), new_route.split("/") | ||
): | ||
raise ValueError( | ||
f"You cannot use multiple catchall for the same dynamic route ({route} !== {new_route})" | ||
) | ||
if rw in segments and r != nr: | ||
# If the slugs in the segments of both routes are not the same, then the route is invalid | ||
raise ValueError( | ||
f"You cannot use different slug names for the same dynamic path in {route} and {new_route} ('{r}' != '{nr}')" | ||
) | ||
elif rw not in segments and r != nr: | ||
# if the section being compared in both routes is not a dynamic segment(i.e not wrapped in brackets) | ||
# then we are guaranteed that the route is valid and there's no need checking the rest. | ||
# eg. /posts/[id]/info/[slug1] and /posts/[id]/info1/[slug1] is always going to be valid since | ||
# info1 will break away into its own tree. | ||
break | ||
|
||
def add_custom_404_page( | ||
self, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can move this outside of this function, so it won't get redefined every time