From 9f152310656eb0310419878fcf7feb4bd395370f Mon Sep 17 00:00:00 2001 From: Jakob Pinterits Date: Mon, 16 Sep 2024 20:02:01 +0200 Subject: [PATCH] @page now supports an explicit order --- rio/app.py | 11 ++--- rio/routing.py | 44 ++++++++++++++++++- .../pages/login_page.py | 2 +- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/rio/app.py b/rio/app.py index 65261989..588e6b1b 100644 --- a/rio/app.py +++ b/rio/app.py @@ -141,9 +141,10 @@ class App: name: str | None = None, description: str | None = None, icon: ImageLike | None = None, - pages: Iterable[rio.ComponentPage] + pages: Iterable[rio.ComponentPage | rio.Redirect] | os.PathLike - | Literal["auto"] = "auto", + | str + | None = None, on_app_start: rio.EventHandler[App] = None, on_app_close: rio.EventHandler[App] = None, on_session_start: rio.EventHandler[rio.Session] = None, @@ -193,7 +194,7 @@ class App: Per default, rio scans your project's "pages" directory for components decorated with `@rio.page` and turns them into pages. To - override the location of this directory, you can pass in a file + override the location of this directory, you can provide a custom path. `on_app_start`: A function that will be called when the app is first @@ -321,12 +322,12 @@ class App: def _load_pages(self) -> None: pages: Iterable[rio.ComponentPage | rio.Redirect] - if self._raw_pages == "auto": + if self._raw_pages is None: pages = routing.auto_detect_pages( self._module_path / "pages", package=f"{self._module_path.stem}.pages", ) - elif isinstance(self._raw_pages, os.PathLike): + elif isinstance(self._raw_pages, (os.PathLike, str)): pages = routing.auto_detect_pages(Path(self._raw_pages)) else: pages = self._raw_pages # type: ignore (wtf?) diff --git a/rio/routing.py b/rio/routing.py index 79d46dd2..8a4122cd 100644 --- a/rio/routing.py +++ b/rio/routing.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import math import typing as t import warnings from collections.abc import Callable, Iterable, Sequence @@ -129,6 +130,10 @@ class ComponentPage: guard: Callable[[rio.GuardEvent], None | rio.URL | str] | None = None meta_tags: dict[str, str] = field(default_factory=dict) + # This is used to allow users to order pages when using the `rio.page` + # decorator. It's not public, but simply a convenient place to store this. + _page_order_: int | None = field(default=None, init=False) + if not t.TYPE_CHECKING: page_url: str | None = None @@ -330,8 +335,11 @@ def page( icon: str = DEFAULT_ICON, guard: (Callable[[GuardEvent], None | rio.URL | str] | None) = None, meta_tags: dict[str, str] | None = None, + order: int | None = None, ): - """ """ + """ + TODO + """ def decorator(build: C) -> C: nonlocal name, url_segment @@ -358,10 +366,44 @@ def page( return decorator +def _page_sort_key(page: rio.ComponentPage) -> tuple: + """ + Returns a key that can be used to sort pages. + """ + return ( + # Any explicit ordering takes precedence + math.inf if page._page_order_ is None else page._page_order_, + # Put the home page first + not page.url_segment == "", + # Then sort by name + page.name, + ) + + def auto_detect_pages( directory: Path, *, package: str | None = None, +) -> list[rio.ComponentPage]: + # Find all pages using the iterator method + pages = list( + _auto_detect_pages_iter( + directory, + package=package, + ) + ) + + # Sort them + pages.sort(key=_page_sort_key) + + # Done + return pages + + +def _auto_detect_pages_iter( + directory: Path, + *, + package: str | None = None, ) -> Iterable[rio.ComponentPage]: try: contents = list(directory.iterdir()) diff --git a/rio/snippets/snippet-files/project-template-Authentication/pages/login_page.py b/rio/snippets/snippet-files/project-template-Authentication/pages/login_page.py index 2593046b..ac0f5edb 100644 --- a/rio/snippets/snippet-files/project-template-Authentication/pages/login_page.py +++ b/rio/snippets/snippet-files/project-template-Authentication/pages/login_page.py @@ -22,7 +22,7 @@ def guard(event: rio.GuardEvent) -> str | None: ## Parameters - `event`: The event that triggered the guard containing the `session` and `active_pages`. + `event`: The event that triggered the guard containing the `session` and `active_pages`. """ # If the user is already logged in, there is no reason to show the login page.