flet 0.70.0.dev5776__py3-none-any.whl → 0.70.0.dev5835__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flet might be problematic. Click here for more details.

Files changed (44) hide show
  1. flet/__init__.py +32 -4
  2. flet/components/__init__.py +0 -0
  3. flet/components/component.py +346 -0
  4. flet/components/component_decorator.py +24 -0
  5. flet/components/component_owned.py +22 -0
  6. flet/components/hooks/__init__.py +0 -0
  7. flet/components/hooks/hook.py +12 -0
  8. flet/components/hooks/use_callback.py +28 -0
  9. flet/components/hooks/use_context.py +91 -0
  10. flet/components/hooks/use_effect.py +104 -0
  11. flet/components/hooks/use_memo.py +52 -0
  12. flet/components/hooks/use_state.py +58 -0
  13. flet/components/memo.py +34 -0
  14. flet/components/observable.py +269 -0
  15. flet/components/public_utils.py +10 -0
  16. flet/components/utils.py +85 -0
  17. flet/controls/base_control.py +34 -10
  18. flet/controls/base_page.py +44 -40
  19. flet/controls/context.py +22 -1
  20. flet/controls/control_event.py +19 -2
  21. flet/controls/core/column.py +5 -0
  22. flet/controls/core/drag_target.py +17 -8
  23. flet/controls/core/row.py +5 -0
  24. flet/controls/core/view.py +6 -6
  25. flet/controls/id_counter.py +24 -0
  26. flet/controls/material/divider.py +6 -0
  27. flet/controls/material/textfield.py +10 -1
  28. flet/controls/material/vertical_divider.py +6 -0
  29. flet/controls/object_patch.py +434 -197
  30. flet/controls/page.py +203 -84
  31. flet/controls/services/haptic_feedback.py +0 -3
  32. flet/controls/services/shake_detector.py +0 -3
  33. flet/messaging/flet_socket_server.py +13 -6
  34. flet/messaging/session.py +103 -10
  35. flet/{controls/session_storage.py → messaging/session_store.py} +2 -2
  36. flet/version.py +1 -1
  37. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev5835.dist-info}/METADATA +5 -5
  38. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev5835.dist-info}/RECORD +41 -28
  39. flet/controls/cache.py +0 -87
  40. flet/controls/control_id.py +0 -22
  41. flet/controls/core/state_view.py +0 -60
  42. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev5835.dist-info}/WHEEL +0 -0
  43. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev5835.dist-info}/entry_points.txt +0 -0
  44. {flet-0.70.0.dev5776.dist-info → flet-0.70.0.dev5835.dist-info}/top_level.txt +0 -0
flet/controls/page.py CHANGED
@@ -19,9 +19,11 @@ from urllib.parse import urlparse
19
19
 
20
20
  from flet.auth.authorization import Authorization
21
21
  from flet.auth.oauth_provider import OAuthProvider
22
+ from flet.components.component import Renderer
23
+ from flet.components.public_utils import unwrap_component
22
24
  from flet.controls.base_control import BaseControl, control
23
25
  from flet.controls.base_page import BasePage
24
- from flet.controls.context import _context_page
26
+ from flet.controls.context import _context_page, context
25
27
  from flet.controls.control import Control
26
28
  from flet.controls.control_event import (
27
29
  ControlEvent,
@@ -40,7 +42,6 @@ from flet.controls.services.service import Service
40
42
  from flet.controls.services.shared_preferences import SharedPreferences
41
43
  from flet.controls.services.storage_paths import StoragePaths
42
44
  from flet.controls.services.url_launcher import UrlLauncher
43
- from flet.controls.session_storage import SessionStorage
44
45
  from flet.controls.types import (
45
46
  AppLifecycleState,
46
47
  Brightness,
@@ -50,6 +51,7 @@ from flet.controls.types import (
50
51
  Wrapper,
51
52
  )
52
53
  from flet.utils import is_pyodide
54
+ from flet.utils.deprecated import deprecated
53
55
  from flet.utils.strings import random_string
54
56
 
55
57
  if not is_pyodide():
@@ -70,10 +72,6 @@ except ImportError:
70
72
 
71
73
 
72
74
  logger = logging.getLogger("flet")
73
- try:
74
- from typing import ParamSpec
75
- except ImportError:
76
- from typing_extensions import ParamSpec
77
75
 
78
76
 
79
77
  AT = TypeVar("AT", bound=Authorization)
@@ -175,18 +173,12 @@ class Page(BasePage):
175
173
 
176
174
  sess: InitVar["Session"]
177
175
  """
178
- TBD
176
+ The session that this page belongs to.
179
177
  """
180
178
 
181
179
  multi_views: list[MultiView] = field(default_factory=list)
182
180
  """
183
- TBD
184
- """
185
-
186
- window: Window = field(default_factory=lambda: Window())
187
- """
188
- Provides properties/methods/events to monitor and control the
189
- app's native OS window.
181
+ The list of multi-views associated with this page.
190
182
  """
191
183
 
192
184
  browser_context_menu: BrowserContextMenu = field(
@@ -204,82 +196,97 @@ class Page(BasePage):
204
196
  default_factory=lambda: SharedPreferences(), metadata={"skip": True}
205
197
  )
206
198
  """
207
- TBD
199
+ Provides a persistent key-value storage for simple data types.
208
200
  """
209
201
 
210
202
  clipboard: Clipboard = field(
211
203
  default_factory=lambda: Clipboard(), metadata={"skip": True}
212
204
  )
213
205
  """
214
- TBD
206
+ Provides access to the system clipboard.
215
207
  """
216
208
 
217
209
  storage_paths: StoragePaths = field(
218
210
  default_factory=lambda: StoragePaths(), metadata={"skip": True}
219
211
  )
220
212
  """
221
- TBD
213
+ Provides the information about common storage paths.
222
214
  """
223
215
 
224
216
  url_launcher: UrlLauncher = field(
225
217
  default_factory=lambda: UrlLauncher(), metadata={"skip": True}
226
218
  )
227
219
  """
228
- TBD
220
+ Provides methods for launching URLs.
229
221
  """
230
222
 
231
- _services: list[Service] = field(default_factory=list)
223
+ window: Window = field(default_factory=lambda: Window())
232
224
  """
233
- TBD
225
+ Provides properties/methods/events to monitor and control the
226
+ app's native OS window.
234
227
  """
235
228
 
236
- _user_services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry())
237
- """
238
- TBD
229
+ route: str = "/"
239
230
  """
231
+ Gets current app route.
240
232
 
241
- route: Optional[str] = None
242
- """
243
- Get or sets page's navigation route. See
244
- [Navigation and routing](https://flet.dev/docs/getting-started/navigation-and-routing)
245
- section for more information and examples.
233
+ Note:
234
+ This property is read-only.
246
235
  """
247
236
 
248
237
  web: bool = False
249
238
  """
250
239
  `True` if the application is running in the web browser.
240
+
241
+ Note:
242
+ This property is read-only.
251
243
  """
252
244
 
253
245
  pwa: bool = False
254
246
  """
255
247
  `True` if the application is running as Progressive Web App (PWA).
256
248
 
257
- Value is read-only.
249
+ Note:
250
+ This property is read-only.
258
251
  """
259
-
260
252
  debug: bool = False
261
253
  """
262
254
  `True` if Flutter client of Flet app is running in debug mode.
255
+
256
+ Note:
257
+ This property is read-only.
263
258
  """
264
259
 
265
260
  wasm: bool = False
266
261
  """
267
- TBD
262
+ `True` if the application is running in WebAssembly (WASM) mode.
263
+
264
+ Note:
265
+ This property is read-only.
268
266
  """
269
267
 
270
268
  test: bool = False
271
269
  """
272
- TBD
270
+ `True` if the application is running with test mode.
271
+
272
+ Note:
273
+ This property is read-only.
273
274
  """
274
275
 
275
276
  multi_view: bool = False
276
277
  """
277
- TBD
278
+ `True` if the application is running with multi-view support.
279
+
280
+ Note:
281
+ This property is read-only.
278
282
  """
279
283
 
280
- platform: Optional[PagePlatform] = None
284
+ pyodide: bool = False
281
285
  """
282
- The operating system the application is running on.
286
+ `True` if the application is running in Pyodide (WebAssembly) mode.
287
+
288
+ Note:
289
+ This property is read-only.
283
290
  """
284
291
 
285
292
  platform_brightness: Optional[Brightness] = None
@@ -295,7 +302,7 @@ class Page(BasePage):
295
302
  IP address of the connected user.
296
303
 
297
304
  Note:
298
- This property is web only.
305
+ This property is web- and read-only only.
299
306
  """
300
307
 
301
308
  client_user_agent: Optional[str] = None
@@ -303,7 +310,12 @@ class Page(BasePage):
303
310
  Browser details of the connected user.
304
311
 
305
312
  Note:
306
- This property is web only.
313
+ This property is web- and read-only only.
314
+ """
315
+
316
+ platform: Optional[PagePlatform] = None
317
+ """
318
+ The operating system the application is running on.
307
319
  """
308
320
 
309
321
  fonts: Optional[dict[str, str]] = None
@@ -400,6 +412,8 @@ class Page(BasePage):
400
412
  """
401
413
  TBD
402
414
  """
415
+ _services: list[Service] = field(default_factory=list)
416
+ _user_services: ServiceRegistry = field(default_factory=lambda: ServiceRegistry())
403
417
 
404
418
  def __post_init__(
405
419
  self,
@@ -420,7 +434,6 @@ class Page(BasePage):
420
434
  ]
421
435
  self.__last_route = None
422
436
  self.__query: QueryString = QueryString(self)
423
- self.__session_storage: SessionStorage = SessionStorage()
424
437
  self.__authorization: Optional[Authorization] = None
425
438
 
426
439
  def get_control(self, id: int) -> Optional[BaseControl]:
@@ -442,7 +455,37 @@ class Page(BasePage):
442
455
  ft.run(main)
443
456
  ```
444
457
  """
445
- return self.get_session().index.get(id)
458
+ return self.session.index.get(id)
459
+
460
+ def render(
461
+ self,
462
+ component: Callable[..., Union[list[View], View, list[Control], Control]],
463
+ *args,
464
+ **kwargs,
465
+ ):
466
+ logger.debug("Page.render()")
467
+ self._notify = self.__notify
468
+ self.views[0].controls = Renderer().render(component, *args, **kwargs)
469
+ self.__render()
470
+
471
+ def render_views(
472
+ self,
473
+ component: Callable[..., Union[list[View], View, list[Control], Control]],
474
+ *args,
475
+ **kwargs,
476
+ ):
477
+ logger.debug("Page.render_views()")
478
+ self._notify = self.__notify
479
+ self.views = Renderer().render(component, *args, **kwargs)
480
+ self.__render()
481
+
482
+ def __render(self):
483
+ self.update()
484
+ context.enable_components_mode()
485
+ self.session.start_updates_scheduler()
486
+
487
+ def schedule_update(self):
488
+ self.session.schedule_update(self)
446
489
 
447
490
  def update(self, *controls) -> None:
448
491
  if len(controls) == 0:
@@ -450,17 +493,15 @@ class Page(BasePage):
450
493
  else:
451
494
  self.__update(*controls)
452
495
 
453
- def get_session(self):
454
- if sess := self.__session():
455
- return sess
456
- raise Exception("An attempt to fetch destroyed session.")
496
+ def __notify(self, name: str, value: Any):
497
+ self.schedule_update()
457
498
 
458
499
  def __update(self, *controls: Control):
459
500
  for c in controls:
460
- self.get_session().patch_control(c)
501
+ self.session.patch_control(c)
461
502
 
462
503
  def error(self, message: str) -> None:
463
- self.get_session().error(message)
504
+ self.session.error(message)
464
505
 
465
506
  def before_event(self, e: ControlEvent):
466
507
  if isinstance(e, RouteChangeEvent):
@@ -468,12 +509,15 @@ class Page(BasePage):
468
509
  return False
469
510
  self.__last_route = e.route
470
511
  self.query()
512
+
471
513
  elif isinstance(e, ViewPopEvent):
472
- view = next((v for v in self.views if v.route == e.data), None)
473
- if view is None:
474
- return False
475
- e.view = view
476
- return True
514
+ views = unwrap_component(self.views)
515
+ view_index = next(
516
+ (i for i, v in enumerate(views) if v.route == e.route), None
517
+ )
518
+ if view_index is not None:
519
+ e.view = views[view_index]
520
+
477
521
  return super().before_event(e)
478
522
 
479
523
  def run_task(
@@ -491,7 +535,7 @@ class Page(BasePage):
491
535
  raise TypeError("handler must be a coroutine function")
492
536
 
493
537
  future = asyncio.run_coroutine_threadsafe(
494
- handler(*args, **kwargs), self.get_session().connection.loop
538
+ handler(*args, **kwargs), self.session.connection.loop
495
539
  )
496
540
 
497
541
  def _on_completion(f):
@@ -527,13 +571,14 @@ class Page(BasePage):
527
571
  if is_pyodide():
528
572
  handler_with_context(*args, **kwargs)
529
573
  else:
530
- loop = self.get_session().connection.loop
574
+ loop = self.session.connection.loop
531
575
  loop.call_soon_threadsafe(
532
576
  loop.run_in_executor,
533
577
  self.executor,
534
578
  partial(handler_with_context, *args, **kwargs),
535
579
  )
536
580
 
581
+ @deprecated("Use push_route() instead.", version="0.70.0", show_parentheses=True)
537
582
  def go(
538
583
  self, route: str, skip_route_change_event: bool = False, **kwargs: Any
539
584
  ) -> None:
@@ -542,20 +587,76 @@ class Page(BasePage):
542
587
  [`page.on_route_change`](#on_route_change) event handler to update views and
543
588
  finally calls `page.update()`.
544
589
  """
545
- self.route = route if not kwargs else route + self.query.post(kwargs)
546
590
 
547
- if not skip_route_change_event:
548
- e = RouteChangeEvent(
549
- name="route_change", control=self, data=self.route, route=self.route
550
- )
551
- if self.on_route_change:
552
- if asyncio.iscoroutinefunction(self.on_route_change):
553
- asyncio.create_task(self.on_route_change(e))
554
- elif callable(self.on_route_change):
555
- self.on_route_change(e)
591
+ self.push_route(route, **kwargs)
556
592
 
557
- self.update()
558
- self.query() # Update query url (required when using go)
593
+ def push_route(self, route: str, **kwargs: Any) -> None:
594
+ """
595
+ Pushes a new navigation route to the browser history stack.
596
+ Changing route will fire [`page.on_route_change`](#on_route_change) event
597
+ handler.
598
+
599
+ Example:
600
+
601
+ ```python
602
+ import flet as ft
603
+
604
+
605
+ def main(page: ft.Page):
606
+ page.title = "Push Route Example"
607
+
608
+ def route_change(e):
609
+ page.views.clear()
610
+ page.views.append(
611
+ ft.View(
612
+ route="/",
613
+ controls=[
614
+ ft.AppBar(title=ft.Text("Flet app")),
615
+ ft.ElevatedButton(
616
+ "Visit Store",
617
+ on_click=lambda _: page.push_route("/store"),
618
+ ),
619
+ ],
620
+ )
621
+ )
622
+ if page.route == "/store":
623
+ page.views.append(
624
+ ft.View(
625
+ route="/store",
626
+ can_pop=True,
627
+ controls=[
628
+ ft.AppBar(title=ft.Text("Store")),
629
+ ft.ElevatedButton(
630
+ "Go Home", on_click=lambda _: page.push_route("/")
631
+ ),
632
+ ],
633
+ )
634
+ )
635
+
636
+ def view_pop(e):
637
+ page.views.pop()
638
+ top_view = page.views[-1]
639
+ page.push_route(top_view.route)
640
+
641
+ page.on_route_change = route_change
642
+ page.on_view_pop = view_pop
643
+
644
+
645
+ ft.run(main)
646
+ ```
647
+
648
+ Args:
649
+ route: New navigation route.
650
+ **kwargs: Additional query string parameters to be added to the route.
651
+ """
652
+
653
+ new_route = route if not kwargs else route + self.query.post(kwargs)
654
+ asyncio.create_task(
655
+ self._invoke_method(
656
+ "push_route",
657
+ arguments={"route": new_route},
658
+ )
659
+ )
559
660
 
560
661
  def get_upload_url(self, file_name: str, expires: int) -> str:
561
662
  """
@@ -577,7 +678,7 @@ class Page(BasePage):
577
678
  ft.run(main, upload_dir="uploads")
578
679
  ```
579
680
  """
580
- return self.get_session().connection.get_upload_url(file_name, expires)
681
+ return self.session.connection.get_upload_url(file_name, expires)
581
682
 
582
683
  async def login(
583
684
  self,
@@ -613,9 +714,9 @@ class Page(BasePage):
613
714
  if redirect_to_page:
614
715
  up = urlparse(provider.redirect_url)
615
716
  auth_attrs["completePageUrl"] = up._replace(
616
- path=f"{self.get_session().connection.page_name}{self.route}"
717
+ path=f"{self.session.connection.page_name}{self.route}"
617
718
  ).geturl()
618
- self.get_session().connection.oauth_authorize(auth_attrs)
719
+ self.session.connection.oauth_authorize(auth_attrs)
619
720
  if on_open_authorization_url:
620
721
  await on_open_authorization_url(authorization_url)
621
722
  else:
@@ -742,42 +843,60 @@ class Page(BasePage):
742
843
  """
743
844
  await self.url_launcher.close_in_app_web_view()
744
845
 
745
- # query
846
+ @property
847
+ def session(self) -> "Session":
848
+ """
849
+ The session that this page belongs to.
850
+ """
851
+ if sess := self.__session():
852
+ return sess
853
+ raise Exception("An attempt to fetch destroyed session.")
854
+
746
855
  @property
747
856
  def query(self) -> QueryString:
857
+ """
858
+ The query parameters of the current page.
859
+ """
748
860
  return self.__query
749
861
 
750
- # url
751
862
  @property
752
863
  def url(self) -> Optional[str]:
753
- return self.get_session().connection.page_url
864
+ """
865
+ The URL of the current page.
866
+ """
867
+ return self.session.connection.page_url
754
868
 
755
- # name
756
869
  @property
757
870
  def name(self) -> str:
758
- return self.get_session().connection.page_name
871
+ """
872
+ The name of the current page.
873
+ """
874
+ return self.session.connection.page_name
759
875
 
760
- # loop
761
876
  @property
762
877
  def loop(self) -> asyncio.AbstractEventLoop:
763
- return self.get_session().connection.loop
878
+ """
879
+ The event loop for the current page.
880
+ """
881
+ return self.session.connection.loop
764
882
 
765
- # executor
766
883
  @property
767
884
  def executor(self) -> Optional[ThreadPoolExecutor]:
768
- return self.get_session().connection.executor
885
+ """
886
+ The executor for the current page.
887
+ """
888
+ return self.session.connection.executor
769
889
 
770
- # auth
771
890
  @property
772
891
  def auth(self) -> Optional[Authorization]:
892
+ """
893
+ The current authorization context, or `None` if the user is not authorized.
894
+ """
773
895
  return self.__authorization
774
896
 
775
- # pubsub
776
897
  @property
777
898
  def pubsub(self) -> "PubSubClient":
778
- return self.get_session().pubsub_client
779
-
780
- # session_storage
781
- @property
782
- def session(self) -> SessionStorage:
783
- return self.__session_storage
899
+ """
900
+ The PubSub client for the current page.
901
+ """
902
+ return self.session.pubsub_client
@@ -8,9 +8,6 @@ __all__ = ["HapticFeedback"]
8
8
  class HapticFeedback(Service):
9
9
  """
10
10
  Allows access to the haptic feedback interface on the device.
11
-
12
- It is non-visual and should be added to
13
- [`Page.services`][flet.] list before it can be used.
14
11
  """
15
12
 
16
13
  async def heavy_impact(self):
@@ -12,9 +12,6 @@ __all__ = ["ShakeDetector"]
12
12
  class ShakeDetector(Service):
13
13
  """
14
14
  Detects phone shakes.
15
-
16
- It is non-visual and should be added to
17
- [`Page.services`][flet.] list before it can be used.
18
15
  """
19
16
 
20
17
  minimum_shake_count: int = 1
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import Any, Optional
9
9
 
10
10
  import msgpack
11
+
11
12
  from flet.controls.base_control import BaseControl
12
13
  from flet.messaging.connection import Connection
13
14
  from flet.messaging.protocol import (
@@ -236,17 +237,23 @@ class FletSocketServer(Connection):
236
237
 
237
238
  logger.debug("Cancelling pending tasks...")
238
239
 
239
- tasks = [task for task in [
240
- self.__receive_loop_task,
241
- self.__send_loop_task,
242
- self.__serve_task,
243
- ] if task]
240
+ tasks = [
241
+ task
242
+ for task in [
243
+ self.__receive_loop_task,
244
+ self.__send_loop_task,
245
+ self.__serve_task,
246
+ ]
247
+ if task
248
+ ]
244
249
 
245
250
  for task in tasks:
246
251
  task.cancel()
247
252
 
248
253
  try:
249
- await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=1.0)
254
+ await asyncio.wait_for(
255
+ asyncio.gather(*tasks, return_exceptions=True), timeout=1.0
256
+ )
250
257
  except asyncio.TimeoutError:
251
258
  logger.warning("Some tasks did not exit in time, skipping.")
252
259
  except asyncio.CancelledError: