1
0

route.py 4.1 KB

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