route.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """The route decorator and associated variables and functions."""
  2. from __future__ import annotations
  3. import re
  4. from typing import Dict, List, Optional, Union
  5. from pynecone import constants
  6. from pynecone.event import EventHandler
  7. DECORATED_ROUTES = []
  8. def route(
  9. route: Optional[str] = None,
  10. title: Optional[str] = None,
  11. image: Optional[str] = None,
  12. description: Optional[str] = None,
  13. on_load: Optional[Union[EventHandler, List[EventHandler]]] = None,
  14. ):
  15. """Decorate a function as a page.
  16. pc.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. def decorator(render_fn):
  30. kwargs = {}
  31. if route:
  32. kwargs["route"] = route
  33. if title:
  34. kwargs["title"] = title
  35. if image:
  36. kwargs["image"] = image
  37. if description:
  38. kwargs["description"] = description
  39. if on_load:
  40. kwargs["on_load"] = on_load
  41. DECORATED_ROUTES.append((render_fn, kwargs))
  42. return render_fn
  43. return decorator
  44. def verify_route_validity(route: str) -> None:
  45. """Verify if the route is valid, and throw an error if not.
  46. Args:
  47. route: The route that need to be checked
  48. Raises:
  49. ValueError: If the route is invalid.
  50. """
  51. pattern = catchall_in_route(route)
  52. if pattern and not route.endswith(pattern):
  53. raise ValueError(f"Catch-all must be the last part of the URL: {route}")
  54. def get_route_args(route: str) -> Dict[str, str]:
  55. """Get the dynamic arguments for the given route.
  56. Args:
  57. route: The route to get the arguments for.
  58. Returns:
  59. The route arguments.
  60. """
  61. args = {}
  62. def add_route_arg(match: re.Match[str], type_: str):
  63. """Add arg from regex search result.
  64. Args:
  65. match: Result of a regex search
  66. type_: The assigned type for this arg
  67. Raises:
  68. ValueError: If the route is invalid.
  69. """
  70. arg_name = match.groups()[0]
  71. if arg_name in args:
  72. raise ValueError(
  73. f"Arg name [{arg_name}] is used more than once in this URL"
  74. )
  75. args[arg_name] = type_
  76. # Regex to check for route args.
  77. check = constants.RouteRegex.ARG
  78. check_strict_catchall = constants.RouteRegex.STRICT_CATCHALL
  79. check_opt_catchall = constants.RouteRegex.OPT_CATCHALL
  80. # Iterate over the route parts and check for route args.
  81. for part in route.split("/"):
  82. match_opt = check_opt_catchall.match(part)
  83. if match_opt:
  84. add_route_arg(match_opt, constants.RouteArgType.LIST)
  85. break
  86. match_strict = check_strict_catchall.match(part)
  87. if match_strict:
  88. add_route_arg(match_strict, constants.RouteArgType.LIST)
  89. break
  90. match = check.match(part)
  91. if match:
  92. # Add the route arg to the list.
  93. add_route_arg(match, constants.RouteArgType.SINGLE)
  94. return args
  95. def catchall_in_route(route: str) -> str:
  96. """Extract the catchall part from a route.
  97. Args:
  98. route: the route from which to extract
  99. Returns:
  100. str: the catchall part of the URI
  101. """
  102. match_ = constants.RouteRegex.CATCHALL.search(route)
  103. return match_.group() if match_ else ""
  104. def catchall_prefix(route: str) -> str:
  105. """Extract the prefix part from a route that contains a catchall.
  106. Args:
  107. route: the route from which to extract
  108. Returns:
  109. str: the prefix part of the URI
  110. """
  111. pattern = catchall_in_route(route)
  112. return route.replace(pattern, "") if pattern else ""