1
- from typing import Dict , Optional , Type , Any
1
+ from typing import Dict , Optional , Type , Any , Callable , TypeVar , List , Tuple
2
2
import urllib .parse
3
3
import flet as ft
4
4
from .model import Model
5
5
6
+ # Type variable for decorator return type annotation
7
+ T = TypeVar ('T' , bound = Type [Model ])
8
+
9
+ # Global storage for route registrations
10
+ _pending_registrations : List [Tuple [str , Type [Model ]]] = []
11
+
12
+
13
+ def route (route_path : str ) -> Callable [[T ], T ]:
14
+ """
15
+ Decorator for registering a model class with a specific route.
16
+
17
+ Example:
18
+ @route('todo')
19
+ class TodoModel(Model):
20
+ # model implementation
21
+
22
+ Args:
23
+ route_path: The route path to register
24
+
25
+ Returns:
26
+ The decorated model class
27
+ """
28
+
29
+ def decorator (model_class : T ) -> T :
30
+ if not issubclass (model_class , Model ):
31
+ raise TypeError (f"Class { model_class .__name__ } must inherit from Model" )
32
+
33
+ # Store the route in the class for reference
34
+ model_class .route = route_path
35
+
36
+ # Save for deferred registration
37
+ _pending_registrations .append ((route_path , model_class ))
38
+ return model_class
39
+
40
+ return decorator
41
+
6
42
7
43
class Router :
8
44
"""Router class for handling navigation in Flet applications."""
@@ -11,22 +47,29 @@ class Router:
11
47
_routes : Dict [str , Model ] = {}
12
48
_page : Optional [ft .Page ] = None
13
49
_view_cache : Dict [str , ft .View ] = {}
50
+ _initialized : bool = False
14
51
15
52
def __new__ (cls , * args , ** kwargs ):
16
53
if cls ._instance is None :
17
54
cls ._instance = super (Router , cls ).__new__ (cls )
18
55
return cls ._instance
19
56
20
57
def __init__ (self , * route_maps : Dict [str , Model ]):
21
- if not self ._routes :
22
- self . _routes = {}
23
- for route_map in route_maps :
24
- self ._routes . update ( route_map )
58
+ if self ._initialized :
59
+ return
60
+
61
+ self ._routes = {}
25
62
26
- if route_maps and list (route_maps [0 ].values ()):
27
- first_model = list (route_maps [0 ].values ())[0 ]
28
- self ._page = first_model .page
29
- self ._setup_routing ()
63
+ # Add any routes from explicit dictionaries
64
+ for route_map in route_maps :
65
+ self ._routes .update (route_map )
66
+
67
+ # If we have routes and a page from dict-based initialization
68
+ if route_maps and list (route_maps [0 ].values ()):
69
+ first_model = list (route_maps [0 ].values ())[0 ]
70
+ self ._page = first_model .page
71
+ self ._setup_routing ()
72
+ self ._initialized = True
30
73
31
74
def _parse_route_and_hash (self , route : str ) -> tuple [list [str ], dict [str , dict ]]:
32
75
parts = route .split ('/' )
@@ -47,7 +90,6 @@ def _parse_route_and_hash(self, route: str) -> tuple[list[str], dict[str, dict]]
47
90
48
91
return route_parts , hash_data
49
92
50
-
51
93
def _setup_routing (self ) -> None :
52
94
"""Set up route handling and initialize default route."""
53
95
if not self ._page :
@@ -56,11 +98,6 @@ def _setup_routing(self) -> None:
56
98
self ._page .on_route_change = self ._handle_route_change
57
99
self ._page .on_view_pop = self ._handle_view_pop
58
100
59
- if not self ._page .route or self ._page .route == '/' :
60
- default_route = next (iter (self ._routes .keys ()))
61
- self ._page .route = default_route
62
- self ._page .go (default_route )
63
-
64
101
def _handle_route_change (self , e : ft .RouteChangeEvent ) -> None :
65
102
route_parts , hash_data = self ._parse_route_and_hash (self ._page .route .lstrip ('/' ))
66
103
self ._page .views .clear ()
@@ -104,4 +141,63 @@ def get_current_model(cls) -> Optional[Model]:
104
141
return None
105
142
106
143
current_route = cls ._instance ._page .route .split ('/' )[- 1 ]
107
- return cls ._instance ._routes .get (current_route )
144
+ return cls ._instance ._routes .get (current_route )
145
+
146
+ # Auto-initialize with the page when app starts
147
+ @classmethod
148
+ def initialize (cls , page : ft .Page ) -> None :
149
+ """
150
+ Initialize the router with a page and register any pending routes.
151
+ This also sets up the route handlers.
152
+
153
+ Args:
154
+ page: The flet Page instance
155
+ """
156
+ global _pending_registrations
157
+
158
+ # Create instance if it doesn't exist
159
+ if not cls ._instance :
160
+ cls ._instance = cls ()
161
+
162
+ cls ._instance ._page = page
163
+ cls ._instance ._initialized = True
164
+
165
+ # Register any routes that were decorated
166
+ for route_path , model_class in _pending_registrations :
167
+ model_instance = model_class (page )
168
+ cls ._instance ._routes [route_path ] = model_instance
169
+
170
+ # Clear pending registrations
171
+ _pending_registrations = []
172
+
173
+ # Setup routing
174
+ cls ._instance ._setup_routing ()
175
+
176
+ # Return the instance for method chaining if needed
177
+ return cls ._instance
178
+
179
+ @classmethod
180
+ def navigate (cls , route : str ) -> None :
181
+ """
182
+ Navigate to a specific route, ensuring the router is initialized first.
183
+
184
+ Args:
185
+ route: The route to navigate to
186
+ """
187
+ if cls ._instance and cls ._instance ._page :
188
+ cls ._instance ._page .go (route )
189
+
190
+
191
+ # Modify flet.Page to inject our router
192
+ def enhanced_page_go (self , route , * args , ** kwargs ):
193
+ """Enhanced go method that ensures router is initialized before navigation"""
194
+ # Initialize router if there are pending routes
195
+ if _pending_registrations and not Router ._instance :
196
+ Router .initialize (self )
197
+ # Call the original go method
198
+ original_go (self , route , * args , ** kwargs )
199
+
200
+
201
+ # Store original method and apply patch
202
+ original_go = ft .Page .go
203
+ ft .Page .go = enhanced_page_go
0 commit comments