Procházet zdrojové kódy

Handle dynamic routes (#250)

Thomas Brandého před 2 roky
rodič
revize
9d59936737
3 změnil soubory, kde provedl 77 přidání a 12 odebrání
  1. 5 3
      pynecone/app.py
  2. 33 0
      pynecone/state.py
  3. 39 9
      pynecone/utils.py

+ 5 - 3
pynecone/app.py

@@ -184,11 +184,13 @@ class App(Base):
             ), "Path must be set if component is not a callable."
             path = component.__name__
 
-        # Get args from the path for dynamic routes.
-        args = utils.get_path_args(path)
+        # Check if the path given is valid
+        utils.verify_path_validity(path)
+
+        self.state.setup_dynamic_args(utils.get_path_args(path))
 
         # Generate the component if it is a callable.
-        component = component if isinstance(component, Component) else component(*args)
+        component = component if isinstance(component, Component) else component()
 
         # Add meta information to the component.
         compiler_utils.add_meta(

+ 33 - 0
pynecone/state.py

@@ -279,6 +279,39 @@ class State(Base, ABC):
         """
         return self.router_data.get("query", {})
 
+    @classmethod
+    def setup_dynamic_args(cls, args: dict[str, str]):
+        """Set up args for easy access in renderer.
+
+        Args:
+            args: a dict of args
+        """
+
+        def param_factory(param):
+            @ComputedVar
+            def inner_func(self) -> str:
+                return self.get_query_params().get(param, "")
+
+            return inner_func
+
+        def catchall_factory(param):
+            @ComputedVar
+            def inner_func(self) -> List:
+                return self.get_query_params().get(param, [])
+
+            return inner_func
+
+        for param, value in args.items():
+
+            if value == "catchall":
+                func = catchall_factory(param)
+            elif value == "patharg":
+                func = param_factory(param)
+            else:
+                continue
+            cls.computed_vars[param] = func.set_state(cls)  # type: ignore
+            setattr(cls, param, func)
+
     def __getattribute__(self, name: str) -> Any:
         """Get the state var.
 

+ 39 - 9
pynecone/utils.py

@@ -771,12 +771,34 @@ def indent(text: str, indent_level: int = 2) -> str:
     return os.linesep.join(f"{' ' * indent_level}{line}" for line in lines) + os.linesep
 
 
-def get_path_args(path: str) -> List[str]:
+def verify_path_validity(path: str) -> None:
+    """Verify if the path is valid, and throw an error if not.
+
+    Args:
+        path: the path that need to be checked
+
+    Raises:
+        ValueError: explains what is wrong with the path.
+    """
+    check_catchall = re.compile(r"^\[\.\.\.(.+)\]$")
+    catchall_found = False
+    for part in path.split("/"):
+        if catchall_found:
+            raise ValueError(f"Catch-all must be the last part of the URL: {path}")
+        match = check_catchall.match(part)
+        if match:
+            catchall_found = True
+
+
+def get_path_args(path: str) -> Dict[str, str]:
     """Get the path arguments for the given path.
 
     Args:
         path: The path to get the arguments for.
 
+    Raises:
+        ValueError: explains what is wrong with the path.
+
     Returns:
         The path arguments.
     """
@@ -785,19 +807,27 @@ def get_path_args(path: str) -> List[str]:
 
     # Regex to check for path args.
     check = re.compile(r"^\[(.+)\]$")
+    check_catchall = re.compile(r"^\[\.\.\.(.+)\]$")
 
     # Iterate over the path parts and check for path args.
-    args = []
-    for part in os.path.split(path):
+    args = {}
+    for part in path.split("/"):
+        match = check_catchall.match(part)
+        if match:
+            arg_name = match.groups()[0]
+            if arg_name in args:
+                raise ValueError(f"arg [{arg_name}] is used more than once in this URL")
+
+            args[arg_name] = "catchall"
+            continue
+
         match = check.match(part)
         if match:
             # Add the path arg to the list.
-            v = BaseVar(
-                name=match.groups()[0],
-                type_=str,
-                state=f"{constants.ROUTER}.query",
-            )
-            args.append(v)
+            arg_name = match.groups()[0]
+            if arg_name in args:
+                raise ValueError(f"arg [{arg_name}] is used more than once in this URL")
+            args[arg_name] = "patharg"
     return args