├── mini.gif ├── .github └── ISSUE_TEMPLATE │ ├── feature-request-.md │ └── bug-error-report-.md ├── LICENSE ├── README.md └── flet_navigator └── __init__.py /mini.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzripper/flet_navigator/HEAD/mini.gif -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request. 3 | about: Request your feature! 4 | title: "[FEATURE]" 5 | labels: enhancement 6 | assignees: xzripper 7 | 8 | --- 9 | 10 | **Describe your new feature:** 11 | ... 12 | 13 | **Additional:** 14 | ... 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-error-report-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/Error Report. 3 | about: Report about bug/error. 4 | title: "[ISSUE]" 5 | labels: bug, enhancement 6 | assignees: xzripper 7 | 8 | --- 9 | 10 | **Describe your problem:** 11 | Your problem... 12 | 13 | **Reproducing the problem:** 14 | Code.. 15 | 16 | **Media (optional):** 17 | Screenshot or video.. 18 | 19 | **flet_navigator version, flet version, OS:** 20 | `FletNavigator version, Flet Version, OS..` 21 | 22 | **Additional:** 23 | ... 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Evan P. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

⚓ FletNavigator V3

2 |

3 | 4 |

5 | 6 |

FletNavigator is a thorough navigation/routing module for the Flet framework that combines speed, simplicity, and efficiency. Features like cross-page arguments, URL parameters, templates, external routes, utilities & decorators, and more are present in FletNavigator! Install it and try it for yourself!

7 | 8 |

Copy&Paste into your terminal: pip install flet_navigator

9 | 10 |

11 | 12 | 13 | 14 | ```python 15 | from flet import app, Text, FilledButton 16 | 17 | from flet_navigator import RouteContext, route, fn_process 18 | 19 | 20 | @route('/') 21 | def main(ctx: RouteContext) -> None: 22 | ctx.add(Text('Hello World!')) 23 | 24 | ctx.add(FilledButton('Navigate to the second page!', on_click=lambda _: ctx.navigate('second'))) 25 | 26 | @route 27 | def second(ctx: RouteContext) -> None: 28 | ctx.add(Text('I am the second page!')) 29 | 30 | ctx.add(FilledButton('Return to the homepage!', on_click=lambda _: ctx.navigate_homepage())) 31 | 32 | app(fn_process()) 33 | ``` 34 | 35 | 36 |

→ Read the official web API documentation.

37 | 38 |

FletNavigator 2025

39 | -------------------------------------------------------------------------------- /flet_navigator/__init__.py: -------------------------------------------------------------------------------- 1 | """A minimalist module for navigation in Flet that combines speed and simplicity.""" 2 | 3 | from importlib import import_module 4 | 5 | from logging import ERROR, basicConfig, getLogger 6 | 7 | from re import compile as re_compile 8 | 9 | from urllib.parse import parse_qs, unquote, urlsplit 10 | 11 | from typing import Any, Callable, Optional, Union 12 | 13 | from flet import Control, Page, Text, IconButton 14 | 15 | 16 | _pre_def_routes: 'Routes' = {} 17 | 18 | 19 | _global_templates: dict[str, 'TemplateDefinition'] = {} 20 | 21 | 22 | _url_fn_space_chr: str = '' 23 | 24 | 25 | FLET_NAVIGATOR_VERSION: str = '3.10.11' 26 | """The version of the Flet Navigator.""" 27 | 28 | 29 | _DEFAULT_PAGE_404: 'PageDefinition' = lambda pg: ( 30 | globals().__setitem__('_FNDP404_PRE_H_A', pg.page.horizontal_alignment), 31 | globals().__setitem__('_FNDP404_PRE_V_A', pg.page.vertical_alignment), 32 | 33 | globals().__setitem__('_FNDP404_CLOSED', False), 34 | 35 | setattr(pg.page, 'horizontal_alignment', 'center'), 36 | setattr(pg.page, 'vertical_alignment', 'center'), 37 | 38 | pg.add(Text('Not Found', size=100, tooltip=f'Invalid route: "{pg.current_route()}".')), 39 | 40 | pg.add(IconButton( 41 | 'door_back_door_outlined', 'white', 60, 42 | 43 | on_click=lambda _: ( 44 | pg.page.clean(), 45 | 46 | setattr(pg.page, 'horizontal_alignment', globals().get('_FNDP404_PRE_H_A')), 47 | setattr(pg.page, 'vertical_alignment', globals().get('_FNDP404_PRE_V_A')), 48 | 49 | globals().__setitem__('_FNDP404_CLOSED', True), 50 | 51 | pg.navigate_homepage() 52 | ), tooltip='Return to the homepage.' 53 | )) 54 | ) 55 | """A default Route-404 handler.""" 56 | 57 | 58 | ROUTE_404: str = 'ROUTE-404' 59 | """A constant string representing the 404 route type. 60 | 61 | Used to specify a custom route for handling 404 errors (Page Not Found) in applications. 62 | This can be customized for routing or error handling purposes.""" 63 | 64 | 65 | Arguments = Union[Any, tuple[Any, ...]] 66 | """An alias for a page-transferring arguments.""" 67 | 68 | PageDefinition = Callable[['RouteContext'], None] 69 | """An alias for a page definition.""" 70 | 71 | TemplateDefinition = Callable[['RouteContext', Arguments], Any] 72 | """An alias for a template definition.""" 73 | 74 | RouteChangeCallback = Callable[['RouteContext'], None] 75 | """An alias for a route change callback.""" 76 | 77 | Routes = dict[str, PageDefinition] 78 | """An alias for a routes map.""" 79 | 80 | RouteParameters = dict[str, Union[str, int, bool, None]] 81 | """An alias for a route parameters map.""" 82 | 83 | RouteProperties = dict[int, dict[str, Any]] 84 | """An alias for a route properties map.""" 85 | 86 | 87 | class RouteContext: 88 | """Route context class used for transferring data between routes and providing Navigator shortcuts.""" 89 | 90 | page: Page = None 91 | """The current page instance.""" 92 | 93 | navigator: Union['PublicFletNavigator', 'VirtualFletNavigator'] = None 94 | """The navigator that created this RouteContext instance.""" 95 | 96 | arguments: Arguments = None 97 | """Arguments passed from the previous page for context.""" 98 | 99 | parameters: 'RouteParameters' = None 100 | """URL parameters associated with the current route.""" 101 | 102 | route_id: tuple[int, str] = None 103 | """The unique identifier for this page.""" 104 | 105 | def __init__(self, page: Page, navigator: Union['PublicFletNavigator', 'VirtualFletNavigator'], arguments: Arguments, parameters: 'RouteParameters', route_id: tuple[int, str]) -> None: 106 | """Initialize a RouteContext instance.""" 107 | self.page = page 108 | 109 | self.navigator = navigator 110 | 111 | self.arguments = arguments 112 | 113 | self.parameters = parameters 114 | 115 | self.route_id = route_id 116 | 117 | def add(self, *controls: Control) -> None: 118 | """Add one or more controls to the current page.""" 119 | self.page.add(*controls) 120 | 121 | def navigate(self, route: str, args: Arguments=(), **parameters: RouteParameters) -> None: 122 | """Navigate to a specific route. If the navigator is virtual, parameters are not used.""" 123 | if self.navigator.is_virtual(): 124 | self.navigator.navigate(route, self.page, args) 125 | 126 | else: 127 | self.navigator.navigate(route, self.page, args, parameters) 128 | 129 | def navigate_homepage(self, args: Arguments=(), **parameters: RouteParameters) -> None: 130 | """Navigate to the homepage. If the navigator is virtual, parameters are not used.""" 131 | if self.navigator.is_virtual(): 132 | self.navigator.navigate_homepage(self.page, args) 133 | 134 | else: 135 | self.navigator.navigate_homepage(self.page, args, parameters) 136 | 137 | def navigate_back(self, args: Arguments=(), **parameters: RouteParameters) -> None: 138 | """Navigate back to the previous route. If the navigator is virtual, parameters are not used.""" 139 | if self.navigator.is_virtual(): 140 | self.navigator.navigate_back(self.page, args) 141 | 142 | else: 143 | self.navigator.navigate_back(self.page, args, parameters) 144 | 145 | def set_homepage(self, homepage: str) -> None: 146 | """Update navigator's homepage address.""" 147 | self.navigator.set_homepage(homepage) 148 | 149 | def spec_cpage_props(self, **props: dict[str, Any]) -> None: 150 | """Specify current page properties.""" 151 | self.navigator.props_map[self.route_id] = props 152 | 153 | AbstractFletNavigator.proc_page_props(self.page, props, ()) 154 | 155 | self.page.update() 156 | 157 | def current_route(self) -> str: 158 | """Get the navigator's current route state.""" 159 | return self.navigator.route 160 | 161 | def __repr__(self) -> str: 162 | """Represent the RouteContext instance as a string for debugging purposes.""" 163 | return f'{self.previous_page} -> {self.navigator.route} [{"NO-ARGUMENTS" if not self.arguments else self.arguments}, {"NO-PARAMETERS" if len(self.parameters) <= 0 else self.parameters}] ({self.route_id}) (NAVIGATOR-OBJECT {self.navigator})' 164 | 165 | 166 | class AbstractFletNavigator: 167 | @staticmethod 168 | def init_nav(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], page: Page=None, routes: Routes={}, route_change_callback: RouteChangeCallback=None) -> None: 169 | basicConfig(format=f'%(name)s %(levelname)s: %(message)s') 170 | 171 | nav._logger = getLogger(f'FN') 172 | 173 | nav._logger.setLevel(ERROR) 174 | 175 | nav._afn_vroute = re_compile(r'^[a-zA-Z_]\w*$') 176 | 177 | if page: 178 | nav._afn_proute = re_compile(r'^[a-zA-Z_]\w*\?\w+=([\w+~`!@"#№$;%^:*-,.<>\'{}\[\]()-]+)(?:&\w+=([\w+~`!@"#№$;%^:*-,.<>\'{}\[\]()-]+))*$') 179 | nav._afn_floatstr = re_compile(r'^-?\d+\.\d+$') 180 | 181 | nav.page = page 182 | 183 | nav.virtual = False 184 | 185 | else: 186 | nav.virtual = True 187 | 188 | nav.routes = routes 189 | 190 | nav.route_change_callback = route_change_callback 191 | 192 | if not nav.is_virtual(): 193 | page.on_route_change = nav.fn_route_change_handler_ 194 | 195 | routes_to_delete = [] 196 | 197 | for route in _pre_def_routes: 198 | nav.routes[route] = _pre_def_routes[route] 199 | 200 | for route in nav.routes: 201 | if route != '/' and route != ROUTE_404: 202 | if not nav._afn_vroute.match(route): 203 | routes_to_delete.append(route) 204 | 205 | nav._logger.error(f'Invalid route name: "{route}". Route names must start with a letter or underscore and contain only alphanumeric characters or underscores.') 206 | 207 | for route_to_delete in routes_to_delete: 208 | nav.routes.pop(route_to_delete) 209 | 210 | nav._returning = False 211 | 212 | @staticmethod 213 | def navigate(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], route: str, page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 214 | if nav.is_virtual() and '?' in route: 215 | route = route.split('?')[0] 216 | 217 | nav._logger.error('VirtualFletNavigator does not support URL parameters. Use page arguments instead, or switch to PublicFletNavigator for full URL parameters support.') 218 | 219 | if not nav._returning: 220 | nav.previous_routes.append(nav.route) 221 | 222 | nav.route = route 223 | 224 | if not nav.is_virtual(): 225 | nav._nav_temp_args = args 226 | 227 | if route == '/' and parameters: 228 | page.go(route) 229 | 230 | nav._logger.error('Index/Main route does not support parameters; parameters transferring skipped.') 231 | 232 | else: 233 | page.go(AbstractFletNavigator.fparams(route, **parameters)) 234 | 235 | else: 236 | nav.process(page, args) 237 | 238 | @staticmethod 239 | def navigate_homepage(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 240 | AbstractFletNavigator.navigate(nav, nav.homepage, page, args, parameters) 241 | 242 | @staticmethod 243 | def set_homepage(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], homepage: str) -> None: 244 | if homepage not in nav.routes: 245 | nav._logger.error('Can\'t update homepage address: invalid route.') 246 | 247 | return 248 | 249 | nav.homepage = homepage 250 | 251 | @staticmethod 252 | def navigate_back(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 253 | if len(nav.previous_routes) > 0: 254 | nav._returning = True 255 | 256 | AbstractFletNavigator.navigate(nav, nav.previous_routes[-1], page, args, parameters) 257 | 258 | nav._returning = False 259 | 260 | nav.previous_routes.pop() 261 | 262 | @staticmethod 263 | def process(nav: Union['VirtualFletNavigator', 'PublicFletNavigator'], page: Page, args: Arguments=(), route_parameters: RouteParameters={}) -> None: 264 | total_props = AbstractFletNavigator.find_all_specified_props(nav.routes, nav.props_map) 265 | 266 | if nav.route not in nav.routes: 267 | page.clean() 268 | 269 | r404_rctx_inst = RouteContext(page, nav, args, route_parameters, ROUTE_404) 270 | 271 | if ROUTE_404 in nav.routes: 272 | AbstractFletNavigator.proc_page_props(page, nav.props_map.get(ROUTE_404), total_props) 273 | 274 | nav.routes[ROUTE_404](r404_rctx_inst) 275 | 276 | else: 277 | _DEFAULT_PAGE_404(r404_rctx_inst) 278 | 279 | if nav.route_change_callback: 280 | nav.route_change_callback(r404_rctx_inst) 281 | 282 | nav._logger.error(f'Route "{nav.route}" does not exist in the defined routes. Unable to process the page.') 283 | 284 | else: 285 | page.clean() 286 | 287 | route_id = hash(nav.route) 288 | 289 | AbstractFletNavigator.proc_page_props(page, nav.props_map.get(route_id), total_props) 290 | 291 | nav.routes[nav.route](nxrctx := RouteContext(page, nav, args, route_parameters, route_id)) 292 | 293 | page.update() 294 | 295 | if nav.route_change_callback: 296 | nav.route_change_callback(nxrctx) 297 | 298 | @staticmethod 299 | def find_all_specified_props(routes: Routes, props_map: RouteProperties) -> tuple[str]: 300 | total_props_specified = [] 301 | 302 | for route in routes: 303 | route_id = hash(route) 304 | 305 | if route_id in props_map: 306 | total_props_specified += list(props_map[route_id].keys()) 307 | 308 | return tuple(total_props_specified) 309 | 310 | @staticmethod 311 | def proc_page_props(page: Page, props: RouteProperties, total_props: tuple[str]) -> None: 312 | for prop in total_props: 313 | setattr(page, prop, None) 314 | 315 | if props: 316 | for prop, prop_value in props.items(): 317 | setattr(page, prop, prop_value) 318 | 319 | @staticmethod 320 | def fparams(route: str, **_parameters: dict) -> str: 321 | return f'{route}?{"&".join(f"{key}={value}" for key, value in _parameters.items())}' if len(_parameters) > 0 else route 322 | 323 | @staticmethod 324 | def is_virtual(nav: Union['VirtualFletNavigator', 'PublicFletNavigator']) -> bool: 325 | return getattr(nav, 'virtual', None) 326 | 327 | 328 | class PublicFletNavigator: 329 | """The Public Flet Navigator. It's just like the virtual one, but with URL parameters and visible routes.""" 330 | 331 | page: Page = None 332 | """Page object representing the current page.""" 333 | 334 | route: str = '/' 335 | """The current active route.""" 336 | 337 | routes: Routes = {} 338 | """A map of all registered routes in the application.""" 339 | 340 | previous_routes: list[str] = [] 341 | """A list of all previously visited routes.""" 342 | 343 | homepage: str = '/' 344 | """The homepage route.""" 345 | 346 | props_map: RouteProperties = {} 347 | """A page properties map for each page ID.""" 348 | 349 | route_change_callback: RouteChangeCallback = None 350 | """A callback function that is triggered when the route changes.""" 351 | 352 | _nav_temp_args: Arguments = None 353 | 354 | def __init__(self, page: Page, routes: Routes={}, route_change_callback: RouteChangeCallback=None) -> None: 355 | """Initialize the public navigator.""" 356 | AbstractFletNavigator.init_nav(self, page, routes, route_change_callback) 357 | 358 | def navigate(self, route: str, page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 359 | """Navigate to a specific route in the application.""" 360 | AbstractFletNavigator.navigate(self, route, page, args, parameters) 361 | 362 | def navigate_homepage(self, page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 363 | """Navigate to the homepage route.""" 364 | AbstractFletNavigator.navigate_homepage(self, page, args, parameters) 365 | 366 | def set_homepage(self, homepage: str) -> None: 367 | """Update navigator's homepage address.""" 368 | AbstractFletNavigator.set_homepage(self, homepage) 369 | 370 | def navigate_back(self, page: Page, args: Arguments=(), parameters: RouteParameters={}) -> None: 371 | """Navigate back to the previous route.""" 372 | AbstractFletNavigator.navigate_back(self, page, args, parameters) 373 | 374 | def process(self, page: Page, args: Arguments=(), route_parameters: RouteParameters={}) -> None: 375 | """Process the current route on the provided page.""" 376 | AbstractFletNavigator.process(self, page, args, route_parameters) 377 | 378 | def is_virtual(self) -> bool: 379 | """Check if the navigator is virtual or public.""" 380 | return AbstractFletNavigator.is_virtual(self) 381 | 382 | def fn_route_change_handler_(self, _) -> None: 383 | route: str = self.page.route.replace(' ', _url_fn_space_chr).replace('%20', _url_fn_space_chr).replace('+', _url_fn_space_chr) 384 | 385 | route = route[1:] if route.startswith('/') and len(route) >= 2 else route 386 | 387 | split_url = urlsplit(route) 388 | 389 | base_route, qstr = split_url.path, split_url.query 390 | 391 | parameters = {} 392 | 393 | if self._afn_proute.match(route): 394 | _parsed_params = parse_qs(qstr, True) 395 | 396 | for key, values in _parsed_params.items(): 397 | if not key.isalpha(): 398 | self._logger.error(f'Invalid key name: "{key}". The key is expected to be a string.') 399 | 400 | continue 401 | 402 | value = unquote(values[0]) if values else '' 403 | 404 | if value.isdigit(): parameters[key] = int(value) 405 | elif self._afn_floatstr.match(value): parameters[key] = float(value) 406 | elif value in ['True', 'False']: parameters[key] = value == 'True' 407 | elif value == 'None': parameters[key] = None 408 | else: parameters[key] = value.replace(_url_fn_space_chr, ' ') 409 | 410 | self.route = base_route 411 | 412 | if not globals().get('_FNDP404_CLOSED'): 413 | setattr(self.page, 'horizontal_alignment', globals().get('_FNDP404_PRE_H_A')), 414 | setattr(self.page, 'vertical_alignment', globals().get('_FNDP404_PRE_V_A')), 415 | 416 | self.process(self.page, self._nav_temp_args, parameters) 417 | 418 | self._nav_temp_args = None 419 | 420 | 421 | class VirtualFletNavigator: 422 | """The Virtual Flet Navigator. It's just like the public one, but without URL parameters and visible routes.""" 423 | 424 | route: str = '/' 425 | """The current active route.""" 426 | 427 | routes: Routes = {} 428 | """A map of all registered routes in the application.""" 429 | 430 | previous_routes: list[str] = [] 431 | """A list of all previously visited routes.""" 432 | 433 | homepage: str = '/' 434 | """The homepage route.""" 435 | 436 | props_map: RouteProperties = {} 437 | """A page properties map for each page ID.""" 438 | 439 | route_change_callback: RouteChangeCallback = None 440 | """A callback function that is triggered when the route changes.""" 441 | 442 | def __init__(self, routes: Routes={}, route_change_callback: RouteChangeCallback=None) -> None: 443 | """Initialize the virtual navigator.""" 444 | AbstractFletNavigator.init_nav(self, None, routes, route_change_callback) 445 | 446 | def navigate(self, route: str, page: Page, args: Arguments=()) -> None: 447 | """Navigate to a specific route in the application.""" 448 | AbstractFletNavigator.navigate(self, route, page, args) 449 | 450 | def navigate_homepage(self, page: Page, args: Arguments=()) -> None: 451 | """Navigate to the homepage route.""" 452 | AbstractFletNavigator.navigate_homepage(self, page, args) 453 | 454 | def set_homepage(self, homepage: str) -> None: 455 | """Update navigator's homepage address.""" 456 | AbstractFletNavigator.set_homepage(self, homepage) 457 | 458 | def navigate_back(self, page: Page, args: Arguments=()) -> None: 459 | """Navigate back to the previous route.""" 460 | AbstractFletNavigator.navigate_back(self, page, args) 461 | 462 | def process(self, page: Page, args: Arguments=()) -> None: 463 | """Process the current route on the provided page.""" 464 | AbstractFletNavigator.process(self, page, args) 465 | 466 | def is_virtual(self) -> bool: 467 | """Check if the navigator is virtual or public.""" 468 | return AbstractFletNavigator.is_virtual(self) 469 | 470 | 471 | def route(route: Union[str, PageDefinition]) -> Any: 472 | """Link a route to the last initialized navigator. 473 | 474 | This function registers the route and associates it with a given page definition. 475 | The only difference is the name. You can specify the name in the first argument. 476 | or this function will fetch the given function name automatically.""" 477 | if isinstance(route, Callable): 478 | _pre_def_routes[route.__name__] = route 479 | 480 | else: 481 | def _route_decorator(page_definition: PageDefinition) -> None: 482 | _pre_def_routes[route] = page_definition 483 | 484 | return _route_decorator 485 | 486 | 487 | def load_page(path: str, name: Optional[str]=None) -> PageDefinition: 488 | """Load a page definition from a specified module. 489 | 490 | Let me explain this technically: it replaces all the system path separators with a dot. 491 | After loading the module by its path, it loads the page definition function. 492 | The function name is determined by the path. If a name is specified, then it loads the specified name. 493 | Otherwise, it uses the last name in the path. 494 | 495 | Can throw `ModuleNotFoundError` and `AttributeError`.""" 496 | path = path.replace('\\', '.').replace('/', '.') 497 | 498 | page = None 499 | 500 | try: 501 | page = getattr(import_module(path), _pd := path.split('.')[-1] if not name else name) 502 | except ModuleNotFoundError as module_exc: 503 | raise TypeError(f'Failed to load page definition module: "{path}".') from module_exc 504 | except AttributeError as attr_error: 505 | raise ImportError(f'Failed to load page definition: "{_pd}".') from attr_error 506 | 507 | return page 508 | 509 | 510 | def template(template_definition: Union[str, TemplateDefinition], route_data: RouteContext, arguments: Arguments=()) -> Optional[Any]: 511 | """Render a template for the given page data and arguments. 512 | 513 | If `template_definition` is a string, then it's a global template. 514 | The function will try to find the template you defined earlier via `@global_template` in the list of global templates. 515 | If `template_definition` is a callable, then it's a local template. 516 | The template will be rendered by calling the template function.""" 517 | if isinstance(template_definition, str): 518 | if template_definition in _global_templates: 519 | return _global_templates[template_definition](route_data, arguments) 520 | 521 | else: 522 | route_data.navigator._logger.error(f'No global template found with the name: "{template_definition}". Ensure the template is registered and its name is correct.') 523 | 524 | else: 525 | return template_definition(route_data, arguments) 526 | 527 | 528 | def global_template(template_name: Optional[str]=None) -> Any: 529 | """Register a global template to the last initialized navigator. 530 | 531 | This function registers the template and associates it with a given template definition. 532 | The only difference is the name. You can specify the name in the first argument. 533 | or this function will fetch the given template function name automatically.""" 534 | if isinstance(template_name, Callable): 535 | _global_templates[template_name.__name__] = template_name 536 | 537 | else: 538 | def _global_template(template: TemplateDefinition) -> None: 539 | _global_templates[template.__name__ if not template_name or not isinstance(template_name, str) else template_name] = template 540 | 541 | return _global_template 542 | 543 | 544 | def fn_process(start: str='/', virtual: bool=False, routes: Routes={}, route_change_callback: RouteChangeCallback=None, startup_args: Arguments=(), public_startup_parameters: RouteParameters={}) -> Callable[[Page], None]: 545 | """Shortcut to skip main function implementation and just calling `fn_process` in Flet's `app` function. 546 | 547 | The best way to explain this function is to show an example: 548 | ``` 549 | @route('/') 550 | def main(rd: RouteContext) -> None: 551 | ... 552 | 553 | app(fn_process()) # Instead of: app(lambda page: PublicFletNavigator(page).process(page)) 554 | ```""" 555 | return lambda page: ( 556 | fn := PublicFletNavigator(page, routes, route_change_callback), 557 | 558 | setattr(fn, 'route', start), 559 | 560 | fn.process(page, startup_args, public_startup_parameters)) \ 561 | if not virtual else ( 562 | fn := VirtualFletNavigator(routes, route_change_callback), 563 | 564 | setattr(fn, 'route', start), 565 | 566 | fn.process(page, startup_args) 567 | ) 568 | --------------------------------------------------------------------------------