"""The route decorator and associated variables and functions.""" from __future__ import annotations import re from reflex import constants from reflex.event import EventHandler from reflex.page import page from reflex.utils.console import deprecate def route( route: str | None = None, title: str | None = None, image: str | None = None, description: str | None = None, on_load: EventHandler | list[EventHandler] | None = None, ): """Decorate a function as a page. rx.App() will automatically call add_page() for any method decorated with route when App.compile is called. All defaults are None because they will use the one from add_page(). Note: the decorated functions still need to be imported. Args: route: The route to reach the page. title: The title of the page. image: The favicon of the page. description: The description of the page on_load: The event handler(s) called when the page load. Returns: The decorated function. """ deprecate("@rx.route is deprecated and is being replaced by @rx.page instead") return page( route=route, title=title, image=image, description=description, on_load=on_load, ) def verify_route_validity(route: str) -> None: """Verify if the route is valid, and throw an error if not. Args: route: The route that need to be checked Raises: ValueError: If the route is invalid. """ pattern = catchall_in_route(route) if pattern and not route.endswith(pattern): raise ValueError(f"Catch-all must be the last part of the URL: {route}") def get_route_args(route: str) -> dict[str, str]: """Get the dynamic arguments for the given route. Args: route: The route to get the arguments for. Returns: The route arguments. """ args = {} def add_route_arg(match: re.Match[str], type_: str): """Add arg from regex search result. Args: match: Result of a regex search type_: The assigned type for this arg Raises: ValueError: If the route is invalid. """ arg_name = match.groups()[0] if arg_name in args: raise ValueError( f"Arg name [{arg_name}] is used more than once in this URL" ) args[arg_name] = type_ # Regex to check for route args. check = constants.RouteRegex.ARG check_strict_catchall = constants.RouteRegex.STRICT_CATCHALL check_opt_catchall = constants.RouteRegex.OPT_CATCHALL # Iterate over the route parts and check for route args. for part in route.split("/"): match_opt = check_opt_catchall.match(part) if match_opt: add_route_arg(match_opt, constants.RouteArgType.LIST) break match_strict = check_strict_catchall.match(part) if match_strict: add_route_arg(match_strict, constants.RouteArgType.LIST) break match = check.match(part) if match: # Add the route arg to the list. add_route_arg(match, constants.RouteArgType.SINGLE) return args def catchall_in_route(route: str) -> str: """Extract the catchall part from a route. Args: route: the route from which to extract Returns: str: the catchall part of the URI """ match_ = constants.RouteRegex.CATCHALL.search(route) return match_.group() if match_ else "" def catchall_prefix(route: str) -> str: """Extract the prefix part from a route that contains a catchall. Args: route: the route from which to extract Returns: str: the prefix part of the URI """ pattern = catchall_in_route(route) return route.replace(pattern, "") if pattern else ""