Handle dynamic routes (#250)

This commit is contained in:
Thomas Brandého 2023-01-12 23:38:29 +01:00 committed by GitHub
parent 98e9edd98a
commit 9d59936737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 12 deletions

View File

@ -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(

View File

@ -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.

View File

@ -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