route.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. """The route decorator and associated variables and functions."""
  2. from __future__ import annotations
  3. import re
  4. from reflex import constants
  5. from reflex.event import EventHandler
  6. from reflex.page import page
  7. from reflex.utils.console import deprecate
  8. def route(
  9. route: str | None = None,
  10. title: str | None = None,
  11. image: str | None = None,
  12. description: str | None = None,
  13. on_load: EventHandler | list[EventHandler] | None = None,
  14. ):
  15. """Decorate a function as a page.
  16. rx.App() will automatically call add_page() for any method decorated with route
  17. when App.compile is called.
  18. All defaults are None because they will use the one from add_page().
  19. Note: the decorated functions still need to be imported.
  20. Args:
  21. route: The route to reach the page.
  22. title: The title of the page.
  23. image: The favicon of the page.
  24. description: The description of the page
  25. on_load: The event handler(s) called when the page load.
  26. Returns:
  27. The decorated function.
  28. """
  29. deprecate("@rx.route is deprecated and is being replaced by @rx.page instead")
  30. return page(
  31. route=route,
  32. title=title,
  33. image=image,
  34. description=description,
  35. on_load=on_load,
  36. )
  37. def verify_route_validity(route: str) -> None:
  38. """Verify if the route is valid, and throw an error if not.
  39. Args:
  40. route: The route that need to be checked
  41. Raises:
  42. ValueError: If the route is invalid.
  43. """
  44. pattern = catchall_in_route(route)
  45. if pattern and not route.endswith(pattern):
  46. raise ValueError(f"Catch-all must be the last part of the URL: {route}")
  47. def get_route_args(route: str) -> dict[str, str]:
  48. """Get the dynamic arguments for the given route.
  49. Args:
  50. route: The route to get the arguments for.
  51. Returns:
  52. The route arguments.
  53. """
  54. args = {}
  55. def add_route_arg(match: re.Match[str], type_: str):
  56. """Add arg from regex search result.
  57. Args:
  58. match: Result of a regex search
  59. type_: The assigned type for this arg
  60. Raises:
  61. ValueError: If the route is invalid.
  62. """
  63. arg_name = match.groups()[0]
  64. if arg_name in args:
  65. raise ValueError(
  66. f"Arg name [{arg_name}] is used more than once in this URL"
  67. )
  68. args[arg_name] = type_
  69. # Regex to check for route args.
  70. check = constants.RouteRegex.ARG
  71. check_strict_catchall = constants.RouteRegex.STRICT_CATCHALL
  72. check_opt_catchall = constants.RouteRegex.OPT_CATCHALL
  73. # Iterate over the route parts and check for route args.
  74. for part in route.split("/"):
  75. match_opt = check_opt_catchall.match(part)
  76. if match_opt:
  77. add_route_arg(match_opt, constants.RouteArgType.LIST)
  78. break
  79. match_strict = check_strict_catchall.match(part)
  80. if match_strict:
  81. add_route_arg(match_strict, constants.RouteArgType.LIST)
  82. break
  83. match = check.match(part)
  84. if match:
  85. # Add the route arg to the list.
  86. add_route_arg(match, constants.RouteArgType.SINGLE)
  87. return args
  88. def catchall_in_route(route: str) -> str:
  89. """Extract the catchall part from a route.
  90. Args:
  91. route: the route from which to extract
  92. Returns:
  93. str: the catchall part of the URI
  94. """
  95. match_ = constants.RouteRegex.CATCHALL.search(route)
  96. return match_.group() if match_ else ""
  97. def catchall_prefix(route: str) -> str:
  98. """Extract the prefix part from a route that contains a catchall.
  99. Args:
  100. route: the route from which to extract
  101. Returns:
  102. str: the prefix part of the URI
  103. """
  104. pattern = catchall_in_route(route)
  105. return route.replace(pattern, "") if pattern else ""