route.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. """The route decorator and associated variables and functions."""
  2. from __future__ import annotations
  3. import re
  4. from reflex import constants
  5. def verify_route_validity(route: str) -> None:
  6. """Verify if the route is valid, and throw an error if not.
  7. Args:
  8. route: The route that need to be checked
  9. Raises:
  10. ValueError: If the route is invalid.
  11. """
  12. pattern = catchall_in_route(route)
  13. if pattern and not route.endswith(pattern):
  14. msg = f"Catch-all must be the last part of the URL: {route}"
  15. raise ValueError(msg)
  16. if route == "api" or route.startswith("api/"):
  17. msg = (
  18. f"Cannot have a route prefixed with 'api/': {route} (conflicts with NextJS)"
  19. )
  20. raise ValueError(msg)
  21. def get_route_args(route: str) -> dict[str, str]:
  22. """Get the dynamic arguments for the given route.
  23. Args:
  24. route: The route to get the arguments for.
  25. Returns:
  26. The route arguments.
  27. """
  28. args = {}
  29. def add_route_arg(match: re.Match[str], type_: str):
  30. """Add arg from regex search result.
  31. Args:
  32. match: Result of a regex search
  33. type_: The assigned type for this arg
  34. Raises:
  35. ValueError: If the route is invalid.
  36. """
  37. arg_name = match.groups()[0]
  38. if arg_name in args:
  39. msg = f"Arg name [{arg_name}] is used more than once in this URL"
  40. raise ValueError(msg)
  41. args[arg_name] = type_
  42. # Regex to check for route args.
  43. check = constants.RouteRegex.ARG
  44. check_strict_catchall = constants.RouteRegex.STRICT_CATCHALL
  45. check_opt_catchall = constants.RouteRegex.OPT_CATCHALL
  46. # Iterate over the route parts and check for route args.
  47. for part in route.split("/"):
  48. match_opt = check_opt_catchall.match(part)
  49. if match_opt:
  50. add_route_arg(match_opt, constants.RouteArgType.LIST)
  51. break
  52. match_strict = check_strict_catchall.match(part)
  53. if match_strict:
  54. add_route_arg(match_strict, constants.RouteArgType.LIST)
  55. break
  56. match = check.match(part)
  57. if match:
  58. # Add the route arg to the list.
  59. add_route_arg(match, constants.RouteArgType.SINGLE)
  60. return args
  61. def catchall_in_route(route: str) -> str:
  62. """Extract the catchall part from a route.
  63. Args:
  64. route: the route from which to extract
  65. Returns:
  66. str: the catchall part of the URI
  67. """
  68. match_ = constants.RouteRegex.CATCHALL.search(route)
  69. return match_.group() if match_ else ""
  70. def catchall_prefix(route: str) -> str:
  71. """Extract the prefix part from a route that contains a catchall.
  72. Args:
  73. route: the route from which to extract
  74. Returns:
  75. str: the prefix part of the URI
  76. """
  77. pattern = catchall_in_route(route)
  78. return route.replace(pattern, "") if pattern else ""
  79. def replace_brackets_with_keywords(input_string: str) -> str:
  80. """Replace brackets and everything inside it in a string with a keyword.
  81. Args:
  82. input_string: String to replace.
  83. Returns:
  84. new string containing keywords.
  85. """
  86. # /posts -> /post
  87. # /posts/[slug] -> /posts/__SINGLE_SEGMENT__
  88. # /posts/[slug]/comments -> /posts/__SINGLE_SEGMENT__/comments
  89. # /posts/[[slug]] -> /posts/__DOUBLE_SEGMENT__
  90. # / posts/[[...slug2]]-> /posts/__DOUBLE_CATCHALL_SEGMENT__
  91. # /posts/[...slug3]-> /posts/__SINGLE_CATCHALL_SEGMENT__
  92. # Replace [[...<slug>]] with __DOUBLE_CATCHALL_SEGMENT__
  93. output_string = re.sub(
  94. r"\[\[\.\.\..+?\]\]",
  95. constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
  96. input_string,
  97. )
  98. # Replace [...<slug>] with __SINGLE_CATCHALL_SEGMENT__
  99. output_string = re.sub(
  100. r"\[\.\.\..+?\]",
  101. constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
  102. output_string,
  103. )
  104. # Replace [[<slug>]] with __DOUBLE_SEGMENT__
  105. output_string = re.sub(
  106. r"\[\[.+?\]\]", constants.RouteRegex.DOUBLE_SEGMENT, output_string
  107. )
  108. # Replace [<slug>] with __SINGLE_SEGMENT__
  109. return re.sub(r"\[.+?\]", constants.RouteRegex.SINGLE_SEGMENT, output_string)