├── 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 |
--------------------------------------------------------------------------------