From 7367050f22edb02afebecaca5de70c3a0e389827 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 10 Jun 2026 18:02:20 -0700 Subject: [PATCH 01/47] fix(controls): preserve concrete value type when constructing ValueKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ValueKey(controlKey.value)` produced `ValueKey(value)` because `controlKey.value` is statically typed `Object`. Flutter's `ValueKey.==` is runtimeType-strict, so `ValueKey('foo')` never equals `ValueKey('foo')` — making `find.byKey(Key('foo'))` / `find.byKey(ValueKey('foo'))` in flutter_test fail to locate any Flet-rendered control by user-assigned key. Switch-dispatch on the runtime type so a String value yields `ValueKey`, int → `ValueKey`, etc. This matches what `Key('foo')` (factory for `ValueKey('foo')`) and analogous test-side constructions produce. Repro: flet_example in flet-dev/serious-python on the dart-bridge branch — its integration_test/app_test.dart with `find.byKey(Key('increment'))` for an IconButton with `key="increment"` was finding 0 widgets until this fix. --- packages/flet/lib/src/controls/control_widget.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/flet/lib/src/controls/control_widget.dart b/packages/flet/lib/src/controls/control_widget.dart index ae4db35a96..ead9a54c0c 100644 --- a/packages/flet/lib/src/controls/control_widget.dart +++ b/packages/flet/lib/src/controls/control_widget.dart @@ -29,7 +29,19 @@ class ControlWidget extends StatelessWidget { FletBackend.of(context).globalKeys[controlKey.toString()] = key as GlobalKey; } else if (controlKey != null) { - key = ValueKey(controlKey.value); + // Preserve the concrete value type so the resulting ValueKey matches + // what callers construct in tests, e.g. `find.byKey(Key('foo'))` which + // resolves to `ValueKey('foo')`. `ValueKey(controlKey.value)` + // would produce `ValueKey(...)` because `controlKey.value` is + // statically typed `Object`, and ValueKey's `==` is runtimeType-strict + // — `ValueKey` is never equal to `ValueKey`. + key = switch (controlKey.value) { + String v => ValueKey(v), + int v => ValueKey(v), + double v => ValueKey(v), + bool v => ValueKey(v), + _ => ValueKey(controlKey.value), + }; } return control.buildInControlContext((context) { From 7c380074fd526e4d0ca7767ddcaec471e05fda0f Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 10 Jun 2026 20:48:55 -0700 Subject: [PATCH 02/47] feat(transport): add dart_bridge in-process transport (alongside socket) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a third transport (`FletDartBridgeServer` + Dart-side channel-builder injection) that exchanges Flet's MsgPack protocol over the in-process `dart_bridge` byte channel — the prebuilt-binary FFI bridge consumed by serious_python plugins via `package:serious_python/bridge.dart`. Coexists with the existing UDS / TCP socket transport. Activation: - Python: `FLET_DART_BRIDGE_PORT=` env var + `is_embedded()` true. - Dart: pass `FletApp(channelBuilder: ...)` — the embedder constructs a `FletBackendChannel` impl wrapping a `PythonBridge` and feeds it in. `flet` package itself stays Python-independent: it does NOT depend on `serious_python` or know about `PythonBridge`. The whole PythonBridge wiring lives in the embedder's code (proven by a forthcoming `flet_ffi_example` in serious-python). What lands here in `flet` is just the seam. Python side: - New `flet/messaging/flet_dart_bridge_server.py` — `FletDartBridgeServer` with the same protocol dispatch as `FletSocketServer`, lazy-imported so non-embedded runs never load `dart_bridge`. Inbound: `__on_bytes` enqueues payloads from the C-callback thread onto an asyncio.Queue drained by `__inbound_loop`. Outbound: `send_message` calls `dart_bridge.send_bytes(port, packb(...))`. - `flet/app.py`: `run_async` selection block grows a third arm: if is_embedded() and FLET_DART_BRIDGE_PORT: dart_bridge elif is_socket_server: socket (existing) else: web (existing) - New helper `__run_dart_bridge_server` modelled on `__run_socket_server`. Dart side: - New `FletBackendChannelBuilder` typedef in `transport/flet_backend_channel.dart`. - `FletApp` accepts optional `channelBuilder`; `FletBackend` honours it in `connect()` and skips the URL-scheme factory when present. URL-based routing for socket / websocket / mock / Pyodide is unchanged. Wire protocol — unchanged. Same `[ClientAction, body]` MsgPack frames, same encoder/decoder, same Session dispatch. Only the byte transport differs. --- packages/flet/lib/src/flet_app.dart | 11 +- packages/flet/lib/src/flet_backend.dart | 26 +- .../src/transport/flet_backend_channel.dart | 12 + sdk/python/packages/flet/src/flet/app.py | 59 ++++- .../flet/messaging/flet_dart_bridge_server.py | 250 ++++++++++++++++++ 5 files changed, 345 insertions(+), 13 deletions(-) create mode 100644 sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py diff --git a/packages/flet/lib/src/flet_app.dart b/packages/flet/lib/src/flet_app.dart index ee0873a934..390ebcc8e9 100644 --- a/packages/flet/lib/src/flet_app.dart +++ b/packages/flet/lib/src/flet_app.dart @@ -7,6 +7,7 @@ import 'flet_backend.dart'; import 'flet_extension.dart'; import 'models/control.dart'; import 'testing/tester.dart'; +import 'transport/flet_backend_channel.dart'; /// FletApp - The top-level widget that initializes everything class FletApp extends StatefulWidget { @@ -26,6 +27,12 @@ class FletApp extends StatefulWidget { final Tester? tester; final bool multiView; + /// Optional escape hatch for embedders that bring their own transport + /// (e.g. `serious_python`'s in-process FFI bridge). When set, this builder + /// is invoked from [FletBackend.connect] in place of the URL-scheme + /// factory; [pageUrl] is then irrelevant for transport selection. + final FletBackendChannelBuilder? channelBuilder; + const FletApp( {super.key, required this.pageUrl, @@ -42,7 +49,8 @@ class FletApp extends StatefulWidget { this.args, this.forcePyodide, this.tester, - this.multiView = false}); + this.multiView = false, + this.channelBuilder}); @override State createState() => _FletAppState(); @@ -76,6 +84,7 @@ class _FletAppState extends State { forcePyodide: widget.forcePyodide, tester: widget.tester, multiView: widget.multiView, + channelBuilder: widget.channelBuilder, parentFletBackend: Provider.of(context, listen: false)); }, diff --git a/packages/flet/lib/src/flet_backend.dart b/packages/flet/lib/src/flet_backend.dart index f173ee8584..641d979a8c 100644 --- a/packages/flet/lib/src/flet_backend.dart +++ b/packages/flet/lib/src/flet_backend.dart @@ -62,6 +62,7 @@ class FletBackend extends ChangeNotifier { int _reconnectStarted = 0; int _reconnectDelayMs = 0; FletBackendChannel? _backendChannel; + final FletBackendChannelBuilder? _channelBuilder; final List _sendQueue = []; String route = ""; bool isLoading = true; @@ -104,11 +105,13 @@ class FletBackend extends ChangeNotifier { this.forcePyodide, this.tester, required extensions, + FletBackendChannelBuilder? channelBuilder, FletBackend? parentFletBackend}) : _parentFletBackend = parentFletBackend != null ? WeakReference(parentFletBackend) : null, _reconnectTimeoutMs = reconnectTimeoutMs, - _reconnectIntervalMs = reconnectIntervalMs { + _reconnectIntervalMs = reconnectIntervalMs, + _channelBuilder = channelBuilder { // add Flet extension with core controls and services this.extensions = [...extensions, FletCoreExtension()]; @@ -177,12 +180,21 @@ class FletBackend extends ChangeNotifier { Future connect() async { debugPrint("Connecting to Flet backend $pageUri..."); try { - _backendChannel = FletBackendChannel( - address: pageUri.toString(), - args: args ?? {}, - forcePyodide: forcePyodide == true, - onDisconnect: _onDisconnect, - onMessage: _onMessage); + final builder = _channelBuilder; + if (builder != null) { + // Embedder-supplied transport (e.g. serious_python's in-process FFI + // bridge). The builder is responsible for the entire transport + // lifecycle; we just wire its callbacks to ours. + _backendChannel = builder( + onDisconnect: _onDisconnect, onMessage: _onMessage); + } else { + _backendChannel = FletBackendChannel( + address: pageUri.toString(), + args: args ?? {}, + forcePyodide: forcePyodide == true, + onDisconnect: _onDisconnect, + onMessage: _onMessage); + } await _backendChannel!.connect(); _registerClient(); } catch (e) { diff --git a/packages/flet/lib/src/transport/flet_backend_channel.dart b/packages/flet/lib/src/transport/flet_backend_channel.dart index b4f30c07d5..253b31dc55 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel.dart @@ -10,6 +10,18 @@ import 'flet_backend_channel_web_socket.dart'; typedef FletBackendChannelOnDisconnectCallback = void Function(); typedef FletBackendChannelOnMessageCallback = void Function(Message message); +/// Builds a custom [FletBackendChannel] supplied by the embedder. +/// +/// When provided to [FletApp]/[FletBackend], the channel is used directly and +/// the URL-scheme factory below is skipped — letting embedded runtimes (e.g. +/// `serious_python`'s in-process Dart↔Python FFI bridge) plug in a transport +/// that needs more setup than a `String address` URL can express, without +/// forcing the `flet` package to take a Python-related dependency. +typedef FletBackendChannelBuilder = FletBackendChannel Function({ + required FletBackendChannelOnMessageCallback onMessage, + required FletBackendChannelOnDisconnectCallback onDisconnect, +}); + abstract class FletBackendChannel { factory FletBackendChannel( {required String address, diff --git a/sdk/python/packages/flet/src/flet/app.py b/sdk/python/packages/flet/src/flet/app.py index 24061c5e44..37615bc64c 100644 --- a/sdk/python/packages/flet/src/flet/app.py +++ b/sdk/python/packages/flet/src/flet/app.py @@ -252,15 +252,26 @@ def exit_gracefully(signum, frame): signal.signal(signal.SIGINT, exit_gracefully) signal.signal(signal.SIGTERM, exit_gracefully) - conn = ( - await __run_socket_server( + # Embedded runtime can opt into the in-process dart_bridge transport + # (provided by libdart_bridge from flet-dev/serious-python) by setting + # FLET_DART_BRIDGE_PORT. Falls back to the existing socket / web + # transports when the env var is absent. + bridge_port_env = os.getenv("FLET_DART_BRIDGE_PORT") + if is_embedded() and bridge_port_env: + conn = await __run_dart_bridge_server( + port=int(bridge_port_env), + main=main or target, + before_main=before_main, + ) + elif is_socket_server: + conn = await __run_socket_server( port=port, main=main or target, before_main=before_main, blocking=is_embedded(), ) - if is_socket_server - else await __run_web_server( + else: + conn = await __run_web_server( main=main or target, before_main=before_main, host=host, @@ -273,7 +284,6 @@ def exit_gracefully(signum, frame): no_cdn=no_cdn, on_startup=on_app_startup, ) - ) logger.info("Flet app has started...") @@ -400,6 +410,45 @@ async def __run_socket_server( return conn +async def __run_dart_bridge_server( + port: int, + main: Optional[AppCallable] = None, + before_main: Optional[AppCallable] = None, +): + """ + Start Flet dart_bridge transport and return active connection object. + + This transport exchanges the same MsgPack-framed protocol as + `__run_socket_server`, but over the in-process `dart_bridge` byte channel + instead of a Unix socket — eliminating the socket file, kernel context + switches, and the connect/handshake retry loop for embedded apps. + + Args: + port: Dart native port (passed in via env var by the embedding side; + doubles as the keyed channel identifier). + main: User app entry handler. + before_main: Optional hook called before `main`. + + Returns: + Started dart_bridge-server connection instance. + """ + # Imported lazily so non-embedded runs (web server, native desktop) never + # try to load libdart_bridge. + from flet.messaging.flet_dart_bridge_server import FletDartBridgeServer + + executor = concurrent.futures.ThreadPoolExecutor() + + conn = FletDartBridgeServer( + loop=asyncio.get_running_loop(), + port=port, + on_session_created=__get_on_session_created(main), + before_main=before_main, + executor=executor, + ) + await conn.start() + return conn + + async def __run_web_server( main: Optional[AppCallable], before_main: Optional[AppCallable], diff --git a/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py b/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py new file mode 100644 index 0000000000..de32bd3db6 --- /dev/null +++ b/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py @@ -0,0 +1,250 @@ +""" +In-process dart_bridge transport for Flet's embedded mode. + +`FletDartBridgeServer` is an alternative to `FletSocketServer` that exchanges +the same MsgPack-framed protocol over an in-process byte channel rather than a +Unix-domain socket. The channel is provided by the `dart_bridge` built-in +Python module, registered with CPython via `PyImport_AppendInittab` by +`libdart_bridge` (the native artifact in `flet-dev/dart-bridge`). + +Activation: `flet.app.run_async` instantiates this server when +`FLET_DART_BRIDGE_PORT` is set AND `is_embedded()` is true. The Dart-side port +acts as the channel key in both directions: + +- inbound (Dart → Python): dart_bridge invokes `__on_bytes(payload)` from C + under the GIL whenever the Dart side calls `bridge.send(bytes)`. +- outbound (Python → Dart): `send_message` calls `dart_bridge.send_bytes( + port, packed)` which non-blockingly posts to the Dart native port. + +The MsgPack protocol layer is identical to `FletSocketServer`; only the byte +transport differs. +""" + +import asyncio +import contextlib +import inspect +import logging +import traceback +from collections.abc import Awaitable +from concurrent.futures import ThreadPoolExecutor +from typing import TYPE_CHECKING, Any, Callable, Optional + +import dart_bridge # type: ignore # provided by libdart_bridge inittab +import msgpack + +from flet.controls.base_control import BaseControl +from flet.messaging.connection import Connection +from flet.messaging.protocol import ( + ClientAction, + ClientMessage, + ControlEventBody, + InvokeMethodResponseBody, + RegisterClientRequestBody, + RegisterClientResponseBody, + UpdateControlPropsBody, + configure_encode_object_for_msgpack, + decode_ext_from_msgpack, +) +from flet.messaging.session import Session +from flet.pubsub.pubsub_hub import PubSubHub + +if TYPE_CHECKING: + from flet.app import AppCallable + +logger = logging.getLogger("flet") +transport_log = logging.getLogger("flet_transport") + + +class FletDartBridgeServer(Connection): + """ + Connection transport that uses the `dart_bridge` built-in module instead + of an asyncio Unix-domain socket. See module docstring for the rationale. + + Lifecycle: `start()` registers a byte handler with `dart_bridge`; bytes + arrive synchronously on whatever OS thread Dart called `bridge.send` from, + so the handler is just a thread-safe enqueue onto an asyncio.Queue that an + inbound coroutine drains on the event loop. + """ + + def __init__( + self, + loop: asyncio.AbstractEventLoop, + port: int, + on_session_created: Optional[Callable[[Session], Awaitable[Any]]] = None, + before_main: Optional["AppCallable"] = None, + executor: Optional[ThreadPoolExecutor] = None, + ): + super().__init__() + self.__port = port + self.__on_session_created = on_session_created + self.__before_main = before_main + self.session: Optional[Session] = None + self.__inbound_queue: asyncio.Queue[bytes] = asyncio.Queue() + self.__inbound_task: Optional[asyncio.Task] = None + self.__running_tasks: set[asyncio.Task] = set() + self.loop = loop + self.executor = executor + self.pubsubhub = PubSubHub(loop=loop, executor=executor) + self.page_url = f"dartbridge://{port}" + + async def start(self): + """ + Registers the inbound byte handler with `dart_bridge` and schedules + the inbound dispatch coroutine. Returns immediately — there is no + per-client handshake at the transport level (the first + REGISTER_CLIENT frame from Dart is the application-level handshake). + """ + logger.info("Starting up dart_bridge server on port %s", self.__port) + dart_bridge.set_enqueue_handler_func(self.__port, self.__on_bytes) + self.__inbound_task = asyncio.create_task(self.__inbound_loop()) + + def __on_bytes(self, payload: bytes) -> None: + """ + Receives a byte frame from `dart_bridge`. Called synchronously under + the GIL from C — may run on a non-loop thread, so we marshal onto + the loop via `call_soon_threadsafe`. + """ + self.loop.call_soon_threadsafe(self.__inbound_queue.put_nowait, payload) + + async def __inbound_loop(self): + """ + Drains the inbound queue, feeds bytes into a streaming MsgPack + unpacker, and dispatches each complete frame. + """ + unpacker = msgpack.Unpacker(ext_hook=decode_ext_from_msgpack) + try: + while True: + payload = await self.__inbound_queue.get() + unpacker.feed(payload) + for msg in unpacker: + try: + await self.__on_message(msg) + except Exception: + logger.error( + "Error dispatching dart_bridge frame", exc_info=True + ) + except asyncio.CancelledError: + logger.debug("dart_bridge inbound loop cancelled.") + + async def __on_message(self, data: Any): + """ + Protocol dispatch — identical to `FletSocketServer.__on_message`. + Duplicated here to keep the two transports decoupled; refactor into + a shared base once both have stabilised. + """ + action = ClientAction(data[0]) + body = data[1] + transport_log.debug("_on_message: %s %s", action, body) + task = None + if action == ClientAction.REGISTER_CLIENT: + req = RegisterClientRequestBody(**body) + + # create new session + self.session = Session(self) + + # apply page patch + if not req.session_id: + self.session.apply_page_patch(req.page) + + register_error = "" + try: + if inspect.iscoroutinefunction(self.__before_main): + await self.__before_main(self.session.page) + elif callable(self.__before_main): + self.__before_main(self.session.page) + except Exception as e: + register_error = f"{e}\n{traceback.format_exc()}" + logger.error("Unhandled error in before_main() handler", exc_info=True) + + # register response + self.send_message( + ClientMessage( + ClientAction.REGISTER_CLIENT, + RegisterClientResponseBody( + session_id=self.session.id, + page_patch=self.session.get_page_patch(), + error=register_error, + ), + ) + ) + + if register_error: + self.session.error(register_error) + elif self.__on_session_created is not None: + task = asyncio.create_task(self.__on_session_created(self.session)) + + elif action == ClientAction.CONTROL_EVENT: + req = ControlEventBody(**body) + task = asyncio.create_task( + self.session.dispatch_event(req.target, req.name, req.data) + ) + + elif action == ClientAction.UPDATE_CONTROL_PROPS: + req = UpdateControlPropsBody(**body) + self.session.apply_patch(req.id, req.props) + + elif action == ClientAction.INVOKE_METHOD: + req = InvokeMethodResponseBody(**body) + self.session.handle_invoke_method_results( + req.control_id, req.call_id, req.result, req.error + ) + + else: + raise RuntimeError(f'Unknown message "{action}": {body}') + + if task: + self.__running_tasks.add(task) + task.add_done_callback(self.__running_tasks.discard) + + def send_message(self, message: ClientMessage): + """ + Encodes a protocol message and posts it to the Dart side via + `dart_bridge.send_bytes`. Non-blocking; ordering is preserved by the + Dart VM's port queue. + """ + transport_log.debug("send_message: %s", message) + m = msgpack.packb( + [message.action, message.body], + default=configure_encode_object_for_msgpack(BaseControl), + ) + try: + dart_bridge.send_bytes(self.__port, m) + except Exception: + logger.error("dart_bridge.send_bytes failed", exc_info=True) + + async def close(self): + """ + Releases the dart_bridge handler registration and cancels pending + inbound work. Mirrors `FletSocketServer.close()` so the caller side + in `flet.app.run_async` can treat both transports uniformly. + """ + logger.debug("Closing dart_bridge server...") + try: + dart_bridge.set_enqueue_handler_func(self.__port, None) + except Exception: + logger.debug("Error unregistering dart_bridge handler", exc_info=True) + + session = self.session + self.session = None + if session is not None: + try: + session.close() + except Exception: + logger.debug("Error closing session", exc_info=True) + + if self.__inbound_task and not self.__inbound_task.done(): + self.__inbound_task.cancel() + with contextlib.suppress(asyncio.CancelledError, Exception): + await self.__inbound_task + + for task in list(self.__running_tasks): + if not task.done(): + task.cancel() + if self.__running_tasks: + await asyncio.gather(*self.__running_tasks, return_exceptions=True) + self.__running_tasks.clear() + + if self.executor: + self.executor.shutdown(wait=False, cancel_futures=True) + + logger.debug("dart_bridge server closed.") From 33b3b972373fb3f268fe7acc84876b87dbb9a31c Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 10 Jun 2026 20:50:39 -0700 Subject: [PATCH 03/47] feat(transport): export FletBackendChannel + msgpack helpers from flet.dart (lets embedders implement channelBuilder) --- packages/flet/lib/flet.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/flet/lib/flet.dart b/packages/flet/lib/flet.dart index 6c0933a2e1..4e01c2776d 100644 --- a/packages/flet/lib/flet.dart +++ b/packages/flet/lib/flet.dart @@ -16,6 +16,11 @@ export 'src/flet_service.dart'; export 'src/models/asset_source.dart'; export 'src/models/control.dart'; export 'src/models/page_size_view_model.dart'; +export 'src/protocol/message.dart'; +export 'src/transport/flet_backend_channel.dart'; +export 'src/transport/flet_msgpack_decoder.dart'; +export 'src/transport/flet_msgpack_encoder.dart'; +export 'src/transport/streaming_msgpack_deserializer.dart'; export 'src/routing/deep_linking_bootstrap.dart'; export 'src/testing/test_finder.dart'; export 'src/testing/tester.dart'; From 53bd7ddfce37740245071053d745441e62a66c94 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 10 Jun 2026 21:17:29 -0700 Subject: [PATCH 04/47] fix(transport): park embedded dart_bridge run loop until host shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dart_bridge transport has no accept loop equivalent — start() registers a byte handler with libdart_bridge and returns immediately. Without an explicit wait, run_async() falls through to conn.close() as soon as main() returns, killing the bridge before any Dart-side frame can arrive. The embedded interpreter then exits even though the Flutter host is still running. Mirror the existing url_prefix/socket-server arm: wait on the terminate event when is_embedded() and FLET_DART_BRIDGE_PORT are both set. --- sdk/python/packages/flet/src/flet/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sdk/python/packages/flet/src/flet/app.py b/sdk/python/packages/flet/src/flet/app.py index 37615bc64c..1e07fb2eca 100644 --- a/sdk/python/packages/flet/src/flet/app.py +++ b/sdk/python/packages/flet/src/flet/app.py @@ -315,6 +315,13 @@ def exit_gracefully(signum, frame): with contextlib.suppress(KeyboardInterrupt): await terminate.wait() + elif is_embedded() and bridge_port_env: + # dart_bridge has no serve_forever (no socket accept loop) — the + # embedded interpreter would otherwise exit as soon as the user's + # main() returns. Park here until the host process tears us down. + with contextlib.suppress(KeyboardInterrupt): + await terminate.wait() + elif view == AppView.WEB_BROWSER or view is None or force_web_server: with contextlib.suppress(KeyboardInterrupt): await terminate.wait() From 99434e5de0c1757aaef292426232e6ead12b7a95 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 11 Jun 2026 10:54:24 -0700 Subject: [PATCH 05/47] templates(build): migrate from sockets to PythonBridge FFI transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches the production transport in `flet build`'s generated app from TCP/UDS sockets to the in-process dart_bridge FFI channel that the serious-python `dart-bridge` branch exposes. Web mode (websocket) and developer mode (external Python process over TCP/UDS) stay unchanged — PythonBridge only makes sense when the Python interpreter is embedded in the same OS process as Flutter. main.dart: * Two long-lived PythonBridge instances created in prepareApp(): `_bridge` carries the MsgPack-framed Flet protocol; `_exitBridge` is a dedicated outbound channel for Python's exit code (replaces the legacy stdout-callback ServerSocket). * pageUrl = `dartbridge://`; env exports FLET_DART_BRIDGE_PORT and FLET_DART_BRIDGE_EXIT_PORT. The Python flet package's app.py picks up FLET_DART_BRIDGE_PORT and starts FletDartBridgeServer instead of FletSocketServer. * `_DartBridgeBackendChannel` (lifted from flet_ffi_example): wraps PythonBridge as a FletBackendChannel — streaming msgpack decoder on inbound, encoder + 30s retry loop on outbound. Injected into FletApp via the `channelBuilder` parameter added in the flet PR. * runPythonApp drops the ServerSocket setup; subscribes to `_exitBridge.messages` and reuses the existing error-screen / `exit(code)` handling unchanged. * Dropped the now-unused `getUnusedPort` helper. python.dart: * Drops the `socket` callback channel and FLET_PYTHON_CALLBACK_SOCKET_ADDR. * `flet_exit` posts the exit code as raw UTF-8 bytes via `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`. * stdout/stderr → FLET_APP_CONSOLE file redirection preserved (the Dart side reads it for the error screen on `flet_exit(100)`). pubspec.yaml: * `serious_python` pinned to the dart-bridge branch via git ref — 1.0.1 on pub.dev predates PythonBridge. Swap to a version pin once serious_python ships a release carrying the bridge API. * Added `msgpack_dart: ^1.0.1` for the channel's wire codec. Dev mode (--debug + page URL in args) still creates no bridges and FletApp resolves transport via its URL-scheme factory; web mode reads Uri.base unchanged. --- .../{{cookiecutter.out_dir}}/lib/main.dart | 207 +++++++++++++----- .../{{cookiecutter.out_dir}}/lib/python.dart | 29 ++- .../{{cookiecutter.out_dir}}/pubspec.yaml | 13 +- 3 files changed, 174 insertions(+), 75 deletions(-) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart index a4adf44dcf..de0207a67c 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart @@ -1,14 +1,17 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui'; import 'package:flet/flet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:serious_python/bridge.dart'; import 'package:serious_python/serious_python.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:window_manager/window_manager.dart'; @@ -67,6 +70,14 @@ String assetsDir = ""; String appDir = ""; Map environmentVariables = Map.from(Platform.environment); +// In production (embedded) mode the Flet protocol flows over an in-process +// PythonBridge — no socket file, no TCP. `_exitBridge` is a separate bridge +// dedicated to Python's exit-code transmission (replaces the legacy stdout- +// callback socket). Both are null in web + developer modes where Python is +// either remote or in a separate process. +PythonBridge? _bridge; +PythonBridge? _exitBridge; + void main(List args) async { FletDeepLinkingBootstrap.install(); @@ -86,6 +97,19 @@ void main(List args) async { future: prepareApp(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { + // In production mode prepareApp() created _bridge; wire a + // PythonBridge-backed channel so FletApp talks to the embedded + // Python over the in-process FFI transport. In web + dev modes + // _bridge is null and FletApp falls back to its URL-scheme factory + // (websocket / TCP / UDS). + final channelBuilder = _bridge == null + ? null + : ({ + required FletBackendChannelOnMessageCallback onMessage, + required FletBackendChannelOnDisconnectCallback onDisconnect, + }) => + _DartBridgeBackendChannel(_bridge!, + onMessage: onMessage, onDisconnect: onDisconnect); // OK - start Python program return kIsWeb || (isDesktopPlatform() && _args.isNotEmpty) ? FletApp( @@ -112,6 +136,7 @@ void main(List args) async { assetsDir: assetsDir, showAppStartupScreen: showAppStartupScreen, appStartupScreenMessage: appStartupScreenMessage, + channelBuilder: channelBuilder, extensions: extensions); } }); @@ -197,16 +222,20 @@ Future prepareApp() async { environmentVariables.putIfAbsent( "FLET_PLATFORM", () => defaultTargetPlatform.name.toLowerCase()); - if (defaultTargetPlatform == TargetPlatform.windows) { - // use TCP on Windows - var tcpPort = await getUnusedPort(); - pageUrl = "tcp://localhost:$tcpPort"; - environmentVariables.putIfAbsent("FLET_SERVER_PORT", () => tcpPort.toString()); - } else { - // use UDS on other platforms - pageUrl = "flet_$pid.sock"; - environmentVariables.putIfAbsent("FLET_SERVER_UDS_PATH", () => pageUrl); - } + // In production we use the in-process dart_bridge FFI transport (no UDS, + // no TCP — Python and Flutter share the process). Two bridges: + // _bridge — the Flet MsgPack protocol channel (Dart ↔ Python). + // _exitBridge — Python-only outbound channel carrying the exit code + // when `sys.exit(code)` is called inside the embedded + // interpreter. Replaces the legacy stdout-callback + // socket. + _bridge = PythonBridge(); + _exitBridge = PythonBridge(); + pageUrl = "dartbridge://${_bridge!.port}"; + environmentVariables.putIfAbsent( + "FLET_DART_BRIDGE_PORT", () => _bridge!.port.toString()); + environmentVariables.putIfAbsent( + "FLET_DART_BRIDGE_EXIT_PORT", () => _exitBridge!.port.toString()); } if (!kIsWeb && assetsDir.isNotEmpty) { @@ -226,33 +255,17 @@ Future runPythonApp(List args) async { var completer = Completer(); - ServerSocket outSocketServer; - String socketAddr = ""; - StringBuffer pythonOut = StringBuffer(); - - if (defaultTargetPlatform == TargetPlatform.windows) { - var tcpAddr = "127.0.0.1"; - outSocketServer = await ServerSocket.bind(tcpAddr, 0); - debugPrint( - 'Python output TCP Server is listening on port ${outSocketServer.port}'); - socketAddr = "$tcpAddr:${outSocketServer.port}"; - } else { - socketAddr = "stdout_$pid.sock"; - if (await File(socketAddr).exists()) { - await File(socketAddr).delete(); - } - outSocketServer = await ServerSocket.bind( - InternetAddress(socketAddr, type: InternetAddressType.unix), 0); - debugPrint('Python output Socket Server is listening on $socketAddr'); - } - - environmentVariables.putIfAbsent("FLET_PYTHON_CALLBACK_SOCKET_ADDR", () => socketAddr); - - void closeOutServer() async { - outSocketServer.close(); - - int exitCode = int.tryParse(pythonOut.toString().trim()) ?? 0; - + // Subscribe to the exit-code bridge. Python's `sys.exit(code)` is patched + // (in python.dart) to encode `code` as raw UTF-8 bytes and post them via + // `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`. We don't need + // a streaming codec here — the channel only ever carries a single short + // payload, then Python tears down. + StringBuffer pythonExitBuf = StringBuffer(); + StreamSubscription? exitSub; + + void onExitSignal() async { + await exitSub?.cancel(); + int exitCode = int.tryParse(pythonExitBuf.toString().trim()) ?? 0; if (exitCode == errorExitCode) { var out = ""; if (await File(outLogFilename).exists()) { @@ -264,29 +277,112 @@ Future runPythonApp(List args) async { } } - outSocketServer.listen((client) { - debugPrint( - 'Connection from: ${client.remoteAddress.address}:${client.remotePort}'); - client.listen((data) { - var s = String.fromCharCodes(data); - pythonOut.write(s); - }, onError: (error) { - client.close(); - closeOutServer(); - }, onDone: () { - client.close(); - closeOutServer(); - }); - }); + exitSub = _exitBridge!.messages.listen( + (data) { + pythonExitBuf.write(String.fromCharCodes(data)); + // One frame is always the full code on this channel — act on it. + onExitSignal(); + }, + onError: (error) { + debugPrint('Exit bridge error: $error'); + onExitSignal(); + }, + onDone: onExitSignal, + cancelOnError: false, + ); // run python async SeriousPython.runProgram(path.join(appDir, "$pythonModuleName.pyc"), script: script, environmentVariables: environmentVariables); - // wait for client connection to close + // wait for Python to signal exit return completer.future; } +/// `FletBackendChannel` implementation backed by a [PythonBridge]. Bytes +/// flow Dart↔Python entirely in-process; no Unix socket, no kernel context +/// switch. The wire format is the same MsgPack-framed protocol the existing +/// socket-based `FletSocketBackendChannel` speaks. +class _DartBridgeBackendChannel implements FletBackendChannel { + _DartBridgeBackendChannel(this._bridge, + {required FletBackendChannelOnMessageCallback onMessage, + required FletBackendChannelOnDisconnectCallback onDisconnect}) + : _onMessage = onMessage, + _onDisconnect = onDisconnect, + _deserializer = + StreamingMsgpackDeserializer(extDecoder: FletMsgpackDecoder()); + + final PythonBridge _bridge; + final FletBackendChannelOnMessageCallback _onMessage; + final FletBackendChannelOnDisconnectCallback _onDisconnect; + final StreamingMsgpackDeserializer _deserializer; + StreamSubscription? _subscription; + + @override + Future connect() async { + _subscription = _bridge.messages.listen( + _onBytes, + onError: (error, stack) { + debugPrint("PythonBridge stream error: $error"); + _onDisconnect(); + }, + onDone: () { + debugPrint("PythonBridge stream closed."); + _onDisconnect(); + }, + cancelOnError: false, + ); + } + + void _onBytes(Uint8List bytes) { + _deserializer.addChunk(bytes); + final frames = _deserializer.decodeMessages(); + for (final frame in frames) { + _onMessage(Message.fromList(frame)); + } + } + + @override + void send(Message message) { + final encoded = Uint8List.fromList( + msgpack.serialize(message.toList(), extEncoder: FletMsgpackEncoder())); + // Retry loop covers the brief startup window where Python hasn't yet + // called `dart_bridge.set_enqueue_handler_func` — bridge.send returns + // false in that case. Once Flet's app.py registers the handler (which + // happens before `runpy.run_module` is dispatched), bridge.send returns + // true synchronously. + if (_bridge.send(encoded)) return; + _retrySend(encoded); + } + + void _retrySend(Uint8List encoded) { + const interval = Duration(milliseconds: 50); + const deadline = Duration(seconds: 30); + final start = DateTime.now(); + Timer.periodic(interval, (timer) { + if (_bridge.send(encoded)) { + timer.cancel(); + } else if (DateTime.now().difference(start) > deadline) { + timer.cancel(); + debugPrint( + "PythonBridge send timed out: Python handler never registered."); + } + }); + } + + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 0; + + @override + void disconnect() { + _subscription?.cancel(); + _subscription = null; + } +} + class ErrorScreen extends StatelessWidget { final String title; final String text; @@ -377,10 +473,3 @@ class BlankScreen extends StatelessWidget { } } -Future getUnusedPort() { - return ServerSocket.bind("127.0.0.1", 0).then((socket) { - var port = socket.port; - socket.close(); - return port; - }); -} diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart index 770dff1818..679577c337 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart @@ -1,7 +1,7 @@ const errorExitCode = 100; const pythonScript = """ -import os, runpy, socket, sys, traceback +import os, runpy, sys, traceback # fix for cryptography package os.environ["CRYPTOGRAPHY_OPENSSL_NO_LEGACY"] = "1" @@ -56,29 +56,28 @@ def initialize_ctypes(): initialize_ctypes() out_file = open("{outLogFilename}", "w+", buffering=1) - -callback_socket_addr = os.getenv("FLET_PYTHON_CALLBACK_SOCKET_ADDR") -if ":" in callback_socket_addr: - addr, port = callback_socket_addr.split(":") - callback_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - callback_socket.connect((addr, int(port))) -else: - callback_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - callback_socket.connect(callback_socket_addr) - sys.stdout = sys.stderr = out_file +# Exit-code transport. The Dart side allocated a dedicated PythonBridge port +# (FLET_DART_BRIDGE_EXIT_PORT) and is listening on it. `flet_exit` posts the +# exit code as raw UTF-8 bytes through that bridge — the Dart side parses, +# then either renders the error screen (code == 100) or terminates the host +# process (any other code) using the file we wrote to above. +import dart_bridge # built-in module provided by libdart_bridge +_exit_port = int(os.environ["FLET_DART_BRIDGE_EXIT_PORT"]) + def flet_exit(code=0): - callback_socket.sendall(str(code).encode()) - out_file.close() - callback_socket.close() + try: + dart_bridge.send_bytes(_exit_port, str(code).encode()) + finally: + out_file.close() sys.exit = flet_exit ex = None try: import certifi - + os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() os.environ["SSL_CERT_FILE"] = certifi.where() diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index 243eed7c66..e69ea478f8 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -17,7 +17,18 @@ dependencies: flet: path: ../../../../../packages/flet - serious_python: 1.0.1 + # PythonBridge (in-process dart_bridge FFI transport) ships on the + # serious-python `dart-bridge` branch. Swap back to a version pin + # (`serious_python: ^2.0.0`) once it's published to pub.dev. + serious_python: + git: + url: https://github.com/flet-dev/serious-python.git + ref: dart-bridge + + # MsgPack codec used by the dart_bridge FletBackendChannel implementation + # in lib/main.dart — matches the wire format flet's existing socket + # transport already speaks. + msgpack_dart: ^1.0.1 package_info_plus: ^9.0.0 path_provider: ^2.1.4 From 702bd7d3d780e103d8cd28a28917f7fc4d4eb880 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 11 Jun 2026 13:25:09 -0700 Subject: [PATCH 06/47] Add path for serious-python git dependency Add a `path: src/serious_python` entry to the serious-python git dependency in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This directs the package resolver to the subdirectory within the referenced repo (ref: dart-bridge) so the Dart package is loaded from src/serious_python instead of the repository root. --- sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index e69ea478f8..48b6ab6c6d 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: git: url: https://github.com/flet-dev/serious-python.git ref: dart-bridge + path: src/serious_python # MsgPack codec used by the dart_bridge FletBackendChannel implementation # in lib/main.dart — matches the wire format flet's existing socket From 62799c427e117ca54a35a40d4533727f6a034247 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 11:14:19 -0700 Subject: [PATCH 07/47] Bump 3.13.14 / 3.14.6 / Pyodide 314.0.0; thread date-based python-build vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the serious-python registry bump: * 3.12 row: Astral PBS date 20260610 (CPython 3.12.13 unchanged). * 3.13 row: CPython 3.13.14, PBS date 20260610. * 3.14 row: CPython 3.14.6, PBS date 20260610, Pyodide 314.0.0 GA. * All three rows gain `python_build_date: "20260611"` for the new date-keyed flet-dev/python-build release scheme. The 3.13 wheel platform tag was also wrong — `pyodide-2025.0-wasm32` where it should have been `pyemscripten-2025.0-wasm32` (the prefix transition happened at Pyodide 0.28/0.29, not at 314.0). `flet build web --python-version 3.13` would have failed to match Pyodide-built native wheels. Fixed in the registry and called out in the 0.86.0 changelog. `build_base.py` now exports two new env vars alongside the existing `SERIOUS_PYTHON_VERSION` so the serious-python platform plugin build scripts can construct the new URL form (`…//python-*--*`): * SERIOUS_PYTHON_FULL_VERSION → python_release.standalone * SERIOUS_PYTHON_BUILD_DATE → python_release.python_build_date Both are set in `package_env` (for `serious_python:main package`) and `build_env` (for the subsequent `flutter build`). Breaking-changes docs for 0.86: new 0.86.0 section in the index plus two new guide pages covering (a) the default-Python bump 3.12 → 3.14 with three pin options, and (b) the removal of `flet.version.pyodide_version` / `PYODIDE_VERSION` with the registry-lookup replacement. The dart_bridge default-transport migration guide is intentionally not in this commit; it'll be authored separately. Publish docs tables (`publish/index.md`, `publish/web/static-website`) updated to the new patch + Pyodide versions. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 3 +- .../src/flet_cli/commands/build_base.py | 4 + .../src/flet_cli/utils/python_versions.py | 22 +++-- website/docs/publish/index.md | 4 +- .../docs/publish/web/static-website/index.md | 2 +- .../default-bundled-python-3-14.md | 89 +++++++++++++++++++ .../docs/updates/breaking-changes/index.md | 7 ++ .../removed-pyodide-version-export.md | 78 ++++++++++++++++ 8 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 website/docs/updates/breaking-changes/default-bundled-python-3-14.md create mode 100644 website/docs/updates/breaking-changes/removed-pyodide-version-export.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d2c2fe6d95..d4fedf2a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### New features -* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0a2), and Emscripten wheel platform tag are all resolved from a central registry. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 2.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from a central registry. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 2.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. ### Improvements @@ -18,6 +18,7 @@ ### Bug fixes * Fix `flet build` failing on Windows when a dependency is pulled in via `[tool.flet.].dev_packages` (or any local-path install): the rewritten ` @ file://` URL now uses `Path.as_uri()`, producing the correct `file:///D:/...` three-slash form instead of `file://D:\...`, which pip on Windows parsed as a UNC path and aborted with `OSError: [Errno 2] No such file or directory: '\\\\D:\\a\\...'` ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Fix `flet build web --python-version 3.13` failing to match any Pyodide-built native wheel. The 3.13 row in the Python version registry was set to Pyodide platform tag `pyodide-2025.0-wasm32`, but Pyodide actually publishes 0.29 wheels under `pyemscripten_2025_0_wasm32` (the `pyodide_` → `pyemscripten_` prefix transition happened at 0.28/0.29, not at 314.0). Corrected to `pyemscripten-2025.0-wasm32` so pip's wheel selection picks up the correct tags by @FeodorFitsner. ## 0.85.3 diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index 0851375239..66dd31cc1d 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -1910,6 +1910,8 @@ def package_python_app(self): package_env = { "SERIOUS_PYTHON_VERSION": self.python_release.short, + "SERIOUS_PYTHON_FULL_VERSION": self.python_release.standalone, + "SERIOUS_PYTHON_BUILD_DATE": self.python_release.python_build_date, } # requirements @@ -2199,6 +2201,8 @@ def _run_flutter_command(self): build_env = { "SERIOUS_PYTHON_VERSION": self.python_release.short, + "SERIOUS_PYTHON_FULL_VERSION": self.python_release.standalone, + "SERIOUS_PYTHON_BUILD_DATE": self.python_release.python_build_date, } # site-packages variable diff --git a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py index 729903a466..8f3065d56e 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py @@ -18,6 +18,10 @@ class PythonRelease: short: str standalone: str standalone_date: str + # Release date tag of the matching `flet-dev/python-build` release + # (e.g. "20260611"). Combined with `standalone` to construct the + # platform-plugin download URLs. + python_build_date: str pyodide: str pyodide_platform_tag: str # When True, this release is supported via `--python-version` (and an @@ -31,24 +35,27 @@ class PythonRelease: PythonRelease( short="3.12", standalone="3.12.13", - standalone_date="20260602", + standalone_date="20260610", + python_build_date="20260611", pyodide="0.27.7", pyodide_platform_tag="pyodide-2024.0-wasm32", prerelease=False, ), PythonRelease( short="3.13", - standalone="3.13.13", - standalone_date="20260602", + standalone="3.13.14", + standalone_date="20260610", + python_build_date="20260611", pyodide="0.29.4", - pyodide_platform_tag="pyodide-2025.0-wasm32", + pyodide_platform_tag="pyemscripten-2025.0-wasm32", prerelease=False, ), PythonRelease( short="3.14", - standalone="3.14.5", - standalone_date="20260602", - pyodide="314.0.0a2", + standalone="3.14.6", + standalone_date="20260610", + python_build_date="20260611", + pyodide="314.0.0", pyodide_platform_tag="pyemscripten-2026.0-wasm32", prerelease=False, ), @@ -60,6 +67,7 @@ class PythonRelease: # short="3.15", # standalone="3.15.0", # standalone_date="...", + # python_build_date="...", # pyodide="...", # pyodide_platform_tag="...", # prerelease=True, diff --git a/website/docs/publish/index.md b/website/docs/publish/index.md index ec4fa205de..5d8cf6e3ac 100644 --- a/website/docs/publish/index.md +++ b/website/docs/publish/index.md @@ -130,8 +130,8 @@ Supported versions and the matching CPython / Pyodide artifacts: | Short | CPython runtime | Pyodide (web) | Status | | ----- | --------------- | ------------- | -------- | -| 3.14 | 3.14.5 | 314.0.0a2 | default | -| 3.13 | 3.13.13 | 0.29.4 | stable | +| 3.14 | 3.14.6 | 314.0.0 | default | +| 3.13 | 3.13.14 | 0.29.4 | stable | | 3.12 | 3.12.13 | 0.27.7 | stable | The version is resolved in this order: diff --git a/website/docs/publish/web/static-website/index.md b/website/docs/publish/web/static-website/index.md index 45600ac618..a8f7a61dcd 100644 --- a/website/docs/publish/web/static-website/index.md +++ b/website/docs/publish/web/static-website/index.md @@ -50,7 +50,7 @@ full matrix and resolution rules. In short: | Python | Pyodide | | ------ | --------- | -| 3.14 | 314.0.0a2 | +| 3.14 | 314.0.0 | | 3.13 | 0.29.4 | | 3.12 | 0.27.7 | diff --git a/website/docs/updates/breaking-changes/default-bundled-python-3-14.md b/website/docs/updates/breaking-changes/default-bundled-python-3-14.md new file mode 100644 index 0000000000..c9a8d8cff3 --- /dev/null +++ b/website/docs/updates/breaking-changes/default-bundled-python-3-14.md @@ -0,0 +1,89 @@ +--- +title: "Default bundled Python version is now 3.14" +--- + +# Default bundled Python version is now 3.14 + +:::note +This guide is accurate as of Flet 0.86.0. Later releases might add new APIs or +additional migration paths. + +The [breaking changes and deprecations index](.) lists the guides created for each release. +::: + +## Summary + +Flet 0.86.0 introduces multi-version bundled CPython support to `flet build` +and `flet publish`. The bundled Python interpreter is now selected per build +(see [Choosing a Python version](/docs/publish#choosing-a-python-version)) and +the **default is the latest supported stable** — currently CPython 3.14. +Earlier Flet releases implicitly bundled CPython 3.12 via the single-version +`serious_python` 1.x. + +Apps that depend on Python packages whose pre-built wheels aren't yet +available for 3.14 (typically packages with C/Rust extensions that haven't +caught up to the new ABI) need to pin a previous Python version. + +## Background + +Multi-version Python support landed in +[#6577](https://github.com/flet-dev/flet/pull/6577) and is tracked by the +central registry in +[`flet_cli/utils/python_versions.py`](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py). +The supported short versions are `3.12`, `3.13`, and `3.14` (each pinned to a +specific CPython patch + Pyodide release on the registry side). + +The default flips to the latest stable so new projects get the most recent +Python by default. Existing projects without a pin start picking it up too on +their next build. + +## Migration guide + +You can pin a different Python version in three equivalent ways. Use whichever +fits your project layout. + +### Pin in `pyproject.toml` (recommended) + +Add or update `[project].requires-python`: + +```toml +[project] +requires-python = ">=3.12,<3.13" +``` + +`flet build` parses the specifier and picks the **highest supported short +version** that satisfies it. So `>=3.12,<3.13` resolves to 3.12, +`>=3.13,<3.14` resolves to 3.13, and `>=3.14` resolves to 3.14 (the default). + +### Pin via the CLI flag + +Pass `--python-version` on every invocation: + +```bash +flet build apk --python-version 3.12 +flet publish --python-version 3.12 +``` + +The CLI flag overrides anything in `pyproject.toml`. + +### Pin via environment variable + +Export `SERIOUS_PYTHON_VERSION` in the shell that runs `flet build`: + +```bash +export SERIOUS_PYTHON_VERSION=3.12 +flet build apk +``` + +Useful in CI pipelines where you don't want to thread the flag through every +job. + +## Timeline + +- Changed in: `0.86.0` + +## References + +- API documentation: [Choosing a Python version](/docs/publish#choosing-a-python-version) +- Issues and PRs: [#6577](https://github.com/flet-dev/flet/pull/6577) +- Release notes: [Flet 0.86.0](../release-notes.md) diff --git a/website/docs/updates/breaking-changes/index.md b/website/docs/updates/breaking-changes/index.md index bdb3f1ecf6..6ffd0069dc 100644 --- a/website/docs/updates/breaking-changes/index.md +++ b/website/docs/updates/breaking-changes/index.md @@ -22,6 +22,13 @@ This page lists the guides created for each release. The following guides are available. They're sorted by release, with the most recent release first. Each guide explains the change, the reason for it, and how to migrate your code. +### Released in Flet 0.86.0 + +#### Breaking changes + +- [Default bundled Python version is now 3.14](/docs/updates/breaking-changes/default-bundled-python-3-14) +- [`flet.version.pyodide_version` and `PYODIDE_VERSION` removed](/docs/updates/breaking-changes/removed-pyodide-version-export) + ### Released in Flet 0.85.0 #### Breaking changes diff --git a/website/docs/updates/breaking-changes/removed-pyodide-version-export.md b/website/docs/updates/breaking-changes/removed-pyodide-version-export.md new file mode 100644 index 0000000000..ba65634155 --- /dev/null +++ b/website/docs/updates/breaking-changes/removed-pyodide-version-export.md @@ -0,0 +1,78 @@ +--- +title: "flet.version.pyodide_version and PYODIDE_VERSION removed" +--- + +# `flet.version.pyodide_version` and `PYODIDE_VERSION` removed + +:::note +This guide is accurate as of Flet 0.86.0. Later releases might add new APIs or +additional migration paths. + +The [breaking changes and deprecations index](.) lists the guides created for each release. +::: + +## Summary + +Flet 0.86.0 removed the module-level `flet.version.pyodide_version` attribute +and the matching `PYODIDE_VERSION` constant. The single fixed-string export no +longer makes sense now that Pyodide is selected **per build** as part of the +multi-version bundled Python support — the bundled release depends on which +Python version your app pins, not a global default. + +## Background + +Earlier Flet releases used a single Pyodide pin in `flet.version` to drive the +`flet --version` output and let tooling read which Pyodide release `flet build +web` would bundle. With +[multi-version Python support](/docs/publish#choosing-a-python-version) the +mapping is now Python-version-aware (`3.12 → 0.27.7`, `3.13 → 0.29.4`, +`3.14 → 314.0.0`) and lives in +[`flet_cli/utils/python_versions.py`](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py). + +`flet --version` now lists every supported Python version with its matching +Pyodide release, newest first. + +## Migration guide + +If you imported the constant somewhere — typically from `flet.version` — +switch to looking the release up from the new registry. + +Code before migration: + +```python +import flet.version + +print(f"Bundled Pyodide: {flet.version.pyodide_version}") +``` + +Code after migration: + +```python +from flet_cli.utils.python_versions import SUPPORTED_PYTHON_VERSIONS + +for release in SUPPORTED_PYTHON_VERSIONS: + print(f" {release.short} → Pyodide {release.pyodide}") +``` + +If you only need the version that `flet build` is about to bundle, resolve +the Python release the same way the CLI does: + +```python +from flet_cli.utils.python_versions import ( + DEFAULT_PYTHON_VERSION, + get_release, +) + +release = get_release(DEFAULT_PYTHON_VERSION) +print(f"Default Pyodide: {release.pyodide}") +``` + +## Timeline + +- Removed in: `0.86.0` + +## References + +- API documentation: [Choosing a Python version](/docs/publish#choosing-a-python-version) +- Issues and PRs: [#6577](https://github.com/flet-dev/flet/pull/6577) +- Release notes: [Flet 0.86.0](../release-notes.md) From 6ed8029690237ffadc1e43b6954c7563e19ff11d Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:38:37 -0700 Subject: [PATCH 08/47] feat(transport): DataChannel API for high-throughput widget byte streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds dedicated byte channels (`ft.DataChannel`) that let widgets exchange bulk binary data (image frames, audio buffers, ML tensors) with their Python counterpart without going through the MsgPack control protocol. Architecture: * `package:flet` exposes abstract `DataChannel` + `DataChannelFactory`. Embedders inject a fast-path factory; absent that, a built-in `ProtocolMuxedDataChannelFactory` muxes channel bytes over the active Flet protocol transport. * Python side: `ft.DataChannel` ABC with `_DartBridgeDataChannel` (embedded native, dedicated PythonBridge) and `_ProtocolMuxedDataChannel` (muxed fallback) impls. `Control.get_data_channel(id)` resolves a channel allocated on the Dart side. * Handshake: control-level event `data_channel_open` carrying `{channel_name, channel_id}` — push-driven, no polling, no race. Wire format change (breaking): * All transports now prefix every packet with a 1-byte type discriminator: `0x00` = MsgPack-encoded Flet protocol frame, `0x01` = raw DataChannel frame (`[channel_id:u32 LE][bytes]`). * Stream-oriented transports (UDS/TCP) gain a 4-byte little-endian length prefix; message-oriented transports (WebSocket, postMessage, dart_bridge) keep native message boundaries. * `StreamingMsgpackDeserializer` removed — every inbound packet is one complete MsgPack value, decoded via one-shot `msgpack.deserialize`. Same simplification on the Python side: `Unpacker.feed` loops → `msgpack.unpackb(payload)`. Updated all four Connection subclasses (`FletSocketServer`, `FletDartBridgeServer`, `flet_web.fastapi.FletApp`, `PyodideConnection`) and all five Dart transports (socket, WebSocket, JavaScript/postMessage, mock, JS stub) to the new framing. Pyodide outbound uses Transferable ArrayBuffer for zero-copy across the worker boundary. Three smoke tests in `packages/flet/test/transport/data_channel_test.dart` cover factory allocation, inbound routing by channel id, and the outbound muxed packet shape. --- client/pubspec.lock | 2 +- packages/flet/lib/flet.dart | 2 +- packages/flet/lib/src/flet_app.dart | 12 +- packages/flet/lib/src/flet_backend.dart | 100 +++++- .../flet/lib/src/transport/data_channel.dart | 55 ++++ .../src/transport/flet_backend_channel.dart | 35 ++- .../flet_backend_channel_javascript_io.dart | 9 +- .../flet_backend_channel_javascript_web.dart | 38 +-- .../transport/flet_backend_channel_mock.dart | 23 +- .../flet_backend_channel_socket.dart | 74 +++-- .../flet_backend_channel_web_socket.dart | 27 +- .../protocol_muxed_data_channel.dart | 77 +++++ .../streaming_msgpack_deserializer.dart | 291 ------------------ .../test/transport/data_channel_test.dart | 128 ++++++++ .../flet-web/src/flet_web/fastapi/flet_app.py | 58 +++- sdk/python/packages/flet/src/flet/__init__.py | 3 + .../flet/src/flet/controls/base_control.py | 20 ++ .../packages/flet/src/flet/data_channel.py | 153 +++++++++ .../flet/src/flet/messaging/connection.py | 36 +++ .../flet/messaging/flet_dart_bridge_server.py | 59 +++- .../src/flet/messaging/flet_socket_server.py | 85 ++++- .../src/flet/messaging/pyodide_connection.py | 60 +++- 22 files changed, 935 insertions(+), 412 deletions(-) create mode 100644 packages/flet/lib/src/transport/data_channel.dart create mode 100644 packages/flet/lib/src/transport/protocol_muxed_data_channel.dart delete mode 100644 packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart create mode 100644 packages/flet/test/transport/data_channel_test.dart create mode 100644 sdk/python/packages/flet/src/flet/data_channel.py diff --git a/client/pubspec.lock b/client/pubspec.lock index e095af5265..8028a32cb3 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -359,7 +359,7 @@ packages: path: "../packages/flet" relative: true source: path - version: "0.85.3" + version: "0.86.0" flet_ads: dependency: "direct main" description: diff --git a/packages/flet/lib/flet.dart b/packages/flet/lib/flet.dart index 4e01c2776d..da46bb70ce 100644 --- a/packages/flet/lib/flet.dart +++ b/packages/flet/lib/flet.dart @@ -17,10 +17,10 @@ export 'src/models/asset_source.dart'; export 'src/models/control.dart'; export 'src/models/page_size_view_model.dart'; export 'src/protocol/message.dart'; +export 'src/transport/data_channel.dart'; export 'src/transport/flet_backend_channel.dart'; export 'src/transport/flet_msgpack_decoder.dart'; export 'src/transport/flet_msgpack_encoder.dart'; -export 'src/transport/streaming_msgpack_deserializer.dart'; export 'src/routing/deep_linking_bootstrap.dart'; export 'src/testing/test_finder.dart'; export 'src/testing/tester.dart'; diff --git a/packages/flet/lib/src/flet_app.dart b/packages/flet/lib/src/flet_app.dart index 390ebcc8e9..1eb088187f 100644 --- a/packages/flet/lib/src/flet_app.dart +++ b/packages/flet/lib/src/flet_app.dart @@ -7,6 +7,7 @@ import 'flet_backend.dart'; import 'flet_extension.dart'; import 'models/control.dart'; import 'testing/tester.dart'; +import 'transport/data_channel.dart'; import 'transport/flet_backend_channel.dart'; /// FletApp - The top-level widget that initializes everything @@ -33,6 +34,13 @@ class FletApp extends StatefulWidget { /// factory; [pageUrl] is then irrelevant for transport selection. final FletBackendChannelBuilder? channelBuilder; + /// Optional factory for high-throughput byte channels (see [DataChannel]). + /// Embedders that ship an in-process Python runtime can inject a + /// `PythonBridge`-backed factory here; when `null`, `FletBackend` falls + /// back to a built-in factory that muxes raw bytes over the regular Flet + /// protocol channel. + final DataChannelFactory? dataChannelFactory; + const FletApp( {super.key, required this.pageUrl, @@ -50,7 +58,8 @@ class FletApp extends StatefulWidget { this.forcePyodide, this.tester, this.multiView = false, - this.channelBuilder}); + this.channelBuilder, + this.dataChannelFactory}); @override State createState() => _FletAppState(); @@ -85,6 +94,7 @@ class _FletAppState extends State { tester: widget.tester, multiView: widget.multiView, channelBuilder: widget.channelBuilder, + dataChannelFactory: widget.dataChannelFactory, parentFletBackend: Provider.of(context, listen: false)); }, diff --git a/packages/flet/lib/src/flet_backend.dart b/packages/flet/lib/src/flet_backend.dart index 641d979a8c..3a140314df 100644 --- a/packages/flet/lib/src/flet_backend.dart +++ b/packages/flet/lib/src/flet_backend.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import 'package:provider/provider.dart'; import 'flet_app_errors_handler.dart'; @@ -24,7 +25,11 @@ import 'protocol/register_client_response_body.dart'; import 'protocol/session_crashed_body.dart'; import 'protocol/update_control_body.dart'; import 'testing/tester.dart'; +import 'transport/data_channel.dart'; import 'transport/flet_backend_channel.dart'; +import 'transport/flet_msgpack_decoder.dart'; +import 'transport/flet_msgpack_encoder.dart'; +import 'transport/protocol_muxed_data_channel.dart'; import 'utils/desktop.dart'; import 'utils/images.dart'; import 'utils/numbers.dart'; @@ -63,6 +68,13 @@ class FletBackend extends ChangeNotifier { int _reconnectDelayMs = 0; FletBackendChannel? _backendChannel; final FletBackendChannelBuilder? _channelBuilder; + late final DataChannelFactory _dataChannelFactory; + final DataChannelFactory? _injectedDataChannelFactory; + // Inbound mux registry for ProtocolMuxedDataChannel — type-byte 0x01 + // frames are routed by channel_id to the matching channel's deliver hook. + // PythonBridge-backed DataChannels do NOT live in this registry (their + // bytes arrive on their own native port, never on the Flet transport). + final Map _dataChannels = {}; final List _sendQueue = []; String route = ""; bool isLoading = true; @@ -106,12 +118,16 @@ class FletBackend extends ChangeNotifier { this.tester, required extensions, FletBackendChannelBuilder? channelBuilder, + DataChannelFactory? dataChannelFactory, FletBackend? parentFletBackend}) : _parentFletBackend = parentFletBackend != null ? WeakReference(parentFletBackend) : null, _reconnectTimeoutMs = reconnectTimeoutMs, _reconnectIntervalMs = reconnectIntervalMs, - _channelBuilder = channelBuilder { + _channelBuilder = channelBuilder, + _injectedDataChannelFactory = dataChannelFactory { + _dataChannelFactory = + _injectedDataChannelFactory ?? ProtocolMuxedDataChannelFactory(this); // add Flet extension with core controls and services this.extensions = [...extensions, FletCoreExtension()]; @@ -186,14 +202,14 @@ class FletBackend extends ChangeNotifier { // bridge). The builder is responsible for the entire transport // lifecycle; we just wire its callbacks to ours. _backendChannel = builder( - onDisconnect: _onDisconnect, onMessage: _onMessage); + onDisconnect: _onDisconnect, onPacket: _onPacket); } else { _backendChannel = FletBackendChannel( address: pageUri.toString(), args: args ?? {}, forcePyodide: forcePyodide == true, onDisconnect: _onDisconnect, - onMessage: _onMessage); + onPacket: _onPacket); } await _backendChannel!.connect(); _registerClient(); @@ -204,6 +220,40 @@ class FletBackend extends ChangeNotifier { } } + /// Opens a dedicated [DataChannel] for high-throughput byte traffic from a + /// widget. In embedded mode this is backed by a fresh `PythonBridge`; in + /// dev / web modes it is a logical channel multiplexed over the active + /// [FletBackendChannel] (see [ProtocolMuxedDataChannelFactory]). + /// + /// Must be called from the main Isolate (it doesn't escape there, but + /// the returned channel is main-Isolate-bound either way). + DataChannel openDataChannel() => _dataChannelFactory.open(); + + // --------------------------------------------------------------------- + // Mux registry — used by ProtocolMuxedDataChannel only. + // --------------------------------------------------------------------- + + /// Registers a muxed data channel so inbound 0x01 frames for [id] are + /// routed to it. Called from [ProtocolMuxedDataChannel.]. + void registerDataChannel(int id, ProtocolMuxedDataChannel channel) { + assert(!_dataChannels.containsKey(id), "duplicate data channel id $id"); + _dataChannels[id] = channel; + } + + /// Removes [id] from the routing table. Called from + /// [ProtocolMuxedDataChannel.close]. Idempotent — frames for an + /// unregistered id are silently dropped. + void unregisterDataChannel(int id) { + _dataChannels.remove(id); + } + + /// Sends a fully-formed packet on the active transport. Used by + /// [ProtocolMuxedDataChannel] to ship `[0x01][channel_id:u32 LE][bytes]` + /// alongside regular protocol traffic. + void sendRawPacket(Uint8List packet) { + _backendChannel?.send(packet); + } + _registerClient() { debugPrint("Registering web client: $page"); _send( @@ -428,9 +478,42 @@ class FletBackend extends ChangeNotifier { return getAssetSrc(src, pageUri, assetsDir); } - _onMessage(Message message) { + /// Inbound transport dispatcher. Every packet starts with a 1-byte type + /// discriminator: + /// 0x00 → MsgPack-encoded Flet control frame (the existing protocol). + /// 0x01 → raw DataChannel frame `[channel_id:u32 LE][payload]`. + void _onPacket(Uint8List packet) { + if (packet.isEmpty) { + debugPrint("Dropping empty packet"); + return; + } + final type = packet[0]; + if (type == 0x00) { + // Decode the MsgPack body and dispatch as a Flet protocol message. + final body = msgpack.deserialize( + Uint8List.sublistView(packet, 1), + extDecoder: FletMsgpackDecoder()); + _onMessage(Message.fromList(body)); + } else if (type == 0x01) { + if (packet.length < 5) { + debugPrint("Dropping malformed data channel frame (len=${packet.length})"); + return; + } + final channelId = + ByteData.sublistView(packet, 1, 5).getUint32(0, Endian.little); + final channel = _dataChannels[channelId]; + if (channel == null) { + // Stale frame after channel.close() — silently drop. + return; + } + channel.deliver(Uint8List.sublistView(packet, 5)); + } else { + debugPrint("Dropping packet with unknown type byte 0x${type.toRadixString(16)}"); + } + } + + void _onMessage(Message message) { debugPrint("Received message: ${message.toList()}"); - //debugPrint("message.payload: ${message.payload}"); switch (message.action) { case MessageAction.registerClient: _onClientRegistered( @@ -580,7 +663,12 @@ class FletBackend extends ChangeNotifier { _send(Message message, {bool unbuffered = false}) { if (unbuffered || !isLoading) { debugPrint("_send: ${message.action} ${message.payload}"); - _backendChannel?.send(message); + final encoded = msgpack.serialize(message.toList(), + extEncoder: FletMsgpackEncoder()); + final packet = Uint8List(1 + encoded.length); + packet[0] = 0x00; + packet.setRange(1, packet.length, encoded); + _backendChannel?.send(packet); } else { _sendQueue.add(message); } diff --git a/packages/flet/lib/src/transport/data_channel.dart b/packages/flet/lib/src/transport/data_channel.dart new file mode 100644 index 0000000000..f0d39875a0 --- /dev/null +++ b/packages/flet/lib/src/transport/data_channel.dart @@ -0,0 +1,55 @@ +import 'dart:typed_data'; + +/// One bidirectional byte channel between Dart and Python, dedicated to a +/// single widget's bulk-data traffic. +/// +/// The Dart side of an extension widget opens one via +/// [FletBackend.openDataChannel] in `initState`, then announces it to Python +/// by firing a `data_channel_open` control event carrying +/// `{channel_name, channel_id: id}` — Python's handler retrieves the +/// matching `DataChannel` via `Control.get_data_channel(channel_id)`. +/// +/// Bytes flow over a transport chosen by the active [DataChannelFactory]: +/// a dedicated `PythonBridge` per channel in embedded native mode, or a +/// raw-byte frame muxed over the regular Flet protocol transport in dev / +/// web modes (see `ProtocolMuxedDataChannelFactory`). +/// +/// **Isolate scope.** [FletBackend.openDataChannel] runs on the main Isolate +/// (it goes through `FletBackend.of(BuildContext)`). The returned channel — +/// and, in embedded mode, the backing `PythonBridge` — therefore lives on +/// the main Isolate. For per-Isolate bridges in worker Isolates, construct +/// `PythonBridge` directly from `package:serious_python` and send the port +/// back to the main Isolate via `SendPort` for the `data_channel_open` +/// fire. +abstract class DataChannel { + /// Stable identifier carried in the `data_channel_open` event payload so + /// Python can attach to the same channel. Implementation-specific: for + /// the `PythonBridge`-backed factory this is the Dart native port number, + /// for the muxed fallback it is a monotonic u32 minted by the factory. + int get id; + + /// Bytes pushed from Python via this channel. Hot path — consumers should + /// avoid synchronous heavy work in the listener and instead enqueue to a + /// worker. + Stream get messages; + + /// Send bytes Dart → Python. Returns `false` only during the brief startup + /// window before the Python side has attached (embedded mode); widget + /// code should treat this as transient and retry / queue accordingly. + bool send(Uint8List bytes); + + /// Releases the channel. Must be called from the widget's `dispose()`. + /// Idempotent. + void close(); +} + +/// Factory injected by the embedder. The `flet build` template injects a +/// `PythonBridge`-backed factory for native mode; web / dev / Pyodide +/// deployments leave this `null` and `FletBackend` falls back to the +/// built-in `ProtocolMuxedDataChannelFactory` that rides the existing +/// Flet protocol transport. +abstract class DataChannelFactory { + /// Opens a fresh data channel. Each call mints a new id; a control may + /// open as many channels as it needs. + DataChannel open(); +} diff --git a/packages/flet/lib/src/transport/flet_backend_channel.dart b/packages/flet/lib/src/transport/flet_backend_channel.dart index 253b31dc55..7b99e1ce02 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel.dart @@ -1,4 +1,5 @@ -import '../protocol/message.dart'; +import 'dart:typed_data'; + import '../utils/platform_utils_web.dart' if (dart.library.io) "../utils/platform_utils_non_web.dart"; import 'flet_backend_channel_javascript_web.dart' @@ -8,7 +9,18 @@ import 'flet_backend_channel_socket.dart'; import 'flet_backend_channel_web_socket.dart'; typedef FletBackendChannelOnDisconnectCallback = void Function(); -typedef FletBackendChannelOnMessageCallback = void Function(Message message); + +/// Called when the transport receives one complete packet from the peer. +/// The packet is the **full** byte sequence including the 1-byte type +/// discriminator at offset 0: +/// +/// `[type:u8][payload]` +/// +/// where `type == 0x00` is a MsgPack-encoded Flet protocol frame and +/// `type == 0x01` is a raw DataChannel frame (`[channel_id:u32 LE][bytes]`). +/// Transports are responsible only for delivering packet boundaries; the +/// type byte is interpreted by [FletBackend]. +typedef FletBackendChannelOnPacketCallback = void Function(Uint8List packet); /// Builds a custom [FletBackendChannel] supplied by the embedder. /// @@ -18,7 +30,7 @@ typedef FletBackendChannelOnMessageCallback = void Function(Message message); /// that needs more setup than a `String address` URL can express, without /// forcing the `flet` package to take a Python-related dependency. typedef FletBackendChannelBuilder = FletBackendChannel Function({ - required FletBackendChannelOnMessageCallback onMessage, + required FletBackendChannelOnPacketCallback onPacket, required FletBackendChannelOnDisconnectCallback onDisconnect, }); @@ -28,33 +40,38 @@ abstract class FletBackendChannel { required Map args, required bool forcePyodide, required FletBackendChannelOnDisconnectCallback onDisconnect, - required FletBackendChannelOnMessageCallback onMessage}) { + required FletBackendChannelOnPacketCallback onPacket}) { if (isPyodideMode() || forcePyodide) { // Pyodide/JavaScript return FletJavaScriptBackendChannel( address: address, args: args, onDisconnect: onDisconnect, - onMessage: onMessage); + onPacket: onPacket); } else if (address.startsWith("http://") || address.startsWith("https://")) { // WebSocket return FletWebSocketBackendChannel( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); + address: address, onDisconnect: onDisconnect, onPacket: onPacket); } else if (address == "mock") { // Mock return FletMockBackendChannel( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); + address: address, onDisconnect: onDisconnect, onPacket: onPacket); } else { // TCP or UDS return FletSocketBackendChannel( - address: address, onDisconnect: onDisconnect, onMessage: onMessage); + address: address, onDisconnect: onDisconnect, onPacket: onPacket); } } Future connect(); bool get isLocalConnection; int get defaultReconnectIntervalMs; - void send(Message message); + + /// Sends one full packet — `[type:u8][payload]` — to the peer. The transport + /// is responsible for delimiting packet boundaries (length prefix on + /// stream-oriented transports; native message boundary on others). + void send(Uint8List packet); + void disconnect(); } diff --git a/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart b/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart index 65149e543f..1ed8c86b64 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_javascript_io.dart @@ -1,17 +1,18 @@ -import '../protocol/message.dart'; +import 'dart:typed_data'; + import 'flet_backend_channel.dart'; class FletJavaScriptBackendChannel implements FletBackendChannel { final String address; final Map args; - final FletBackendChannelOnMessageCallback onMessage; + final FletBackendChannelOnPacketCallback onPacket; final FletBackendChannelOnDisconnectCallback onDisconnect; FletJavaScriptBackendChannel( {required this.address, required this.args, required this.onDisconnect, - required this.onMessage}); + required this.onPacket}); @override connect() async {} @@ -23,7 +24,7 @@ class FletJavaScriptBackendChannel implements FletBackendChannel { int get defaultReconnectIntervalMs => 10; @override - void send(Message data) {} + void send(Uint8List packet) {} @override void disconnect() {} diff --git a/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart b/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart index 46a6933223..7b76dc8765 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_javascript_web.dart @@ -1,37 +1,35 @@ import 'dart:js_interop'; import 'package:flutter/foundation.dart'; -import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; -import '../protocol/message.dart'; import 'flet_backend_channel.dart'; -import 'flet_msgpack_decoder.dart'; -import 'flet_msgpack_encoder.dart'; @JS() external JSPromise jsConnect( String appId, JSAny args, JSExportedDartFunction onMessage); +/// The optional `transferList` argument names ArrayBuffers (in `data`'s +/// underlying buffer) whose ownership should transfer to the receiver — +/// `postMessage` then avoids the structured-clone copy. We pass the +/// packet's `.buffer` here so bulk DataChannel frames are zero-copy across +/// the Worker boundary. @JS() -external void jsSend(String appId, JSUint8Array data); +external void jsSend(String appId, JSUint8Array data, JSArray? transferList); @JS() external void jsDisconnect(String appId); -typedef FletBackendJavascriptChannelOnMessageCallback = void Function( - List message); - class FletJavaScriptBackendChannel implements FletBackendChannel { final String address; final Map args; - final FletBackendChannelOnMessageCallback onMessage; + final FletBackendChannelOnPacketCallback onPacket; final FletBackendChannelOnDisconnectCallback onDisconnect; FletJavaScriptBackendChannel( {required this.address, required this.args, required this.onDisconnect, - required this.onMessage}); + required this.onPacket}); @override connect() async { @@ -40,8 +38,9 @@ class FletJavaScriptBackendChannel implements FletBackendChannel { } void _onMessage(JSUint8Array data) { - onMessage(Message.fromList( - msgpack.deserialize(data.toDart, extDecoder: FletMsgpackDecoder()))); + // Each postMessage event is one packet — message boundaries are + // preserved by the underlying MessageChannel. + onPacket(data.toDart); } @override @@ -51,12 +50,15 @@ class FletJavaScriptBackendChannel implements FletBackendChannel { int get defaultReconnectIntervalMs => 10000; @override - void send(Message message) { - jsSend( - address, - msgpack - .serialize(message.toList(), extEncoder: FletMsgpackEncoder()) - .toJS); + void send(Uint8List packet) { + final jsBytes = packet.toJS; + // Transfer the underlying ArrayBuffer to the receiver — zero copy + // across the Worker boundary. Safe because the sender does not access + // `packet` after this call (FletBackend always builds a fresh buffer + // per send). + final jsBuffer = packet.buffer.toJS; + final transferList = [jsBuffer as JSObject].toJS; + jsSend(address, jsBytes, transferList); } @override diff --git a/packages/flet/lib/src/transport/flet_backend_channel_mock.dart b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart index f583f23819..a0b53bde40 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_mock.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_mock.dart @@ -1,18 +1,32 @@ import 'dart:math'; import 'package:flutter/foundation.dart'; +import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import '../protocol/message.dart'; import 'flet_backend_channel.dart'; +import 'flet_msgpack_encoder.dart'; class FletMockBackendChannel implements FletBackendChannel { - FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnPacketCallback onPacket; FletBackendChannelOnDisconnectCallback onDisconnect; FletMockBackendChannel( {required String address, required this.onDisconnect, - required this.onMessage}); + required this.onPacket}); + + /// Wrap test-scenario `Message` payloads in the on-wire packet shape + /// `[0x00][msgpack(message.toList())]` so the inbound dispatcher sees + /// them through the same code path as real transports. + void onMessage(Message message) { + final encoded = msgpack.serialize(message.toList(), + extEncoder: FletMsgpackEncoder()); + final packet = Uint8List(1 + encoded.length); + packet[0] = 0x00; + packet.setRange(1, packet.length, encoded); + onPacket(packet); + } @override bool get isLocalConnection => true; @@ -296,8 +310,9 @@ class FletMockBackendChannel implements FletBackendChannel { } @override - void send(Message message) { - debugPrint("Send message: ${message.toList()}"); + void send(Uint8List packet) { + debugPrint("Send packet: type=${packet.isNotEmpty ? packet[0] : -1} " + "len=${packet.length}"); } @override diff --git a/packages/flet/lib/src/transport/flet_backend_channel_socket.dart b/packages/flet/lib/src/transport/flet_backend_channel_socket.dart index a1fcb79a01..3bcd5af2ca 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_socket.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_socket.dart @@ -1,36 +1,36 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; -import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; -import '../protocol/message.dart'; import '../utils/networking.dart'; import 'flet_backend_channel.dart'; -import 'flet_msgpack_decoder.dart'; -import 'flet_msgpack_encoder.dart'; -import 'streaming_msgpack_deserializer.dart'; const int defaultLocalReconnectInterval = 200; const int defaultPublicReconnectInterval = 500; +/// TCP / Unix-domain-socket transport. +/// +/// Wire format: every packet on the wire is prefixed with its length as +/// a 4-byte little-endian unsigned integer. The packet itself starts with +/// the 1-byte type discriminator interpreted by [FletBackend]; we just +/// deliver the payload bytes 1:1. class FletSocketBackendChannel implements FletBackendChannel { String address; - FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnPacketCallback onPacket; FletBackendChannelOnDisconnectCallback onDisconnect; Socket? _socket; late final bool _isLocalConnection; late final int _defaultReconnectIntervalMs; - // Create an instance of the StreamingDeserializer. - // This object buffers incoming chunks and decodes complete MessagePack objects. - final StreamingMsgpackDeserializer _streamingDeserializer; + // Inbound framing state: accumulate bytes, parse length-prefixed packets. + final BytesBuilder _inboundBuffer = BytesBuilder(copy: false); FletSocketBackendChannel({ required this.address, required this.onDisconnect, - required this.onMessage, - }) : _streamingDeserializer = - StreamingMsgpackDeserializer(extDecoder: FletMsgpackDecoder()); + required this.onPacket, + }); @override connect() async { @@ -53,19 +53,8 @@ class FletSocketBackendChannel implements FletBackendChannel { debugPrint('Connected to: $udsPath'); } - // Listen for incoming data. _socket!.listen( - (Uint8List data) { - debugPrint("Received packet: ${data.length}"); - // Feed the incoming chunk into the streaming deserializer. - _streamingDeserializer.addChunk(data); - // Try to decode complete MessagePack messages from buffered data. - var messages = _streamingDeserializer.decodeMessages(); - for (var message in messages) { - //debugPrint('Decoded message: ${message.toString()}'); - _onMessage(message); - } - }, + _onBytes, onError: (error) { debugPrint("Error: $error"); _socket?.destroy(); @@ -79,22 +68,41 @@ class FletSocketBackendChannel implements FletBackendChannel { ); } + void _onBytes(Uint8List chunk) { + _inboundBuffer.add(chunk); + // Parse as many complete packets as the buffer currently holds. + while (true) { + final buffered = _inboundBuffer.length; + if (buffered < 4) return; + final bytes = _inboundBuffer.toBytes(); + final len = ByteData.sublistView(bytes, 0, 4).getUint32(0, Endian.little); + if (bytes.length < 4 + len) { + // Reset builder to the partial-packet remainder so we accumulate + // the rest on the next read. + _inboundBuffer.clear(); + _inboundBuffer.add(bytes); + return; + } + final packet = Uint8List.sublistView(bytes, 4, 4 + len); + onPacket(packet); + _inboundBuffer.clear(); + if (bytes.length > 4 + len) { + _inboundBuffer.add(Uint8List.sublistView(bytes, 4 + len)); + } + } + } + @override bool get isLocalConnection => _isLocalConnection; @override int get defaultReconnectIntervalMs => _defaultReconnectIntervalMs; - // Note: At this point, the incoming message is already a decoded MessagePack object. - _onMessage(dynamic message) { - onMessage(Message.fromList(message)); - } - @override - void send(Message message) { - // Serialize the message using MessagePack and send it. - _socket!.add( - msgpack.serialize(message.toList(), extEncoder: FletMsgpackEncoder())); + void send(Uint8List packet) { + final header = ByteData(4)..setUint32(0, packet.length, Endian.little); + _socket!.add(header.buffer.asUint8List()); + _socket!.add(packet); } @override diff --git a/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart b/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart index 34cb16580c..4529e6532b 100644 --- a/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart +++ b/packages/flet/lib/src/transport/flet_backend_channel_web_socket.dart @@ -1,27 +1,23 @@ import 'package:flutter/foundation.dart'; -import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import 'package:web_socket_channel/web_socket_channel.dart'; -import '../protocol/message.dart'; import '../utils/networking.dart'; import '../utils/platform_utils_web.dart' if (dart.library.io) "../utils/platform_utils_non_web.dart"; import '../utils/uri.dart'; import 'flet_backend_channel.dart'; -import 'flet_msgpack_decoder.dart'; -import 'flet_msgpack_encoder.dart'; class FletWebSocketBackendChannel implements FletBackendChannel { late final String _wsUrl; late final bool _isLocalConnection; - FletBackendChannelOnMessageCallback onMessage; + FletBackendChannelOnPacketCallback onPacket; FletBackendChannelOnDisconnectCallback onDisconnect; WebSocketChannel? _channel; FletWebSocketBackendChannel( {required String address, required this.onDisconnect, - required this.onMessage}) { + required this.onPacket}) { _wsUrl = getWebSocketEndpoint(Uri.parse(address)); } @@ -35,7 +31,6 @@ class FletWebSocketBackendChannel implements FletBackendChannel { Future connect() async { debugPrint("Connecting to WebSocket $_wsUrl..."); try { - // todo var uri = Uri.parse(_wsUrl); if (kIsWeb) { _isLocalConnection = isLocalhost(uri); @@ -56,15 +51,21 @@ class FletWebSocketBackendChannel implements FletBackendChannel { }); } - _onMessage(message) { - onMessage(Message.fromList( - msgpack.deserialize(message, extDecoder: FletMsgpackDecoder()))); + void _onMessage(dynamic message) { + // Each WebSocket binary message is one complete packet — message + // boundaries are preserved by the transport, no framing needed here. + if (message is Uint8List) { + onPacket(message); + } else if (message is List) { + onPacket(Uint8List.fromList(message)); + } else { + debugPrint("Unexpected WebSocket message type: ${message.runtimeType}"); + } } @override - void send(Message message) { - _channel?.sink.add( - msgpack.serialize(message.toList(), extEncoder: FletMsgpackEncoder())); + void send(Uint8List packet) { + _channel?.sink.add(packet); } @override diff --git a/packages/flet/lib/src/transport/protocol_muxed_data_channel.dart b/packages/flet/lib/src/transport/protocol_muxed_data_channel.dart new file mode 100644 index 0000000000..6dc9569d04 --- /dev/null +++ b/packages/flet/lib/src/transport/protocol_muxed_data_channel.dart @@ -0,0 +1,77 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import '../flet_backend.dart'; +import 'data_channel.dart'; + +/// Default [DataChannelFactory] used when the embedder does not inject a +/// faster transport (i.e. dev mode `flet run` over UDS/TCP, web with +/// Python server, web with Pyodide). Allocates a monotonic 32-bit id per +/// channel and ships frames as `[0x01][channel_id:u32 LE][payload]` over +/// the active [FletBackendChannel] alongside regular Flet protocol traffic. +/// +/// Ids start at 1; 0 is reserved as the "unallocated" sentinel for the +/// `channel_id` field on a control before Dart has minted one. Counter is +/// session-scoped (one [FletBackend] = one session); at one allocation +/// per microsecond the space lasts ~1 hour, well past any realistic +/// channel-churn pattern. +class ProtocolMuxedDataChannelFactory implements DataChannelFactory { + ProtocolMuxedDataChannelFactory(this._backend); + final FletBackend _backend; + int _nextId = 1; + + @override + DataChannel open() { + final id = _nextId++; + return ProtocolMuxedDataChannel(_backend, id); + } +} + +/// Concrete [DataChannel] that rides the Flet protocol transport. Frames +/// are wrapped as `[0x01][channel_id:u32 LE][payload]`; inbound frames are +/// dispatched to [deliver] via [FletBackend]'s mux registry. +class ProtocolMuxedDataChannel implements DataChannel { + ProtocolMuxedDataChannel(this._backend, this._id) { + _backend.registerDataChannel(_id, this); + } + + final FletBackend _backend; + final int _id; + final StreamController _controller = + StreamController.broadcast(); + bool _closed = false; + + @override + int get id => _id; + + @override + Stream get messages => _controller.stream; + + @override + bool send(Uint8List bytes) { + if (_closed) return false; + // Header: [0x01][channel_id:u32 LE]. Single allocation for the whole + // packet (no BytesBuilder copies) — keeps the hot path tight. + final packet = Uint8List(5 + bytes.length); + packet[0] = 0x01; + final view = ByteData.sublistView(packet, 1, 5); + view.setUint32(0, _id, Endian.little); + packet.setRange(5, packet.length, bytes); + _backend.sendRawPacket(packet); + return true; + } + + /// Called by [FletBackend._onPacket] when a 0x01 frame arrives for this id. + void deliver(Uint8List bytes) { + if (_closed) return; + _controller.add(bytes); + } + + @override + void close() { + if (_closed) return; + _closed = true; + _backend.unregisterDataChannel(_id); + _controller.close(); + } +} diff --git a/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart b/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart deleted file mode 100644 index 41c8dff14b..0000000000 --- a/packages/flet/lib/src/transport/streaming_msgpack_deserializer.dart +++ /dev/null @@ -1,291 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:msgpack_dart/msgpack_dart.dart'; - -/// Thrown when a complete MessagePack object cannot be decoded -/// because the data is incomplete. -class IncompleteDataError extends FormatError { - IncompleteDataError(super.message); -} - -class _Deserializer { - final ExtDecoder? _extDecoder; - final codec = const Utf8Codec(); - final Uint8List _list; - final ByteData _data; - int _offset = 0; - - /// If false, decoded binary data buffers will reference underlying input - /// buffer and thus may change when the content of input buffer changes. - /// If true, decoded buffers are copies and the underlying input buffer is - /// free to change after decoding. - final bool copyBinaryData; - - /// The current offset after decoding - int get offset => _offset; - - _Deserializer( - Uint8List list, { - ExtDecoder? extDecoder, - this.copyBinaryData = false, - int initialOffset = 0, - }) : _list = list, - _data = - ByteData.view(list.buffer, list.offsetInBytes, list.lengthInBytes), - _extDecoder = extDecoder { - _offset = initialOffset; - } - - // Checks that at least [required] bytes are available, - // or throws an IncompleteDataError. - void _ensureAvailable(int required) { - if (_offset + required > _list.length) { - throw IncompleteDataError( - "Not enough data: require $required more bytes, available ${_list.length - _offset}"); - } - } - - dynamic decode() { - _ensureAvailable(1); - final u = _list[_offset++]; - if (u <= 127) { - return u; - } else if ((u & 0xE0) == 0xE0) { - // negative small integer - return u - 256; - } else if ((u & 0xE0) == 0xA0) { - return _readString(u & 0x1F); - } else if ((u & 0xF0) == 0x90) { - return _readArray(u & 0xF); - } else if ((u & 0xF0) == 0x80) { - return _readMap(u & 0xF); - } - switch (u) { - case 0xc0: - return null; - case 0xc2: - return false; - case 0xc3: - return true; - case 0xcc: - return _readUInt8(); - case 0xcd: - return _readUInt16(); - case 0xce: - return _readUInt32(); - case 0xcf: - return _readUInt64(); - case 0xd0: - return _readInt8(); - case 0xd1: - return _readInt16(); - case 0xd2: - return _readInt32(); - case 0xd3: - return _readInt64(); - case 0xca: - return _readFloat(); - case 0xcb: - return _readDouble(); - case 0xd9: - return _readString(_readUInt8()); - case 0xda: - return _readString(_readUInt16()); - case 0xdb: - return _readString(_readUInt32()); - case 0xc4: - return _readBuffer(_readUInt8()); - case 0xc5: - return _readBuffer(_readUInt16()); - case 0xc6: - return _readBuffer(_readUInt32()); - case 0xdc: - return _readArray(_readUInt16()); - case 0xdd: - return _readArray(_readUInt32()); - case 0xde: - return _readMap(_readUInt16()); - case 0xdf: - return _readMap(_readUInt32()); - case 0xd4: - return _readExt(1); - case 0xd5: - return _readExt(2); - case 0xd6: - return _readExt(4); - case 0xd7: - return _readExt(8); - case 0xd8: - return _readExt(16); - case 0xc7: - return _readExt(_readUInt8()); - case 0xc8: - return _readExt(_readUInt16()); - case 0xc9: - return _readExt(_readUInt32()); - default: - throw FormatError("Invalid MessagePack format"); - } - } - - int _readInt8() { - _ensureAvailable(1); - return _data.getInt8(_offset++); - } - - int _readUInt8() { - _ensureAvailable(1); - return _data.getUint8(_offset++); - } - - int _readUInt16() { - _ensureAvailable(2); - final res = _data.getUint16(_offset); - _offset += 2; - return res; - } - - int _readInt16() { - _ensureAvailable(2); - final res = _data.getInt16(_offset); - _offset += 2; - return res; - } - - int _readUInt32() { - _ensureAvailable(4); - final res = _data.getUint32(_offset); - _offset += 4; - return res; - } - - int _readInt32() { - _ensureAvailable(4); - final res = _data.getInt32(_offset); - _offset += 4; - return res; - } - - int _readUInt64() { - _ensureAvailable(8); - final res = _data.getUint64(_offset); - _offset += 8; - return res; - } - - int _readInt64() { - _ensureAvailable(8); - final res = _data.getInt64(_offset); - _offset += 8; - return res; - } - - double _readFloat() { - _ensureAvailable(4); - final res = _data.getFloat32(_offset); - _offset += 4; - return res; - } - - double _readDouble() { - _ensureAvailable(8); - final res = _data.getFloat64(_offset); - _offset += 8; - return res; - } - - Uint8List _readBuffer(int length) { - _ensureAvailable(length); - final res = - Uint8List.view(_list.buffer, _list.offsetInBytes + _offset, length); - _offset += length; - return copyBinaryData ? Uint8List.fromList(res) : res; - } - - String _readString(int length) { - final list = _readBuffer(length); - final len = list.length; - for (int i = 0; i < len; ++i) { - if (list[i] > 127) { - return codec.decode(list); - } - } - return String.fromCharCodes(list); - } - - List _readArray(int length) { - final res = List.filled(length, null, growable: false); - for (int i = 0; i < length; ++i) { - res[i] = decode(); - } - return res; - } - - Map _readMap(int length) { - final res = {}; - while (length > 0) { - res[decode()] = decode(); - --length; - } - return res; - } - - dynamic _readExt(int length) { - final extType = _readUInt8(); - final data = _readBuffer(length); - return _extDecoder?.decodeObject(extType, data); - } -} - -/// A helper to decode MessagePack data as it arrives in chunks. -/// Call [addChunk] for every incoming piece of data, -/// then [decodeMessages] to retrieve complete messages. -class StreamingMsgpackDeserializer { - final ExtDecoder? _extDecoder; - final bool copyBinaryData; - final BytesBuilder _buffer = BytesBuilder(); - - StreamingMsgpackDeserializer( - {ExtDecoder? extDecoder, this.copyBinaryData = false}) - : _extDecoder = extDecoder; - - /// Adds a new chunk of MessagePack data. - void addChunk(Uint8List chunk) { - _buffer.add(chunk); - } - - /// Attempts to decode as many complete messages as possible - /// from the buffered data. Incomplete trailing data remains in the buffer. - List decodeMessages() { - List messages = []; - Uint8List data = _buffer.takeBytes(); - int offset = 0; - while (offset < data.length) { - try { - // Create a Deserializer using the current offset - _Deserializer d = _Deserializer( - data, - extDecoder: _extDecoder, - copyBinaryData: copyBinaryData, - initialOffset: offset, - ); - dynamic message = d.decode(); - messages.add(message); - offset = d.offset; - } on IncompleteDataError { - // Not enough data to decode a full message; break out of the loop. - break; - } on FormatError { - // For actual format errors (not just incomplete data), - // rethrow or handle as needed. - rethrow; - } - } - // If there is any leftover (incomplete) data, put it back into the buffer. - if (offset < data.length) { - _buffer.add(data.sublist(offset)); - } - return messages; - } -} diff --git a/packages/flet/test/transport/data_channel_test.dart b/packages/flet/test/transport/data_channel_test.dart new file mode 100644 index 0000000000..750caf9171 --- /dev/null +++ b/packages/flet/test/transport/data_channel_test.dart @@ -0,0 +1,128 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flet/flet.dart'; +import 'package:flet/src/transport/protocol_muxed_data_channel.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Minimal in-memory transport: captures outbound packets and lets the test +/// inject inbound ones, exercising FletBackend's type-byte dispatch + the +/// muxed DataChannel routing without spinning up a real transport. +class _FakeChannel implements FletBackendChannel { + final List sent = []; + late FletBackendChannelOnPacketCallback _onPacket; + + FletBackendChannelBuilder get builder => ({ + required FletBackendChannelOnPacketCallback onPacket, + required FletBackendChannelOnDisconnectCallback onDisconnect, + }) { + _onPacket = onPacket; + return this; + }; + + void deliver(Uint8List packet) => _onPacket(packet); + + @override + Future connect() async {} + + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 0; + + @override + void send(Uint8List packet) => sent.add(Uint8List.fromList(packet)); + + @override + void disconnect() {} +} + +void main() { + group('ProtocolMuxedDataChannel wire format', () { + test('send emits [0x01][channel_id:u32 LE][payload]', () { + final backend = FletBackend( + pageUri: Uri.parse("mock"), + assetsDir: "", + extensions: [], + multiView: false); + final ch1 = backend.openDataChannel(); + final ch2 = backend.openDataChannel(); + expect(ch1.id, 1); + expect(ch2.id, 2); + }); + + test('inbound 0x01 frame routes to the right channel', () async { + final fake = _FakeChannel(); + final backend = FletBackend( + pageUri: Uri.parse("mock"), + assetsDir: "", + extensions: [], + multiView: false, + channelBuilder: fake.builder); + // Trigger the connect path so FletBackend wires the fake's onPacket. + // We don't await — connect() also tries _registerClient which goes + // through the (now unused) channel send path; both are fine for this + // smoke test. + // ignore: unawaited_futures + backend.connect(); + + final chA = backend.openDataChannel() as ProtocolMuxedDataChannel; + final chB = backend.openDataChannel() as ProtocolMuxedDataChannel; + + final aFrames = []; + final bFrames = []; + chA.messages.listen(aFrames.add); + chB.messages.listen(bFrames.add); + + // Inbound for channel B (id=2) → 0x01, id LE, payload. + final inbound = Uint8List(5 + 3); + inbound[0] = 0x01; + ByteData.sublistView(inbound, 1, 5).setUint32(0, chB.id, Endian.little); + inbound.setRange(5, 8, [10, 20, 30]); + fake.deliver(inbound); + + // Let the stream-controller microtask flush. + await Future.delayed(Duration.zero); + + expect(aFrames, isEmpty); + expect(bFrames, hasLength(1)); + expect(bFrames.single, equals(Uint8List.fromList([10, 20, 30]))); + + // Stale frame for closed channel is silently dropped. + chA.close(); + final staleA = Uint8List(5 + 1); + staleA[0] = 0x01; + ByteData.sublistView(staleA, 1, 5).setUint32(0, 1, Endian.little); + staleA[5] = 99; + fake.deliver(staleA); // no throw, no delivery + }); + + test('outbound send emits the muxed packet shape', () async { + final fake = _FakeChannel(); + final backend = FletBackend( + pageUri: Uri.parse("mock"), + assetsDir: "", + extensions: [], + multiView: false, + channelBuilder: fake.builder); + // ignore: unawaited_futures + backend.connect(); + + // Drain the register-client packet that FletBackend.connect emits. + final preCount = fake.sent.length; + + final ch = backend.openDataChannel(); + ch.send(Uint8List.fromList([0xAA, 0xBB, 0xCC])); + + // Filter to the 0x01 packets we care about. + final dataPackets = + fake.sent.skip(preCount).where((p) => p.isNotEmpty && p[0] == 0x01).toList(); + expect(dataPackets, hasLength(1)); + final p = dataPackets.single; + expect(p[0], 0x01); + expect(ByteData.sublistView(p, 1, 5).getUint32(0, Endian.little), ch.id); + expect(p.sublist(5), equals(Uint8List.fromList([0xAA, 0xBB, 0xCC]))); + }); + }); +} diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py index 2c920c5b02..aa8e628f08 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py @@ -96,6 +96,11 @@ def __init__( self.__upload_endpoint_path = upload_endpoint_path self.__secret_key = secret_key + # DataChannel mux registry keyed by channel_id minted on the Dart + # side. Populated lazily on the first Control.get_data_channel(id) + # call. Frames for unknown ids are silently dropped. + self._data_channels: dict[int, Any] = {} + app_id = self.__id weakref.finalize( self, lambda: logger.info(f"FletApp was garbage collected: {app_id}") @@ -214,6 +219,11 @@ async def __receive_loop(self): """ Receive binary frames from WebSocket and dispatch decoded client messages. + Wire format: each WebSocket binary frame is one packet — + `[type:u8][payload]`. type=0x00 is a MsgPack-encoded Flet protocol + frame; type=0x01 is a raw DataChannel frame + (`[channel_id:u32 LE][bytes]`). + On disconnect/error, terminates send loop via queue sentinel when a session is active. """ @@ -222,9 +232,23 @@ async def __receive_loop(self): try: while True: data = await self.__websocket.receive_bytes() - await self.__on_message( - msgpack.unpackb(data, ext_hook=decode_ext_from_msgpack) - ) + if not data: + continue + ptype = data[0] + if ptype == 0x00: + await self.__on_message( + msgpack.unpackb(data[1:], ext_hook=decode_ext_from_msgpack) + ) + elif ptype == 0x01: + if len(data) < 5: + logger.debug("Dropping malformed data-channel frame.") + continue + channel_id = int.from_bytes(data[1:5], "little", signed=False) + channel = self._data_channels.get(channel_id) + if channel is not None: + channel._deliver(data[5:]) + else: + logger.debug("Dropping packet with unknown type 0x%02x", ptype) except Exception as e: if not isinstance(e, WebSocketDisconnect): logger.warning(f"Receive loop error: {e}", exc_info=True) @@ -383,16 +407,40 @@ def send_message(self, message: ClientMessage): """ Serialize and enqueue a server message for transport to the client. + Wire format: one packet per `send_bytes` call — + `[0x00][msgpack body]`. WebSocket preserves message boundaries so + no length prefix is needed. + Args: message: Outbound protocol message. """ transport_log.debug(f"send_message: {message}") - m = msgpack.packb( + body = msgpack.packb( [message.action, message.body], default=configure_encode_object_for_msgpack(BaseControl), ) - self.__send_queue.put_nowait(m) + self.__send_queue.put_nowait(b"\x00" + body) + + def send_data_channel_frame(self, channel_id: int, payload: bytes) -> None: + """Send a raw DataChannel frame `[0x01][channel_id:u32 LE][bytes]` + over the WebSocket. Called by `_ProtocolMuxedDataChannel.send`.""" + header = b"\x01" + channel_id.to_bytes(4, "little", signed=False) + self.__send_queue.put_nowait(header + payload) + + def data_channel_for(self, channel_id: int): + """Resolve or construct the muxed DataChannel for `channel_id`.""" + from flet.data_channel import _ProtocolMuxedDataChannel + + existing = self._data_channels.get(channel_id) + if existing is not None: + return existing + channel = _ProtocolMuxedDataChannel(channel_id, self) + self._data_channels[channel_id] = channel + return channel + + def unregister_data_channel(self, channel_id: int) -> None: + self._data_channels.pop(channel_id, None) def get_upload_url(self, file_name: str, expires: int) -> str: """ diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index ee854e7b27..37ac5880b9 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -562,6 +562,7 @@ VisualDensity, WebRenderer, ) +from flet.data_channel import DataChannel, DataChannelOpenEvent from flet.pubsub.pubsub_client import PubSubClient from flet.pubsub.pubsub_hub import PubSubHub from flet.version import flet_version as __version__ @@ -755,6 +756,8 @@ "CupertinoTimerPickerMode", "CupertinoTintedButton", "DataCell", + "DataChannel", + "DataChannelOpenEvent", "DataColumn", "DataColumnSortEvent", "DataRow", diff --git a/sdk/python/packages/flet/src/flet/controls/base_control.py b/sdk/python/packages/flet/src/flet/controls/base_control.py index 95e29de41c..86ceb41ff4 100644 --- a/sdk/python/packages/flet/src/flet/controls/base_control.py +++ b/sdk/python/packages/flet/src/flet/controls/base_control.py @@ -362,6 +362,26 @@ def will_unmount(self): controls_log.debug("%s.will_unmount()", self) pass + def get_data_channel(self, channel_id: int): + """ + Resolve the [DataChannel] allocated on the Dart side for this + widget. Pattern: + + on_data_channel_open: Optional[ft.EventHandler[DataChannelOpenEvent]] = None + + def init(self): + self.on_data_channel_open = self._on_open + + def _on_open(self, e): + self._channel = self.get_data_channel(e.channel_id) + + Idempotent — the underlying Connection caches DataChannels by id, + so repeated calls return the same instance. No error path: the id + always comes from a framework-fired event, so by the time this + runs the channel exists on both sides. + """ + return self.page.session.connection.data_channel_for(channel_id) + # public methods def update(self) -> None: """ diff --git a/sdk/python/packages/flet/src/flet/data_channel.py b/sdk/python/packages/flet/src/flet/data_channel.py new file mode 100644 index 0000000000..ae3065c12d --- /dev/null +++ b/sdk/python/packages/flet/src/flet/data_channel.py @@ -0,0 +1,153 @@ +"""Widget-facing byte-channel API. + +A `DataChannel` is a dedicated bidirectional byte transport between a +single widget's Dart side and its Python counterpart, separate from the +Flet control protocol. Used for bulk binary data — image frames, audio +buffers, ML tensors — that would otherwise pay MsgPack encode/decode +overhead through the regular protocol channel. + +The transport implementation is chosen by the active Connection subclass: + +- `FletDartBridgeServer` (embedded native via `dart_bridge`): each + channel rides its own dedicated PythonBridge native port (4–7 GiB/s on + M2 Pro). +- `FletSocketServer` / `flet_web.fastapi.FletApp` (dev mode, web with + Python server): bytes are muxed over the active Flet protocol + transport with a 1-byte type discriminator (0x01) + 4-byte channel id. + +Allocation lives on the Dart side. The widget's Dart code calls +`FletBackend.of(context).openDataChannel()` in `initState`, then fires a +`data_channel_open` control event carrying `{channel_name, channel_id}`. +The Python widget declares `on_data_channel_open: +Optional[ft.EventHandler[DataChannelOpenEvent]] = None` and inside the +handler calls `self.get_data_channel(e.channel_id)` to attach. +""" + +from __future__ import annotations + +import contextlib +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import TYPE_CHECKING, Callable + +from flet.controls.control_event import Event + +if TYPE_CHECKING: + from flet.messaging.connection import Connection + + +@dataclass +class DataChannelOpenEvent(Event["BaseControl"]): + """Fired by Dart when it opens a DataChannel for a control. Carry the + channel id (Dart native port in embedded mode, monotonic u32 in muxed + fallback) plus a user-defined `channel_name` so widgets that open + several channels can dispatch. + + Note: the field is `channel_name`, not `name`, because `Event.name` + already carries the event's own name (`"data_channel_open"`). + """ + + channel_name: str = "" + channel_id: int = 0 + + +class DataChannel(ABC): + """Abstract widget-facing byte channel.""" + + @abstractmethod + def on_bytes(self, handler: Callable[[bytes], None] | None) -> None: + """Register a handler for bytes pushed from Dart. Pass `None` to + clear. The handler runs synchronously on whatever thread the + transport delivers from — push heavy work to a queue/worker.""" + ... + + @abstractmethod + def send(self, payload: bytes) -> None: + """Send bytes Python → Dart. Fire-and-forget.""" + ... + + @abstractmethod + def close(self) -> None: + """Release the channel. Idempotent.""" + ... + + +class _DartBridgeDataChannel(DataChannel): + """Embedded native mode: bytes flow over a dedicated PythonBridge port. + `channel_id` is the Dart native port number minted on the Dart side. + """ + + def __init__(self, port: int) -> None: + import dart_bridge # type: ignore — built-in module from libdart_bridge + + self._port = port + self._dart_bridge = dart_bridge + self._handler: Callable[[bytes], None] | None = None + self._closed = False + + def on_bytes(self, handler: Callable[[bytes], None] | None) -> None: + self._handler = handler + self._dart_bridge.set_enqueue_handler_func(self._port, handler) + + def send(self, payload: bytes) -> None: + if self._closed: + return + self._dart_bridge.send_bytes(self._port, payload) + + def close(self) -> None: + if self._closed: + return + self._closed = True + if self._handler is not None: + with contextlib.suppress(Exception): + self._dart_bridge.set_enqueue_handler_func(self._port, None) + self._handler = None + + +class _ProtocolMuxedDataChannel(DataChannel): + """Non-embedded modes: bytes ride the Flet protocol transport as + `[0x01][channel_id:u32 LE][payload]` frames. The owning Connection + routes inbound frames here via `_deliver()`. + """ + + def __init__(self, channel_id: int, conn: Connection) -> None: + self._id = channel_id + self._conn = conn + self._handler: Callable[[bytes], None] | None = None + self._closed = False + + def on_bytes(self, handler: Callable[[bytes], None] | None) -> None: + self._handler = handler + + def send(self, payload: bytes) -> None: + if self._closed: + return + # Connection knows the wire format for its transport (length + # prefix on stream transports, none on message transports). + self._conn.send_data_channel_frame(self._id, payload) + + def _deliver(self, payload: bytes) -> None: + if self._closed: + return + handler = self._handler + if handler is not None: + try: + handler(payload) + except Exception: + import logging + + logging.getLogger("flet").exception( + "DataChannel handler raised; channel id=%s", self._id + ) + + def close(self) -> None: + if self._closed: + return + self._closed = True + # Connection's unregister is best-effort — duplicate unregisters + # are safe. + unreg = getattr(self._conn, "unregister_data_channel", None) + if unreg is not None: + with contextlib.suppress(Exception): + unreg(self._id) + self._handler = None diff --git a/sdk/python/packages/flet/src/flet/messaging/connection.py b/sdk/python/packages/flet/src/flet/messaging/connection.py index aa80edd72d..b4aa05081b 100644 --- a/sdk/python/packages/flet/src/flet/messaging/connection.py +++ b/sdk/python/packages/flet/src/flet/messaging/connection.py @@ -120,3 +120,39 @@ def dispose(self): Subclasses can override this method to clean up transport-specific state. """ pass + + # ------------------------------------------------------------------ + # DataChannel transport hooks. Subclasses implement these for the + # transports that carry widget byte channels: dart_bridge mode (each + # channel = a dedicated PythonBridge port), socket / WebSocket modes + # (channel frames muxed over the same protocol transport). + # ------------------------------------------------------------------ + + def data_channel_for(self, channel_id: int): + """Return the `DataChannel` for `channel_id` allocated on the Dart + side. Idempotent — subsequent calls with the same id return the + same instance within the session. + + Called by `Control.get_data_channel(id)` after a `data_channel_open` + event arrives. + """ + raise NotImplementedError( + f"{type(self).__name__} does not support DataChannel " + "(transport-specific implementation missing)." + ) + + def send_data_channel_frame(self, channel_id: int, payload: bytes) -> None: + """Send a raw DataChannel frame for `channel_id`. Used by the + protocol-muxed implementation only — dart_bridge channels go + through their own dedicated `dart_bridge.send_bytes(port, ...)` + call instead. + """ + raise NotImplementedError( + f"{type(self).__name__} does not implement send_data_channel_frame." + ) + + def unregister_data_channel(self, channel_id: int) -> None: + """Best-effort drop of `channel_id` from the mux registry. Default + is a no-op so subclasses that don't keep one don't need to override. + """ + pass diff --git a/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py b/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py index de32bd3db6..e6a14383de 100644 --- a/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py +++ b/sdk/python/packages/flet/src/flet/messaging/flet_dart_bridge_server.py @@ -108,21 +108,41 @@ def __on_bytes(self, payload: bytes) -> None: async def __inbound_loop(self): """ - Drains the inbound queue, feeds bytes into a streaming MsgPack - unpacker, and dispatches each complete frame. + Drains the inbound queue and dispatches each packet. Each dart_bridge + send delivers one complete packet — `[type:u8][payload]`. type=0x00 + is a MsgPack-encoded Flet control frame, decoded and dispatched as + a protocol message; type=0x01 is a raw DataChannel frame, but on + the dart_bridge transport that *only* carries the Flet protocol — + DataChannels in embedded mode get their own dedicated PythonBridge, + so a 0x01 here would be an error and we log+drop. """ - unpacker = msgpack.Unpacker(ext_hook=decode_ext_from_msgpack) try: while True: - payload = await self.__inbound_queue.get() - unpacker.feed(payload) - for msg in unpacker: + packet = await self.__inbound_queue.get() + if not packet: + continue + ptype = packet[0] + if ptype == 0x00: try: + msg = msgpack.unpackb( + packet[1:], ext_hook=decode_ext_from_msgpack + ) await self.__on_message(msg) except Exception: logger.error( "Error dispatching dart_bridge frame", exc_info=True ) + elif ptype == 0x01: + logger.debug( + "dart_bridge channel received a 0x01 data frame; " + "DataChannels in embedded mode should use a dedicated " + "PythonBridge, not the protocol channel." + ) + else: + logger.debug( + "dart_bridge channel received packet with unknown type 0x%02x", + ptype, + ) except asyncio.CancelledError: logger.debug("dart_bridge inbound loop cancelled.") @@ -200,18 +220,39 @@ def send_message(self, message: ClientMessage): """ Encodes a protocol message and posts it to the Dart side via `dart_bridge.send_bytes`. Non-blocking; ordering is preserved by the - Dart VM's port queue. + Dart VM's port queue. Wire format: `[0x00][msgpack body]` — no + length prefix (the bridge preserves message boundaries). """ transport_log.debug("send_message: %s", message) - m = msgpack.packb( + body = msgpack.packb( [message.action, message.body], default=configure_encode_object_for_msgpack(BaseControl), ) + packet = b"\x00" + body try: - dart_bridge.send_bytes(self.__port, m) + dart_bridge.send_bytes(self.__port, packet) except Exception: logger.error("dart_bridge.send_bytes failed", exc_info=True) + def data_channel_for(self, channel_id: int): + """Resolve the DataChannel for `channel_id`. In embedded native mode + each DataChannel rides its own dedicated PythonBridge native port + (the `channel_id` *is* the port). Idempotent. + """ + from flet.data_channel import _DartBridgeDataChannel + + # No registry needed: _DartBridgeDataChannel is stateless wrt port + # and the dart_bridge module routes by port number internally. We + # still cache per port to avoid duplicate handler registrations. + if not hasattr(self, "_data_channels"): + self._data_channels: dict[int, Any] = {} + existing = self._data_channels.get(channel_id) + if existing is not None: + return existing + channel = _DartBridgeDataChannel(channel_id) + self._data_channels[channel_id] = channel + return channel + async def close(self): """ Releases the dart_bridge handler registration and cancels pending diff --git a/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py b/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py index 73596cb4bc..597af22d5a 100644 --- a/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py +++ b/sdk/python/packages/flet/src/flet/messaging/flet_socket_server.py @@ -71,6 +71,10 @@ def __init__( self.__before_main = before_main self.__blocking = blocking self.__running_tasks = set() + # DataChannel mux registry. Keyed by channel_id minted on the Dart + # side; populated lazily on the first Control.get_data_channel(id) + # call. Frames for unknown ids are silently dropped. + self._data_channels: dict[int, Any] = {} self.loop = loop self.executor = executor self.pubsubhub = PubSubHub(loop=loop, executor=executor) @@ -244,7 +248,12 @@ async def __terminate_active_connection_locked(self, reason: str) -> None: async def __receive_loop(self, reader: asyncio.StreamReader, connection_token: int): """ - Reads and dispatches inbound MsgPack frames from the socket. + Reads and dispatches inbound packets from the socket. + + Wire format on the byte stream: each packet is prefixed with a 4-byte + little-endian length, then `[type:u8][payload]`. type=0x00 is a + MsgPack-encoded Flet protocol frame; type=0x01 is a raw DataChannel + frame (`[channel_id:u32 LE][bytes]`). The loop exits when: - socket EOF is reached; @@ -255,17 +264,34 @@ async def __receive_loop(self, reader: asyncio.StreamReader, connection_token: i reader: Socket stream reader to consume bytes from. connection_token: Token identifying the connection generation. """ - unpacker = msgpack.Unpacker(ext_hook=decode_ext_from_msgpack) try: while True: - buf = await reader.read(1024 * 1024) - if not buf: + try: + header = await reader.readexactly(4) + except asyncio.IncompleteReadError: + break # EOF mid-stream or clean close + length = int.from_bytes(header, "little", signed=False) + if length == 0: + continue + try: + packet = await reader.readexactly(length) + except asyncio.IncompleteReadError: + logger.debug("Truncated packet read; aborting receive loop.") break - unpacker.feed(buf) - for msg in unpacker: - if self.__connection_token != connection_token: - return + if self.__connection_token != connection_token: + return + ptype = packet[0] + if ptype == 0x00: + msg = msgpack.unpackb(packet[1:], ext_hook=decode_ext_from_msgpack) await self.__on_message(msg) + elif ptype == 0x01: + if len(packet) < 5: + logger.debug("Dropping malformed data-channel frame.") + continue + channel_id = int.from_bytes(packet[1:5], "little", signed=False) + self.__on_data_channel_frame(channel_id, packet[5:]) + else: + logger.debug("Dropping packet with unknown type 0x%02x", ptype) except asyncio.CancelledError: logger.debug("Receive loop cancelled.") except Exception as e: @@ -273,6 +299,14 @@ async def __receive_loop(self, reader: asyncio.StreamReader, connection_token: i finally: logger.debug("Receive loop exiting.") + def __on_data_channel_frame(self, channel_id: int, payload: bytes) -> None: + """Routes an inbound `[0x01][channel_id][payload]` frame to its + registered DataChannel. Silently drops frames for unknown ids + (handles unmount races).""" + channel = self._data_channels.get(channel_id) + if channel is not None: + channel._deliver(payload) + async def __send_loop( self, writer: asyncio.StreamWriter, @@ -390,18 +424,49 @@ def send_message(self, message: ClientMessage): """ Encodes and queues an outbound message for the active connection. + Wire format: `[length:u32 LE][0x00][msgpack body]`. The send loop + writes the bytes verbatim to the socket. + If no active send queue exists (no connected client), the message is dropped. Args: message: Protocol message to send. """ transport_log.debug("send_message: %s", message) - m = msgpack.packb( + body = msgpack.packb( [message.action, message.body], default=configure_encode_object_for_msgpack(BaseControl), ) + packet = b"\x00" + body + framed = len(packet).to_bytes(4, "little", signed=False) + packet if self.__send_queue is not None: - self.__send_queue.put_nowait(m) + self.__send_queue.put_nowait(framed) + + def send_data_channel_frame(self, channel_id: int, payload: bytes) -> None: + """Send a raw DataChannel frame `[length][0x01][channel_id:u32 LE][bytes]`. + Called by `_ProtocolMuxedDataChannel.send` on the Python side.""" + header = b"\x01" + channel_id.to_bytes(4, "little", signed=False) + packet = header + payload + framed = len(packet).to_bytes(4, "little", signed=False) + packet + if self.__send_queue is not None: + self.__send_queue.put_nowait(framed) + + def data_channel_for(self, channel_id: int): + """Resolve or construct the muxed DataChannel for `channel_id`. + Idempotent — returns the same instance per id within the session. + Called from `Control.get_data_channel(id)`. + """ + from flet.data_channel import _ProtocolMuxedDataChannel + + existing = self._data_channels.get(channel_id) + if existing is not None: + return existing + channel = _ProtocolMuxedDataChannel(channel_id, self) + self._data_channels[channel_id] = channel + return channel + + def unregister_data_channel(self, channel_id: int) -> None: + self._data_channels.pop(channel_id, None) async def close(self): """ diff --git a/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py b/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py index ee51c4f589..3a18dd6e16 100644 --- a/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py +++ b/sdk/python/packages/flet/src/flet/messaging/pyodide_connection.py @@ -50,6 +50,10 @@ def __init__( self.__before_main = before_main flet_js.start_connection = self.connect self.__running_tasks = set() + # DataChannel mux registry. Pyodide mode has no dart_bridge, so + # DataChannels ride the same postMessage transport as the Flet + # protocol — disambiguated by the wire-format type byte. + self._data_channels: dict[int, Any] = {} self.pubsubhub = PubSubHub() self.loop = asyncio.get_running_loop() @@ -69,15 +73,32 @@ async def connect(self, send_callback): async def receive_loop(self): """ - Continuously receives, decodes, and dispatches inbound client messages. + Continuously receives, decodes, and dispatches inbound packets. - This loop waits for raw messages queued by `send_from_js()`, decodes MsgPack - payloads, and forwards parsed protocol frames to `__on_message()`. + Wire format on the postMessage transport: each packet from Dart is + `[type:u8][payload]`. type=0x00 is a MsgPack-encoded Flet protocol + frame; type=0x01 is a raw DataChannel frame + (`[channel_id:u32 LE][bytes]`). """ while True: data = await self.__receive_queue.get() - message = msgpack.unpackb(data.to_py(), ext_hook=decode_ext_from_msgpack) - await self.__on_message(message) + packet = bytes(data.to_py()) + if not packet: + continue + ptype = packet[0] + if ptype == 0x00: + message = msgpack.unpackb(packet[1:], ext_hook=decode_ext_from_msgpack) + await self.__on_message(message) + elif ptype == 0x01: + if len(packet) < 5: + logger.debug("Dropping malformed data-channel frame.") + continue + channel_id = int.from_bytes(packet[1:5], "little", signed=False) + channel = self._data_channels.get(channel_id) + if channel is not None: + channel._deliver(packet[5:]) + else: + logger.debug("Dropping packet with unknown type 0x%02x", ptype) def send_from_js(self, message: Any): """ @@ -173,12 +194,37 @@ def send_message(self, message: ClientMessage): """ Serializes and sends an outbound protocol message to JavaScript. + Wire format: `[0x00][msgpack body]`. postMessage preserves message + boundaries, so no length prefix. + Args: message: Client message to serialize with MsgPack and send. """ transport_log.debug("send_message: %s", message) - m = msgpack.packb( + body = msgpack.packb( [message.action, message.body], default=configure_encode_object_for_msgpack(BaseControl), ) - self.send_callback(m) + self.send_callback(b"\x00" + body) + + def send_data_channel_frame(self, channel_id: int, payload: bytes) -> None: + """Send a raw DataChannel frame `[0x01][channel_id:u32 LE][bytes]` + over postMessage. Called by `_ProtocolMuxedDataChannel.send`.""" + header = b"\x01" + channel_id.to_bytes(4, "little", signed=False) + self.send_callback(header + payload) + + def data_channel_for(self, channel_id: int): + """Resolve or construct the muxed DataChannel for `channel_id`. + Idempotent — same id returns the same instance within the session. + """ + from flet.data_channel import _ProtocolMuxedDataChannel + + existing = self._data_channels.get(channel_id) + if existing is not None: + return existing + channel = _ProtocolMuxedDataChannel(channel_id, self) + self._data_channels[channel_id] = channel + return channel + + def unregister_data_channel(self, channel_id: int) -> None: + self._data_channels.pop(channel_id, None) From 4e98dab29d6181b6490062401d099ba0b3783903 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:39:48 -0700 Subject: [PATCH 09/47] feat(flet-charts): migrate MatplotlibChartCanvas to DataChannel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the `_invoke_method`-based `apply_full` / `apply_diff` / `clear` plumbing with a dedicated `DataChannel` carrying 1-byte-opcode frames (0x01=full PNG, 0x02=diff PNG, 0x03=clear). PNG bytes no longer pay MsgPack encode/decode — they flow at memory-bandwidth-class speed in embedded native mode and at near-bandwidth speed in dev/web modes (raw- byte frames muxed over the protocol transport). Backpressure follows the WebAgg pattern: Dart sends a 1-byte `[0xFF]` ack back over the same channel after each apply chain resolves; the canvas exposes `set_on_frame_applied(callback)` so `MatplotlibChart` clears `_waiting` only after Dart confirms the frame painted, mirroring mpl.js's `img.onload → waiting=false` flow. Without this gate, interactive drags pile up frames in the Dart-side queue and replay in a burst. The 3D example (`examples/.../matplotlib_chart/three_d/main.py`) adds a status bar showing avg full/diff frame size, total bytes transferred, sliding-window transfer speed, FPS, and per-stage latency (dart-side paint vs mpl-side render+idle) so users can see where time is spent. GPU / CPU strategy code in both State subclasses is unchanged — only the source of frames switched from `_invokeMethod(...)` to the channel listener. --- .../charts/matplotlib_chart/three_d/main.py | 223 +++++++++++++++++- .../src/flet_charts/matplotlib_chart.py | 28 ++- .../flet_charts/matplotlib_chart_canvas.py | 93 +++++++- .../lib/src/matplotlib_chart_canvas.dart | 82 ++++--- 4 files changed, 377 insertions(+), 49 deletions(-) diff --git a/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py b/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py index 9cc1c2084d..63c1a29ba1 100644 --- a/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py +++ b/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py @@ -1,4 +1,8 @@ +import asyncio import logging +import time +from collections import deque +from dataclasses import dataclass import matplotlib.pyplot as plt @@ -8,7 +12,134 @@ logging.basicConfig(level=logging.INFO) -def main(page: ft.Page): +@dataclass +class _Frame: + t: float + size: int + + +class FrameStats: + """Rolling counters for the matplotlib WebAgg-style frame stream. + + Cumulative averages for per-frame size + total bytes (lifetime of the + run); a short sliding window for transfer speed and FPS so the bar + reflects current activity rather than being dragged down by idle time. + + Latency split (also sliding-window): + + * `dart_ms` — time from a frame leaving Python (`apply_full/diff` call) + to Dart's `[0xFF]` ack arriving. This is Dart-side decode + paint + + ack transit. Transit is microseconds, so essentially decode + paint. + * `mpl_ms` — time from one ack arriving to the next frame leaving + Python. Combines matplotlib's render of the next frame and any + idle time waiting for matplotlib to react to the next "draw" + request. Under sustained interactive load (continuous dragging) + idle ≈ 0 and this is dominated by matplotlib's render cost. + """ + + WINDOW_SECONDS = 2.0 + + def __init__(self) -> None: + self.full_count = 0 + self.full_total = 0 + self.diff_count = 0 + self.diff_total = 0 + self.bytes_total = 0 + self._recent: deque[_Frame] = deque() + # Latency tracking: each entry is (timestamp_when_observed, latency_seconds). + self._dart_latencies: deque[tuple[float, float]] = deque() + self._mpl_gaps: deque[tuple[float, float]] = deque() + # In-flight bookkeeping for pairing send with ack. + self._inflight_send_ts: float | None = None + self._last_ack_ts: float | None = None + + def record_send(self, size: int, is_full: bool) -> None: + """Frame about to leave Python — record size and mark in-flight.""" + now = time.monotonic() + if is_full: + self.full_count += 1 + self.full_total += size + else: + self.diff_count += 1 + self.diff_total += size + self.bytes_total += size + self._recent.append(_Frame(now, size)) + self._evict(now) + # mpl-side gap: how long since the previous ack. + if self._last_ack_ts is not None: + self._mpl_gaps.append((now, now - self._last_ack_ts)) + self._evict_latency_window(self._mpl_gaps, now) + self._inflight_send_ts = now + + def record_ack(self) -> None: + """Dart confirms the frame painted — close the dart-side timing.""" + now = time.monotonic() + if self._inflight_send_ts is not None: + self._dart_latencies.append((now, now - self._inflight_send_ts)) + self._evict_latency_window(self._dart_latencies, now) + self._inflight_send_ts = None + self._last_ack_ts = now + + def _evict(self, now: float) -> None: + cutoff = now - self.WINDOW_SECONDS + while self._recent and self._recent[0].t < cutoff: + self._recent.popleft() + + def _evict_latency_window( + self, window: deque[tuple[float, float]], now: float + ) -> None: + cutoff = now - self.WINDOW_SECONDS + while window and window[0][0] < cutoff: + window.popleft() + + @property + def avg_full(self) -> float: + return self.full_total / self.full_count if self.full_count else 0.0 + + @property + def avg_diff(self) -> float: + return self.diff_total / self.diff_count if self.diff_count else 0.0 + + def speed_and_fps(self) -> tuple[float, float]: + now = time.monotonic() + self._evict(now) + if not self._recent: + return 0.0, 0.0 + span = max(self.WINDOW_SECONDS, now - self._recent[0].t) + speed = sum(f.size for f in self._recent) / span + fps = len(self._recent) / span + return speed, fps + + def dart_avg_ms(self) -> float: + now = time.monotonic() + self._evict_latency_window(self._dart_latencies, now) + if not self._dart_latencies: + return 0.0 + return ( + 1000.0 + * sum(lat for _, lat in self._dart_latencies) + / len(self._dart_latencies) + ) + + def mpl_avg_ms(self) -> float: + now = time.monotonic() + self._evict_latency_window(self._mpl_gaps, now) + if not self._mpl_gaps: + return 0.0 + return 1000.0 * sum(g for _, g in self._mpl_gaps) / len(self._mpl_gaps) + + +def _human_bytes(n: float) -> str: + if n < 1024: + return f"{n:.0f} B" + if n < 1024 * 1024: + return f"{n / 1024:.1f} KB" + if n < 1024**3: + return f"{n / (1024 * 1024):.1f} MB" + return f"{n / (1024**3):.2f} GB" + + +async def main(page: ft.Page): from mpl_toolkits.mplot3d import axes3d fig = plt.figure() @@ -36,13 +167,101 @@ def main(page: ft.Page): zlabel="Z", ) + chart = flet_charts.MatplotlibChartWithToolbar(figure=fig, expand=True) + + # Status bar: regular Flet Text controls in a Row at the bottom. + avg_full_text = ft.Text("avg full: —", size=12) + avg_diff_text = ft.Text("avg diff: —", size=12) + total_text = ft.Text("total: —", size=12) + speed_text = ft.Text("speed: —", size=12) + fps_text = ft.Text("fps: —", size=12) + dart_text = ft.Text("dart: —", size=12) + mpl_text = ft.Text("mpl: —", size=12) + + status_bar = ft.Container( + content=ft.Row( + [ + avg_full_text, + avg_diff_text, + total_text, + speed_text, + fps_text, + dart_text, + mpl_text, + ], + spacing=20, + ), + padding=ft.Padding.symmetric(horizontal=12, vertical=6), + bgcolor=ft.Colors.SURFACE_CONTAINER_HIGH, + ) + page.add( ft.SafeArea( - content=flet_charts.MatplotlibChartWithToolbar(figure=fig, expand=True), + content=ft.Column( + [chart, status_bar], + expand=True, + spacing=0, + ), expand=True, ) ) + stats = FrameStats() + + # Instrument the canvas to capture per-frame sizes + latency. Both + # `chart.mpl` and `chart.mpl.mpl_canvas` are populated by their + # respective `build()` calls, which run only after the control is + # mounted — hence this wrapping has to happen after `page.add(...)`. + canvas = chart.mpl.mpl_canvas + orig_full = canvas.apply_full + orig_diff = canvas.apply_diff + + def apply_full(image_bytes: bytes) -> None: + stats.record_send(len(image_bytes), is_full=True) + orig_full(image_bytes) + + def apply_diff(image_bytes: bytes) -> None: + stats.record_send(len(image_bytes), is_full=False) + orig_diff(image_bytes) + + canvas.apply_full = apply_full + canvas.apply_diff = apply_diff + + # Chain ourselves in front of the chart's frame-applied callback so the + # backpressure ack still clears `_waiting` on the chart. We record the + # Dart→Python ack timestamp here, which pairs with the send timestamp + # captured in `record_send` to give the dart-side decode + paint cost. + chart_ack = canvas._on_frame_applied + + def on_ack() -> None: + stats.record_ack() + if chart_ack is not None: + chart_ack() + + canvas.set_on_frame_applied(on_ack) + + # Background task: refresh the labels at ~4 Hz so speed/fps decay + # visibly when traffic stops and stay readable during fast drags + # (vs. updating once per frame, which thrashes the layout). + async def refresh_loop() -> None: + while True: + speed, fps = stats.speed_and_fps() + avg_full_text.value = ( + f"avg full: {_human_bytes(stats.avg_full)} (n={stats.full_count})" + ) + avg_diff_text.value = ( + f"avg diff: {_human_bytes(stats.avg_diff)} (n={stats.diff_count})" + ) + total_text.value = f"total: {_human_bytes(stats.bytes_total)}" + speed_text.value = f"speed: {_human_bytes(speed)}/s" + fps_text.value = f"fps: {fps:.1f}" + dart_text.value = f"dart: {stats.dart_avg_ms():.1f} ms" + mpl_text.value = f"mpl: {stats.mpl_avg_ms():.1f} ms" + page.update() + await asyncio.sleep(0.25) + + asyncio.create_task(refresh_loop()) + if __name__ == "__main__": ft.run(main) diff --git a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py index ed31e64403..73c49f2992 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py @@ -131,6 +131,10 @@ def build(self): on_resize=self._on_canvas_resize, expand=True, ) + # Hook the Dart-side frame-applied ack so we only clear `_waiting` + # after the frame actually rendered. Without this gate, interactive + # drags pile up frames in Dart's queue and replay them in a burst. + self.mpl_canvas.set_on_frame_applied(self._on_frame_applied) # Rubberband (zoom selection) overlay drawn on top of the chart image. self._rubberband = ft.Container( visible=False, @@ -419,6 +423,17 @@ def download(self, format) -> bytes: self.figure.savefig(buff, format=format, dpi=self.figure.dpi * self.__dpr) return buff.getvalue() + def _on_frame_applied(self) -> None: + """ + Called from the canvas when the Dart side finishes rendering a + frame. Clearing `_waiting` here (rather than immediately after + sending the frame) gates `send_message({"type": "draw"})` so + matplotlib doesn't generate the next frame until the previous one + has actually painted. Without this, interactive drags pile up + frames in the Dart-side queue and play back in a burst. + """ + self._waiting = False + async def _receive_loop(self): """ Consume backend messages and apply canvas/state updates. @@ -435,15 +450,16 @@ async def _receive_loop(self): assert isinstance(content, (bytes, bytearray)) logger.debug(f"receive_binary({len(content)})") # Hand the frame to the client widget — full PNG replaces the - # backbuffer, diff PNG composites onto it. Awaiting naturally - # rate-limits this loop to the client's processing speed and - # yields the asyncio loop for incoming events. + # backbuffer, diff PNG composites onto it. `_waiting` is + # cleared in `_on_frame_applied` when the Dart side acks + # that it actually rendered the frame, not here — otherwise + # interactive drags push frames faster than the renderer + # can keep up and the queue grows unbounded. if self.__image_mode == "full": - await self.mpl_canvas.apply_full(bytes(content)) + self.mpl_canvas.apply_full(bytes(content)) else: - await self.mpl_canvas.apply_diff(bytes(content)) + self.mpl_canvas.apply_diff(bytes(content)) self.img_count += 1 - self._waiting = False else: logger.debug(f"receive_json({content})") if content["type"] == "image_mode": diff --git a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py index 174167a404..c371824f68 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py @@ -1,5 +1,6 @@ +import contextlib from dataclasses import dataclass, field -from typing import Optional +from typing import Callable, Optional import flet as ft @@ -26,10 +27,24 @@ class MatplotlibChartCanvas(ft.LayoutControl): Receives full and incremental "diff" PNG frames and composites them in CPU memory, holding at most one decoded image for display at a time. - Avoids the per-frame `Picture.toImage` allocations that the generic - `flet.canvas.Canvas` capture path uses, which on Flutter web - (CanvasKit/WASM) accumulate and aren't promptly reclaimed by the JS GC - during animation, causing browser memory growth. + Frames flow over a dedicated [ft.DataChannel] rather than the regular + Flet protocol, so the PNG bytes skip MsgPack encode/decode and travel + at memory-bandwidth-class speed in embedded native mode (per-channel + PythonBridge) and at near-bandwidth speed in web/dev modes (raw-byte + frames muxed over the protocol transport). + + Wire format on the data channel (one byte of opcode followed by the + PNG payload): + + | opcode | payload | meaning | + |--------|------------|--------------------------------------------| + | 0x01 | PNG bytes | apply_full — replace backdrop | + | 0x02 | PNG bytes | apply_diff — composite onto backbuffer | + | 0x03 | (empty) | clear — drop backdrop + backbuffer | + + The reverse-direction `resize` event (Dart → Python, small JSON-shaped + payload) stays on the existing Flet protocol channel — no reason to + move tiny control events off it. """ resize_interval: ft.Number = 10 @@ -42,16 +57,68 @@ class MatplotlibChartCanvas(ft.LayoutControl): Called when the size of this canvas has changed. """ - async def apply_full(self, image_bytes: bytes) -> None: + on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]] = None + """ + Framework hook — Dart fires this when it opens the data channel during + `initState`. The default handler captures the channel for use by + apply_full / apply_diff / clear. Override only if you need to do + something extra at attach-time. + """ + + def init(self) -> None: + # `init` is the @ft.control post-construct lifecycle hook (runs + # before `did_mount`). Wire up the default channel-capture handler. + self._channel: Optional[ft.DataChannel] = None + # Backpressure ack callback — invoked when Dart finishes applying + # a frame on its end. Producer-side widgets (e.g. MatplotlibChart) + # set this to gate the next frame so the Dart-side queue stays + # bounded under interactive load. + self._on_frame_applied: Optional[Callable[[], None]] = None + if self.on_data_channel_open is None: + self.on_data_channel_open = self._capture_channel + + def _capture_channel(self, e: ft.DataChannelOpenEvent) -> None: + # Single-channel widget; no need to dispatch on e.channel_name. + self._channel = self.get_data_channel(e.channel_id) + self._channel.on_bytes(self._on_dart_message) + + def _on_dart_message(self, payload: bytes) -> None: + # Wire format on the reverse direction (Dart → Python): + # [0xFF] — frame_applied ack. Sent by Dart after each apply_full / + # apply_diff / clear completes on its end. Restores the + # round-trip backpressure that `_invoke_method` used to + # provide implicitly. + if not payload: + return + if payload[0] == 0xFF: + cb = self._on_frame_applied + if cb is not None: + with contextlib.suppress(Exception): + cb() + + def set_on_frame_applied(self, cb: Optional[Callable[[], None]]) -> None: + """Register a callback invoked when Dart finishes applying a frame. + + Producer widgets use this to gate frame emission — e.g. matplotlib + clears its `_waiting` flag here so the next `draw` message from + the figure is honored. Without this gate, the producer would push + frames into the Dart-side queue faster than they're rendered, + causing the UI to hog and then replay buffered frames in a burst. + """ + self._on_frame_applied = cb + + def apply_full(self, image_bytes: bytes) -> None: """ Replace the current displayed image with a full PNG frame. Args: image_bytes: PNG bytes of the complete frame. """ - await self._invoke_method("apply_full", arguments={"bytes": image_bytes}) + if self._channel is None: + return + self._channel.send(b"\x01" + image_bytes) - async def apply_diff(self, image_bytes: bytes) -> None: + def apply_diff(self, image_bytes: bytes) -> None: """ Composite an incremental "diff" PNG frame onto the current image. @@ -63,10 +130,14 @@ async def apply_diff(self, image_bytes: bytes) -> None: Args: image_bytes: PNG bytes of the diff frame. """ - await self._invoke_method("apply_diff", arguments={"bytes": image_bytes}) + if self._channel is None: + return + self._channel.send(b"\x02" + image_bytes) - async def clear(self) -> None: + def clear(self) -> None: """ Clear the displayed image and discard the backbuffer. """ - await self._invoke_method("clear") + if self._channel is None: + return + self._channel.send(b"\x03") diff --git a/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/matplotlib_chart_canvas.dart b/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/matplotlib_chart_canvas.dart index 2970c752f0..a44ef3e3ef 100644 --- a/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/matplotlib_chart_canvas.dart +++ b/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/matplotlib_chart_canvas.dart @@ -35,22 +35,9 @@ class MatplotlibChartCanvasControl extends StatefulWidget { } // --------------------------------------------------------------------------- -// Shared helpers +// Shared base // --------------------------------------------------------------------------- -Uint8List _extractBytes(dynamic args) { - final v = args is Map ? args["bytes"] : args; - if (v is Uint8List) return v; - if (v is ByteData) { - return v.buffer.asUint8List(v.offsetInBytes, v.lengthInBytes); - } - if (v is List) return Uint8List.fromList(v); - if (v is List && v.every((e) => e is int)) { - return Uint8List.fromList(v.cast()); - } - throw ArgumentError("Expected bytes for image data, got ${v.runtimeType}"); -} - abstract class _MatplotlibChartCanvasStateBase extends State { // Serialize concurrent apply_full / apply_diff calls so backdrop mutations @@ -60,15 +47,30 @@ abstract class _MatplotlibChartCanvasStateBase Size _lastSize = Size.zero; int _lastResize = DateTime.now().millisecondsSinceEpoch; + DataChannel? _channel; + StreamSubscription? _channelSub; + @override - void initState() { - super.initState(); - widget.control.addInvokeMethodListener(_invokeMethod); + void didChangeDependencies() { + super.didChangeDependencies(); + // Open the data channel lazily on first dependency lookup — we need + // BuildContext to reach FletBackend, which isn't available in initState. + if (_channel != null) return; + _channel = FletBackend.of(context).openDataChannel(); + _channelSub = _channel!.messages.listen(_onChannelFrame); + // Announce the channel to Python via the standard convention event. + widget.control.triggerEvent("data_channel_open", { + "channel_name": "frames", + "channel_id": _channel!.id, + }); } @override void dispose() { - widget.control.removeInvokeMethodListener(_invokeMethod); + _channelSub?.cancel(); + _channelSub = null; + _channel?.close(); + _channel = null; disposeResources(); super.dispose(); } @@ -81,22 +83,42 @@ abstract class _MatplotlibChartCanvasStateBase Future clearAll(); CustomPainter buildPainter(); - Future _invokeMethod(String name, dynamic args) async { - switch (name) { - case "apply_full": - await _enqueue(() => applyFull(_extractBytes(args))); - return; - case "apply_diff": - await _enqueue(() => applyDiff(_extractBytes(args))); - return; - case "clear": - await _enqueue(clearAll); - return; + // 1-byte ack sent back to Python after each apply completes. Restores + // round-trip backpressure: matplotlib's producer side keeps `_waiting` + // set until this ack arrives, so frames don't pile up in the Dart-side + // queue during interactive drags. + static final Uint8List _frameAppliedAck = Uint8List.fromList([0xFF]); + + /// Inbound DataChannel frame. Wire format: + /// [0x01][PNG bytes] → apply_full + /// [0x02][PNG bytes] → apply_diff + /// [0x03] → clear + void _onChannelFrame(Uint8List bytes) { + if (bytes.isEmpty) return; + // Zero-copy slice of the same underlying buffer. + final payload = Uint8List.sublistView(bytes, 1); + switch (bytes[0]) { + case 0x01: + _enqueueAndAck(() => applyFull(payload)); + break; + case 0x02: + _enqueueAndAck(() => applyDiff(payload)); + break; + case 0x03: + _enqueueAndAck(clearAll); + break; default: - throw Exception("Unknown MatplotlibChartCanvas method: $name"); + debugPrint( + "MatplotlibChartCanvas: unknown data-channel opcode 0x${bytes[0].toRadixString(16)}"); } } + void _enqueueAndAck(Future Function() task) { + _enqueue(task).whenComplete(() { + _channel?.send(_frameAppliedAck); + }); + } + Future _enqueue(Future Function() task) { final prev = _applyChain ?? Future.value(); final next = prev.then((_) => task()); From ef0c1fc82238cf6b9f285467fbd82a05ed610c83 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:40:05 -0700 Subject: [PATCH 10/47] refactor(build): split native FFI runtime out of main.dart for web compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `flet build web` was failing to compile with errors like "Type 'Pointer' not found" because the build template's `main.dart` unconditionally imported `package:serious_python/bridge.dart` and `package:serious_python/serious_python.dart`, both of which transitively pull in `dart:ffi` types via `package:serious_python_platform_interface`. `dart:ffi` isn't available in the web compile target. Extract everything that touches `serious_python` into a separate `native_runtime.dart`: * `initBridges(envVars) → pageUrl` — creates the protocol + exit PythonBridge instances and stamps env vars. * `channelBuilder`, `dataChannelFactory` getters for the embedded PythonBridge-backed transports. * `runPython(...)` — wraps `SeriousPython.runProgram` + the exit-bridge listener. * `extractAppAssets(...)` — wraps `extractAssetZip`. * The `_DartBridgeBackendChannel`, `_PythonBridgeDataChannel`, and `_PythonBridgeDataChannelFactory` impls. `main.dart` now uses a conditional import: import 'native_runtime_stub.dart' if (dart.library.ffi) 'native_runtime.dart' as nrt; On web, the stub (`native_runtime_stub.dart`) is selected — every entry point either returns null or throws `UnsupportedError`, and is guarded behind `kIsWeb` so the stub is never reached at runtime. The result: `flet build web` compiles cleanly without `dart:ffi` ever entering the compile graph. No behavior change on native (mobile/desktop) builds — they pick up the real `native_runtime.dart` via the conditional and execute the same code that lived in `main.dart` before. --- .../{{cookiecutter.out_dir}}/lib/main.dart | 206 +++------------ .../lib/native_runtime.dart | 240 ++++++++++++++++++ .../lib/native_runtime_stub.dart | 28 ++ 3 files changed, 302 insertions(+), 172 deletions(-) create mode 100644 sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart create mode 100644 sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart index de0207a67c..2a124d81bd 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart @@ -1,22 +1,25 @@ import 'dart:async'; import 'dart:io'; -import 'dart:typed_data'; import 'dart:ui'; import 'package:flet/flet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:msgpack_dart/msgpack_dart.dart' as msgpack; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:serious_python/bridge.dart'; -import 'package:serious_python/serious_python.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:window_manager/window_manager.dart'; -import "python.dart"; +// `dart:ffi` (and therefore `package:serious_python/bridge.dart`, +// `package:serious_python/serious_python.dart`) isn't available on web. +// The conditional import below loads the real PythonBridge-backed runtime +// on platforms where FFI exists; on web it resolves to a stub of the same +// shape that just throws if invoked. `main` guards every use with +// `kIsWeb` so the stub is never actually called. +import 'native_runtime_stub.dart' + if (dart.library.ffi) 'native_runtime.dart' as nrt; {% for dep in cookiecutter.flutter.dependencies %} import 'package:{{ dep }}/{{ dep }}.dart' as {{ dep }}; @@ -70,14 +73,6 @@ String assetsDir = ""; String appDir = ""; Map environmentVariables = Map.from(Platform.environment); -// In production (embedded) mode the Flet protocol flows over an in-process -// PythonBridge — no socket file, no TCP. `_exitBridge` is a separate bridge -// dedicated to Python's exit-code transmission (replaces the legacy stdout- -// callback socket). Both are null in web + developer modes where Python is -// either remote or in a separate process. -PythonBridge? _bridge; -PythonBridge? _exitBridge; - void main(List args) async { FletDeepLinkingBootstrap.install(); @@ -97,19 +92,18 @@ void main(List args) async { future: prepareApp(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { - // In production mode prepareApp() created _bridge; wire a + // In production mode prepareApp() created native bridges; wire a // PythonBridge-backed channel so FletApp talks to the embedded // Python over the in-process FFI transport. In web + dev modes - // _bridge is null and FletApp falls back to its URL-scheme factory - // (websocket / TCP / UDS). - final channelBuilder = _bridge == null - ? null - : ({ - required FletBackendChannelOnMessageCallback onMessage, - required FletBackendChannelOnDisconnectCallback onDisconnect, - }) => - _DartBridgeBackendChannel(_bridge!, - onMessage: onMessage, onDisconnect: onDisconnect); + // the bridges are absent and FletApp falls back to its URL-scheme + // factory (websocket / TCP / UDS). + final channelBuilder = nrt.channelBuilder; + // Native (non-web): high-throughput byte channels for widgets get + // their own dedicated PythonBridge each. Web (Pyodide) leaves this + // null so FletBackend's built-in ProtocolMuxedDataChannelFactory + // muxes channel bytes over the postMessage transport with + // Transferable ArrayBuffer for zero-copy. + final dataChannelFactory = nrt.dataChannelFactory; // OK - start Python program return kIsWeb || (isDesktopPlatform() && _args.isNotEmpty) ? FletApp( @@ -137,6 +131,7 @@ void main(List args) async { showAppStartupScreen: showAppStartupScreen, appStartupScreenMessage: appStartupScreenMessage, channelBuilder: channelBuilder, + dataChannelFactory: dataChannelFactory, extensions: extensions); } }); @@ -189,7 +184,7 @@ Future prepareApp() async { } else { // production mode // extract app from asset - appDir = await extractAssetZip(assetPath, checkHash: true); + appDir = await nrt.extractAppAssets(assetPath, checkHash: true); // set current directory to app path Directory.current = appDir; @@ -223,19 +218,14 @@ Future prepareApp() async { "FLET_PLATFORM", () => defaultTargetPlatform.name.toLowerCase()); // In production we use the in-process dart_bridge FFI transport (no UDS, - // no TCP — Python and Flutter share the process). Two bridges: - // _bridge — the Flet MsgPack protocol channel (Dart ↔ Python). - // _exitBridge — Python-only outbound channel carrying the exit code - // when `sys.exit(code)` is called inside the embedded - // interpreter. Replaces the legacy stdout-callback - // socket. - _bridge = PythonBridge(); - _exitBridge = PythonBridge(); - pageUrl = "dartbridge://${_bridge!.port}"; - environmentVariables.putIfAbsent( - "FLET_DART_BRIDGE_PORT", () => _bridge!.port.toString()); - environmentVariables.putIfAbsent( - "FLET_DART_BRIDGE_EXIT_PORT", () => _exitBridge!.port.toString()); + // no TCP — Python and Flutter share the process). Two bridges, both + // owned by `native_runtime.dart`: + // protocol bridge — the Flet MsgPack channel (Dart ↔ Python). + // exit bridge — Python-only outbound channel carrying the exit + // code when `sys.exit(code)` is called inside the + // embedded interpreter. Replaces the legacy + // stdout-callback socket. + pageUrl = nrt.initBridges(environmentVariables); } if (!kIsWeb && assetsDir.isNotEmpty) { @@ -246,141 +236,13 @@ Future prepareApp() async { } Future runPythonApp(List args) async { - var argvItems = args.map((a) => "\"${a.replaceAll('"', '\\"')}\""); - var argv = "[${argvItems.isNotEmpty ? argvItems.join(',') : '""'}]"; - var script = pythonScript - .replaceAll("{outLogFilename}", outLogFilename.replaceAll("\\", "\\\\")) - .replaceAll('{module_name}', pythonModuleName) - .replaceAll('{argv}', argv); - - var completer = Completer(); - - // Subscribe to the exit-code bridge. Python's `sys.exit(code)` is patched - // (in python.dart) to encode `code` as raw UTF-8 bytes and post them via - // `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`. We don't need - // a streaming codec here — the channel only ever carries a single short - // payload, then Python tears down. - StringBuffer pythonExitBuf = StringBuffer(); - StreamSubscription? exitSub; - - void onExitSignal() async { - await exitSub?.cancel(); - int exitCode = int.tryParse(pythonExitBuf.toString().trim()) ?? 0; - if (exitCode == errorExitCode) { - var out = ""; - if (await File(outLogFilename).exists()) { - out = await File(outLogFilename).readAsString(); - } - completer.complete(out); - } else { - exit(exitCode); - } - } - - exitSub = _exitBridge!.messages.listen( - (data) { - pythonExitBuf.write(String.fromCharCodes(data)); - // One frame is always the full code on this channel — act on it. - onExitSignal(); - }, - onError: (error) { - debugPrint('Exit bridge error: $error'); - onExitSignal(); - }, - onDone: onExitSignal, - cancelOnError: false, + return nrt.runPython( + moduleName: pythonModuleName, + appDir: appDir, + outLogFilename: outLogFilename, + environmentVariables: environmentVariables, + args: args, ); - - // run python async - SeriousPython.runProgram(path.join(appDir, "$pythonModuleName.pyc"), - script: script, environmentVariables: environmentVariables); - - // wait for Python to signal exit - return completer.future; -} - -/// `FletBackendChannel` implementation backed by a [PythonBridge]. Bytes -/// flow Dart↔Python entirely in-process; no Unix socket, no kernel context -/// switch. The wire format is the same MsgPack-framed protocol the existing -/// socket-based `FletSocketBackendChannel` speaks. -class _DartBridgeBackendChannel implements FletBackendChannel { - _DartBridgeBackendChannel(this._bridge, - {required FletBackendChannelOnMessageCallback onMessage, - required FletBackendChannelOnDisconnectCallback onDisconnect}) - : _onMessage = onMessage, - _onDisconnect = onDisconnect, - _deserializer = - StreamingMsgpackDeserializer(extDecoder: FletMsgpackDecoder()); - - final PythonBridge _bridge; - final FletBackendChannelOnMessageCallback _onMessage; - final FletBackendChannelOnDisconnectCallback _onDisconnect; - final StreamingMsgpackDeserializer _deserializer; - StreamSubscription? _subscription; - - @override - Future connect() async { - _subscription = _bridge.messages.listen( - _onBytes, - onError: (error, stack) { - debugPrint("PythonBridge stream error: $error"); - _onDisconnect(); - }, - onDone: () { - debugPrint("PythonBridge stream closed."); - _onDisconnect(); - }, - cancelOnError: false, - ); - } - - void _onBytes(Uint8List bytes) { - _deserializer.addChunk(bytes); - final frames = _deserializer.decodeMessages(); - for (final frame in frames) { - _onMessage(Message.fromList(frame)); - } - } - - @override - void send(Message message) { - final encoded = Uint8List.fromList( - msgpack.serialize(message.toList(), extEncoder: FletMsgpackEncoder())); - // Retry loop covers the brief startup window where Python hasn't yet - // called `dart_bridge.set_enqueue_handler_func` — bridge.send returns - // false in that case. Once Flet's app.py registers the handler (which - // happens before `runpy.run_module` is dispatched), bridge.send returns - // true synchronously. - if (_bridge.send(encoded)) return; - _retrySend(encoded); - } - - void _retrySend(Uint8List encoded) { - const interval = Duration(milliseconds: 50); - const deadline = Duration(seconds: 30); - final start = DateTime.now(); - Timer.periodic(interval, (timer) { - if (_bridge.send(encoded)) { - timer.cancel(); - } else if (DateTime.now().difference(start) > deadline) { - timer.cancel(); - debugPrint( - "PythonBridge send timed out: Python handler never registered."); - } - }); - } - - @override - bool get isLocalConnection => true; - - @override - int get defaultReconnectIntervalMs => 0; - - @override - void disconnect() { - _subscription?.cancel(); - _subscription = null; - } } class ErrorScreen extends StatelessWidget { diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart new file mode 100644 index 0000000000..6e3ef30475 --- /dev/null +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart @@ -0,0 +1,240 @@ +// Native-only embedded-Python runtime: PythonBridge transport, +// SeriousPython.runProgram, exit-bridge wiring. This file is selected by +// `main.dart`'s conditional import on platforms where `dart:ffi` exists +// (mobile, desktop). On web, `native_runtime_stub.dart` is used instead. +// +// Splitting this out is what lets `flet build web` compile — package +// `serious_python` pulls in `dart:ffi` / `Pointer<…>` types via +// `package:serious_python_platform_interface`, which are not part of the +// web SDK. Importing the module unconditionally from `main.dart` makes +// the web build fail with "Type 'Pointer' not found" et al., regardless +// of `kIsWeb` runtime gates. + +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flet/flet.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as path; +import 'package:serious_python/bridge.dart'; +import 'package:serious_python/serious_python.dart'; + +import 'python.dart'; + +// In production (embedded) mode the Flet protocol flows over an in-process +// PythonBridge — no socket file, no TCP. `_exitBridge` is a separate bridge +// dedicated to Python's exit-code transmission (replaces the legacy stdout- +// callback socket). Both stay null until [initBridges] runs. +PythonBridge? _bridge; +PythonBridge? _exitBridge; + +/// Allocate the protocol + exit bridges, stamp the required env vars onto +/// [envVars], and return the `dartbridge://` page URL. +String initBridges(Map envVars) { + _bridge = PythonBridge(); + _exitBridge = PythonBridge(); + envVars.putIfAbsent("FLET_DART_BRIDGE_PORT", () => _bridge!.port.toString()); + envVars.putIfAbsent( + "FLET_DART_BRIDGE_EXIT_PORT", + () => _exitBridge!.port.toString(), + ); + return "dartbridge://${_bridge!.port}"; +} + +bool get bridgesActive => _bridge != null; + +/// Extract the bundled app.zip into a fresh app directory and return its +/// path. Wraps `serious_python.extractAssetZip` so main.dart doesn't have +/// to import the FFI-touching package directly. +Future extractAppAssets(String assetPath, {bool checkHash = false}) => + extractAssetZip(assetPath, checkHash: checkHash); + +/// FletApp's `channelBuilder` for the embedded-Python protocol — wraps the +/// in-process [PythonBridge] in a [FletBackendChannel]. Returns null until +/// [initBridges] has run. +FletBackendChannelBuilder? get channelBuilder => _bridge == null + ? null + : ({ + required FletBackendChannelOnPacketCallback onPacket, + required FletBackendChannelOnDisconnectCallback onDisconnect, + }) => _DartBridgeBackendChannel( + _bridge!, + onPacket: onPacket, + onDisconnect: onDisconnect, + ); + +/// FletApp's `dataChannelFactory` for high-throughput byte channels — +/// each `open()` mints a fresh [PythonBridge] (dedicated native port). +DataChannelFactory? get dataChannelFactory => _PythonBridgeDataChannelFactory(); + +/// Boot the embedded interpreter and wait for it to exit. Returns the +/// captured console output on error exit; calls `exit(code)` directly on +/// normal exit codes. +Future runPython({ + required String moduleName, + required String appDir, + required String outLogFilename, + required Map environmentVariables, + required List args, +}) async { + var argvItems = args.map((a) => "\"${a.replaceAll('"', '\\"')}\""); + var argv = "[${argvItems.isNotEmpty ? argvItems.join(',') : '""'}]"; + var script = pythonScript + .replaceAll("{outLogFilename}", outLogFilename.replaceAll("\\", "\\\\")) + .replaceAll('{module_name}', moduleName) + .replaceAll('{argv}', argv); + + var completer = Completer(); + + // Subscribe to the exit-code bridge. Python's `sys.exit(code)` is patched + // (in python.dart) to encode `code` as raw UTF-8 bytes and post them via + // `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`. We don't need + // a streaming codec here — the channel only ever carries a single short + // payload, then Python tears down. + StringBuffer pythonExitBuf = StringBuffer(); + StreamSubscription? exitSub; + + void onExitSignal() async { + await exitSub?.cancel(); + int exitCode = int.tryParse(pythonExitBuf.toString().trim()) ?? 0; + if (exitCode == errorExitCode) { + var out = ""; + if (await File(outLogFilename).exists()) { + out = await File(outLogFilename).readAsString(); + } + completer.complete(out); + } else { + exit(exitCode); + } + } + + exitSub = _exitBridge!.messages.listen( + (data) { + pythonExitBuf.write(String.fromCharCodes(data)); + onExitSignal(); + }, + onError: (error) { + debugPrint('Exit bridge error: $error'); + onExitSignal(); + }, + onDone: onExitSignal, + cancelOnError: false, + ); + + SeriousPython.runProgram( + path.join(appDir, "$moduleName.pyc"), + script: script, + environmentVariables: environmentVariables, + ); + + return completer.future; +} + +/// `FletBackendChannel` implementation backed by a [PythonBridge]. Bytes +/// flow Dart↔Python entirely in-process; no Unix socket, no kernel context +/// switch. Each PythonBridge `send` is one complete packet on the wire — +/// `[type:u8][payload]`. No framing layer needed (the bridge preserves +/// message boundaries). +class _DartBridgeBackendChannel implements FletBackendChannel { + _DartBridgeBackendChannel( + this._bridge, { + required FletBackendChannelOnPacketCallback onPacket, + required FletBackendChannelOnDisconnectCallback onDisconnect, + }) : _onPacket = onPacket, + _onDisconnect = onDisconnect; + + final PythonBridge _bridge; + final FletBackendChannelOnPacketCallback _onPacket; + final FletBackendChannelOnDisconnectCallback _onDisconnect; + StreamSubscription? _subscription; + + @override + Future connect() async { + _subscription = _bridge.messages.listen( + _onPacket, + onError: (error, stack) { + debugPrint("PythonBridge stream error: $error"); + _onDisconnect(); + }, + onDone: () { + debugPrint("PythonBridge stream closed."); + _onDisconnect(); + }, + cancelOnError: false, + ); + } + + @override + void send(Uint8List packet) { + // Retry loop covers the brief startup window where Python hasn't yet + // called `dart_bridge.set_enqueue_handler_func` — bridge.send returns + // false in that case. Once Flet's app.py registers the handler (which + // happens before `runpy.run_module` is dispatched), bridge.send returns + // true synchronously. + if (_bridge.send(packet)) return; + _retrySend(packet); + } + + void _retrySend(Uint8List packet) { + const interval = Duration(milliseconds: 50); + const deadline = Duration(seconds: 30); + final start = DateTime.now(); + Timer.periodic(interval, (timer) { + if (_bridge.send(packet)) { + timer.cancel(); + } else if (DateTime.now().difference(start) > deadline) { + timer.cancel(); + debugPrint( + "PythonBridge send timed out: Python handler never registered.", + ); + } + }); + } + + @override + bool get isLocalConnection => true; + + @override + int get defaultReconnectIntervalMs => 0; + + @override + void disconnect() { + _subscription?.cancel(); + _subscription = null; + } +} + +/// [DataChannel] backed by a dedicated [PythonBridge] — fast path for +/// embedded native mode. Each open() mints a fresh bridge with its own +/// native port; the bridge's `port` becomes the channel id we propagate +/// to Python (via the widget's `data_channel_open` event). +class _PythonBridgeDataChannel implements DataChannel { + _PythonBridgeDataChannel(this._bridge); + final PythonBridge _bridge; + bool _closed = false; + + @override + int get id => _bridge.port; + + @override + Stream get messages => _bridge.messages; + + @override + bool send(Uint8List bytes) { + if (_closed) return false; + return _bridge.send(bytes); + } + + @override + void close() { + if (_closed) return; + _closed = true; + _bridge.close(); + } +} + +class _PythonBridgeDataChannelFactory implements DataChannelFactory { + @override + DataChannel open() => _PythonBridgeDataChannel(PythonBridge()); +} diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart new file mode 100644 index 0000000000..bbb462169d --- /dev/null +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart @@ -0,0 +1,28 @@ +// Web stub for `native_runtime.dart`. Selected by `main.dart`'s conditional +// import on platforms where `dart:ffi` is NOT available (web). +// +// All callers must guard with `kIsWeb` so the real native_runtime entry +// points are never invoked on web — but the stubs still need to exist for +// the file to compile. + +import 'package:flet/flet.dart'; + +String initBridges(Map envVars) => + throw UnsupportedError("Native bridges not available on web"); + +bool get bridgesActive => false; + +Future extractAppAssets(String assetPath, {bool checkHash = false}) => + throw UnsupportedError("Asset extraction not available on web"); + +FletBackendChannelBuilder? get channelBuilder => null; + +DataChannelFactory? get dataChannelFactory => null; + +Future runPython({ + required String moduleName, + required String appDir, + required String outLogFilename, + required Map environmentVariables, + required List args, +}) => throw UnsupportedError("Native runtime not available on web"); From eb00f4cb2e67b5678a98842f62dc13d51c69df47 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:40:36 -0700 Subject: [PATCH 11/47] fix(web): switch Pyodide worker to module type + pyodide.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pyodide >= 0.29 (and 314.0.0, the Python 3.14 line) throws "Classic web workers are not supported" inside any worker where `importScripts` is callable. python-worker.js was spawned as a classic worker, so booting the Python 3.13 / 3.14 lines surfaced a hard error before any user code ran. Switch to module workers across both the flet web client and the `flet build` template: * `new Worker(url, { type: "module" })` — module workers don't expose `importScripts`, so Pyodide's check passes. * `importScripts(pyodideUrl)` → `const { loadPyodide } = await import(pyodideUrl)` — the dynamic-import form module workers must use. * All `pyodideUrl` defaults flip from `pyodide.js` to `pyodide.mjs` — the ES-module variant has the named export the dynamic import expects. URL injection paths: * `flet publish` / `flet run --web` go through `patch_index.py`, which now injects `pyodide.mjs` URLs (both CDN and `--no-cdn` branches). * `flet build web` uses the cookiecutter template's index.html, which was hardcoded at `/pyodide/pyodide.js` regardless of `--no-cdn`. Replace with a Jinja conditional that honors `cookiecutter.no_cdn` and uses the new `cookiecutter.pyodide_version` variable for the jsdelivr CDN URL. `build_base.py` populates `pyodide_version` from the resolved `python_release.pyodide`. Forward-compatible across all three supported Pyodide lines: 0.27.7 (Python 3.12), 0.29.4 (Python 3.13), 314.0.0 (Python 3.14). Older lines accept module workers too; 0.29+ require them. --- client/web/index.html | 6 +++++- client/web/python-worker.js | 9 ++++++++- client/web/python.js | 19 ++++++++++++++++--- .../src/flet_cli/commands/build_base.py | 4 ++++ .../flet-web/src/flet_web/patch_index.py | 11 +++++++++-- sdk/python/templates/build/cookiecutter.json | 1 + .../{{cookiecutter.out_dir}}/web/index.html | 11 ++++++++++- .../web/python-worker.js | 9 ++++++++- .../{{cookiecutter.out_dir}}/web/python.js | 15 +++++++++++++-- 9 files changed, 74 insertions(+), 11 deletions(-) diff --git a/client/web/index.html b/client/web/index.html index 2e622c51af..0e630c5821 100644 --- a/client/web/index.html +++ b/client/web/index.html @@ -30,7 +30,11 @@ assetBase: "/", routeUrlStrategy: "path", canvasKitBaseUrl: "/canvaskit/", - pyodideUrl: "/pyodide/pyodide.js", + // Default fallback only — `patch_index.py` overrides this with the + // resolved per-build URL (CDN or local) at deploy time. The `.mjs` + // suffix is required because python-worker.js is a module worker + // that loads the runtime via dynamic `import()`. See client/web/python.js. + pyodideUrl: "/pyodide/pyodide.mjs", webRenderer: "auto", fontFallbackBaseUrl: "assets/fonts/", // for Noto Emoji, use Google CDN appPackageUrl: "app.tar.gz" diff --git a/client/web/python-worker.js b/client/web/python-worker.js index 40c6083afc..31350fb9a7 100644 --- a/client/web/python-worker.js +++ b/client/web/python-worker.js @@ -38,7 +38,14 @@ self.sendPythonOutput = function (text, isStderr) { self.initPyodide = async function () { try { - importScripts(self.pyodideUrl); + // Module-worker load path. `importScripts` only exists in classic + // workers — Pyodide >= 0.29 actively refuses to load there ("Classic + // web workers are not supported"). We're a module worker, so the + // runtime ships as `pyodide.mjs` and exposes `loadPyodide` via ESM + // exports. The CDN/jsdelivr fallback URL set by patch_index.py also + // points at the .mjs variant. + const pyodideModule = await import(self.pyodideUrl); + const loadPyodide = pyodideModule.loadPyodide || self.loadPyodide; self.pyodide = await loadPyodide({ stdout: (text) => self.sendPythonOutput(text, false), stderr: (text) => self.sendPythonOutput(text, true), diff --git a/client/web/python.js b/client/web/python.js index d44d66d1d2..1e44e27b2e 100644 --- a/client/web/python.js +++ b/client/web/python.js @@ -13,8 +13,17 @@ globalThis.jsConnect = async function (appId, args, dartOnMessage) { }; console.log(`Starting up Python worker: ${appId}, args: ${args}`); _apps[appId] = app; - app.worker = new Worker((flet.entrypointBaseUrl.endsWith("/") ? - flet.entrypointBaseUrl.slice(0, -1) : flet.entrypointBaseUrl) + "/python-worker.js"); + // Module worker (type: "module") is required by Pyodide >= 0.29 — the + // runtime throws "Classic web workers are not supported" inside any + // worker where `importScripts` is callable. Module workers don't have + // `importScripts`, so the check passes. Older Pyodide lines (0.27.x) + // accept module workers too, so this is forward-compatible across all + // supported Python versions (3.12 → Pyodide 0.27.7, 3.13 → 0.29.4, + // 3.14 → 314.0.0). + app.worker = new Worker( + (flet.entrypointBaseUrl.endsWith("/") ? + flet.entrypointBaseUrl.slice(0, -1) : flet.entrypointBaseUrl) + "/python-worker.js", + { type: "module" }); var error; app.worker.onmessage = (event) => { @@ -32,7 +41,11 @@ globalThis.jsConnect = async function (appId, args, dartOnMessage) { // initialize worker app.worker.postMessage({ - pyodideUrl: flet.pyodideUrl || "pyodide/pyodide.js", + // `.mjs` is the ES-module variant. python-worker.js (now a module + // worker) loads it via dynamic `import()`. The legacy `.js` + // variant relied on `importScripts`, which doesn't exist in a + // module worker. + pyodideUrl: flet.pyodideUrl || "pyodide/pyodide.mjs", args: args, documentUrl: _documentUrl, appPackageUrl: flet.appPackageUrl, diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index 66dd31cc1d..964e4b0dd4 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -1186,6 +1186,10 @@ def _xml_attr_value(v): "no_cdn": ( self.options.no_cdn or self.get_pyproject("tool.flet.web.cdn") == False # noqa: E712 ), + # Surface the resolved Pyodide release to the cookiecutter + # context so the web template's index.html can wire the + # correct jsdelivr URL when CDN mode is on. + "pyodide_version": self.python_release.pyodide, "base_url": f"/{base_url}/" if base_url else "/", "split_per_abi": split_per_abi, "project_name": project_name, diff --git a/sdk/python/packages/flet-web/src/flet_web/patch_index.py b/sdk/python/packages/flet-web/src/flet_web/patch_index.py index 4d3dbe7282..0e206cd5a3 100644 --- a/sdk/python/packages/flet-web/src/flet_web/patch_index.py +++ b/sdk/python/packages/flet-web/src/flet_web/patch_index.py @@ -65,12 +65,19 @@ def patch_index_html( # Pin the Pyodide runtime URL for this build. The web client used to fall # back to a hardcoded CDN URL when not in no-cdn mode; with multi-version # support that constant is gone, so we always inject the URL here. + # + # `.mjs` is the ES-module variant; python-worker.js (a module worker) + # loads it via dynamic `import()`. We can no longer use `.js` because + # Pyodide >= 0.29 throws "Classic web workers are not supported" inside + # any worker that has `importScripts` available — only classic workers + # have it. All supported Pyodide versions (0.27.7 / 0.29.4 / 314.0.0) + # ship `pyodide.mjs`. if pyodide_version: if no_cdn: - pyodide_url = "pyodide/pyodide.js" + pyodide_url = "pyodide/pyodide.mjs" else: pyodide_url = ( - f"https://cdn.jsdelivr.net/pyodide/v{pyodide_version}/full/pyodide.js" + f"https://cdn.jsdelivr.net/pyodide/v{pyodide_version}/full/pyodide.mjs" ) app_config.append(f'flet.pyodideUrl="{pyodide_url}";') diff --git a/sdk/python/templates/build/cookiecutter.json b/sdk/python/templates/build/cookiecutter.json index 405dfb8dfa..b67704878b 100644 --- a/sdk/python/templates/build/cookiecutter.json +++ b/sdk/python/templates/build/cookiecutter.json @@ -29,6 +29,7 @@ "split_per_abi": false, "no_cdn": false, "no_wasm": false, + "pyodide_version": "", "options": null, "pyproject": null, "_extensions": ["cookiecutter_extensions.FletExtension"] diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/index.html b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/index.html index 53bb5e3961..891ee2f47d 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/index.html +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/index.html @@ -29,7 +29,16 @@ assetBase: "{{ cookiecutter.base_url }}", routeUrlStrategy: "{{ cookiecutter.route_url_strategy }}", canvasKitBaseUrl: "/canvaskit/", - pyodideUrl: "/pyodide/pyodide.js", + // .mjs is required: python-worker.js is a module worker and loads + // the runtime via dynamic `import()`. Pyodide >= 0.29 also refuses + // to run in classic workers. Use the local copy that `flet build` + // drops into web/pyodide/ when --no-cdn is set; otherwise pull + // from jsdelivr so the build artifact can stay slim. + {% if cookiecutter.no_cdn == "True" %} + pyodideUrl: "/pyodide/pyodide.mjs", + {% else %} + pyodideUrl: "https://cdn.jsdelivr.net/pyodide/v{{ cookiecutter.pyodide_version }}/full/pyodide.mjs", + {% endif %} pythonModuleName: "{{ cookiecutter.python_module_name }}", webRenderer: "{{ cookiecutter.web_renderer }}", fontFallbackBaseUrl: "assets/fonts/", // for Noto Emoji, use Google CDN diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js index 40c6083afc..31350fb9a7 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js @@ -38,7 +38,14 @@ self.sendPythonOutput = function (text, isStderr) { self.initPyodide = async function () { try { - importScripts(self.pyodideUrl); + // Module-worker load path. `importScripts` only exists in classic + // workers — Pyodide >= 0.29 actively refuses to load there ("Classic + // web workers are not supported"). We're a module worker, so the + // runtime ships as `pyodide.mjs` and exposes `loadPyodide` via ESM + // exports. The CDN/jsdelivr fallback URL set by patch_index.py also + // points at the .mjs variant. + const pyodideModule = await import(self.pyodideUrl); + const loadPyodide = pyodideModule.loadPyodide || self.loadPyodide; self.pyodide = await loadPyodide({ stdout: (text) => self.sendPythonOutput(text, false), stderr: (text) => self.sendPythonOutput(text, true), diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python.js b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python.js index 138fcb3f2e..89e09e6366 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python.js +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python.js @@ -13,7 +13,14 @@ globalThis.jsConnect = async function(appId, args, dartOnMessage) { }; console.log(`Starting up Python worker: ${appId}, args: ${args}`); _apps[appId] = app; - app.worker = new Worker("python-worker.js"); + // Module worker (type: "module") is required by Pyodide >= 0.29 — the + // runtime throws "Classic web workers are not supported" inside any + // worker where `importScripts` is callable. Module workers don't have + // `importScripts`, so the check passes. Older Pyodide lines (0.27.x) + // accept module workers too, so this is forward-compatible across all + // supported Python versions (3.12 → Pyodide 0.27.7, 3.13 → 0.29.4, + // 3.14 → 314.0.0). + app.worker = new Worker("python-worker.js", { type: "module" }); var error; app.worker.onmessage = (event) => { @@ -31,7 +38,11 @@ globalThis.jsConnect = async function(appId, args, dartOnMessage) { // initialize worker app.worker.postMessage({ - pyodideUrl: flet.pyodideUrl || "pyodide/pyodide.js", + // `.mjs` is the ES-module variant. python-worker.js (now a module + // worker) loads it via dynamic `import()`. The legacy `.js` + // variant relied on `importScripts`, which doesn't exist in a + // module worker. + pyodideUrl: flet.pyodideUrl || "pyodide/pyodide.mjs", args: args, documentUrl: _documentUrl, appPackageUrl: flet.appPackageUrl, From d44225d3fd8101eca17a0d5aa4ab6e37a5a3fe67 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:40:51 -0700 Subject: [PATCH 12/47] docs(0.86.0): DataChannel + protocol framing breaking-change guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CHANGELOG: new features (DataChannel API), improvements (length-prefix framing + type-byte discriminator, StreamingMsgpackDeserializer removed), breaking changes (wire format on stream transports, mixed flet versions across `flet run` CLI and runtime no longer supported). * New breaking-changes guide `data-channel-protocol-upgrade.md` — migration notes for users with custom backends speaking the Flet protocol, plus a heads-up for anyone subclassing `MatplotlibChartCanvas` (the Dart-side `_invokeMethod` handler no longer fires). * Add the new guide to the 0.86.0 entry in the breaking-changes index. --- CHANGELOG.md | 3 + .../data-channel-protocol-upgrade.md | 111 ++++++++++++++++++ .../docs/updates/breaking-changes/index.md | 1 + 3 files changed, 115 insertions(+) create mode 100644 website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d4fedf2a9e..2672eeb9fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,20 @@ ### New features * Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from a central registry. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 2.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Add `ft.DataChannel`: dedicated byte channels for widgets that move bulk binary data (image frames, audio buffers, ML tensors) between Dart and Python, bypassing the MsgPack control protocol. The Dart side opens a channel via `FletBackend.of(context).openDataChannel()` and announces it to Python by firing a `data_channel_open` control event with `{channel_name, channel_id}`; the Python side declares `on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]]` and captures the channel via `self.get_data_channel(e.channel_id)`. Backed by a dedicated `PythonBridge` per channel in embedded native mode (4–7 GiB/s on M2 Pro) and by the default `ProtocolMuxedDataChannelFactory` in dev / web modes (raw-byte frames muxed over the active Flet protocol transport with a 1-byte type discriminator). Pyodide gets zero-copy outbound sends via `postMessage` Transferable ArrayBuffer. First consumer: `flet-charts` `MatplotlibChartCanvas`, migrated from `_invoke_method` PNG dispatch to a 1-byte-opcode data channel by @FeodorFitsner. ### Improvements * Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide//` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * `flet --version` now lists the supported Python versions newest first, each with its matching Pyodide release and a `default` / `pre-release` annotation where applicable, instead of the single static `Pyodide: …` line. The global `flet.version.pyodide_version` export is removed (the only external consumer was the CLI version output, now updated) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * `client/web/python.js` and the build template's `python.js` no longer hardcode `defaultPyodideUrl`. `patch_index.py` now injects `flet.pyodideUrl` per build (CDN URL by default, or the local `pyodide/pyodide.js` path under `--no-cdn`) so the runtime URL always tracks the resolved Pyodide release ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Stream-oriented Flet protocol transports (UDS / TCP used by `flet run` dev mode) now use length-prefixed framing instead of streaming `msgpack.Unpacker.feed`. Combined with a new 1-byte type discriminator at the head of every packet (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket, `dart_bridge` FFI, Pyodide `postMessage`). `StreamingMsgpackDeserializer` is removed from `package:flet`; each inbound packet is one complete MsgPack value, decoded one-shot via `msgpack.deserialize(bytes)` by @FeodorFitsner. ### Breaking changes * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * The `flet.version.pyodide_version` module attribute and the `PYODIDE_VERSION` constant are removed. Reach for `flet_cli.utils.python_versions.SUPPORTED_PYTHON_VERSIONS` if you need the per-version Pyodide mapping programmatically ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. ### Bug fixes diff --git a/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md b/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md new file mode 100644 index 0000000000..f0b41cff4c --- /dev/null +++ b/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md @@ -0,0 +1,111 @@ +--- +title: "Flet protocol framing upgraded for DataChannel support" +--- + +# Flet protocol framing upgraded for DataChannel support + +:::note +This guide is accurate as of Flet 0.86.0. Later releases might add new APIs or +additional migration paths. + +The [breaking changes and deprecations index](.) lists the guides created for each release. +::: + +## Summary + +Flet 0.86.0 introduces dedicated **data channels** for widgets that need to +move bulk binary data (image frames, audio buffers, ML tensors) between Dart +and Python without going through the MsgPack control protocol. + +Enabling this required a one-time upgrade of the Flet protocol's wire format: + +- **Stream-oriented transports** (`flet run` dev mode over UDS / TCP) now use + **4-byte little-endian length-prefixed framing**. The previous streaming + `msgpack.Unpacker.feed` decode is gone. +- **Every transport** (sockets, WebSocket, `dart_bridge` FFI, Pyodide + `postMessage`) now puts a **1-byte type discriminator** at the head of each + packet: + - `0x00` — MsgPack-encoded Flet control frame (widget state, events) — same + contents as before, just with a type byte in front. + - `0x01` — raw DataChannel frame (`[channel_id:u32 LE][payload]`). + +The new format is **not backwards-compatible**: a Flet 0.85 client cannot talk +to a Flet 0.86 server, and vice versa. The `flet-cli` dev server and the +in-process Python runtime were upgraded together in this release. + +## Background + +Flet's previous wire format on UDS / TCP relied on `msgpack.Unpacker.feed` to +re-assemble messages from arbitrary byte-stream chunks. That works for +MsgPack-only traffic but doesn't compose with a second logical stream of raw +bytes — you can't tell a partial MsgPack value from the start of a raw frame +without an out-of-band marker. + +Adding the 1-byte type byte + 4-byte length prefix solves this in the smallest +possible way: + +- Receivers read `[length][type][...]`, fan out by type byte, no streaming + decoder state to maintain. +- Message-oriented transports (WebSocket, `postMessage`, `dart_bridge`) drop + the length prefix since each message is already one packet; they keep the + type byte. +- Per-frame overhead is 5 bytes (message-oriented) or 9 bytes (stream-oriented) + — under 1% at any payload size that motivates a data channel. + +## Migration guide + +### Most users — nothing to change + +If you only use `flet build` artifacts or run `flet run` with the matching +`flet` package version (the default install pulls both at once), there's +nothing to do. The CLI and the runtime are version-locked. + +### Users running `flet-cli` and the `flet` Python package from different installs + +Make sure both come from the same release. The common gotcha is a global +`flet` install plus a project-local `flet` in `.venv` at different versions +— upgrade or pin both to ≥ `0.86.0` (or both to ≤ `0.85.x`). + +A version mismatch will surface as a connection failure during `flet run` +with a parse error in the dev-server log. + +### Users with custom backends or sidecars speaking the Flet protocol + +The wire format changed. Update your encoder/decoder: + +- **Inbound on a stream transport** (UDS / TCP): read 4 bytes (length, u32 LE), + read that many bytes, the first byte is the type discriminator, the rest is + the payload. +- **Inbound on a message transport** (WebSocket, `postMessage`): each + message's first byte is the type discriminator, the rest is the payload. +- **Outbound**: same shape, mirrored. + +Treat `0x01` (raw DataChannel) frames as opaque if you don't use data channels +— forward them along or drop them, never feed them to your MsgPack decoder. + +### Code that depended on `StreamingMsgpackDeserializer` + +The Dart-side class `package:flet/src/transport/streaming_msgpack_deserializer.dart` +is removed. There are no public consumers — it was an internal helper for +stream-transport framing that's no longer needed now that every packet is +length-delimited. If you imported it (you shouldn't have), decode each inbound +buffer with one-shot `msgpack.deserialize(bytes)` instead. + +### Custom widgets that override `MatplotlibChartCanvas` + +`MatplotlibChartCanvas` now transports its `apply_full` / `apply_diff` / +`clear` frames through a [DataChannel] rather than `_invoke_method` arguments. +The Python-facing method signatures are unchanged (`apply_full(image_bytes: +bytes)`, etc.), and they remain the documented API. But if you subclassed the +canvas and overrode the Dart-side `_invokeMethod` handler, that override no +longer fires — the Dart side now consumes a 1-byte opcode + PNG payload from +the channel directly. + +## Timeline + +- Changed in: `0.86.0` + +## References + +- API documentation: [DataChannel](/docs/extending-flet/data-channels) +- Release notes: [Flet 0.86.0](../release-notes.md) diff --git a/website/docs/updates/breaking-changes/index.md b/website/docs/updates/breaking-changes/index.md index 6ffd0069dc..d95d4715d4 100644 --- a/website/docs/updates/breaking-changes/index.md +++ b/website/docs/updates/breaking-changes/index.md @@ -28,6 +28,7 @@ Each guide explains the change, the reason for it, and how to migrate your code. - [Default bundled Python version is now 3.14](/docs/updates/breaking-changes/default-bundled-python-3-14) - [`flet.version.pyodide_version` and `PYODIDE_VERSION` removed](/docs/updates/breaking-changes/removed-pyodide-version-export) +- [Flet protocol framing upgraded for DataChannel support](/docs/updates/breaking-changes/data-channel-protocol-upgrade) ### Released in Flet 0.85.0 From 98d8a2a3231e01d39b3f1e463af2836d6af14f99 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 21:54:10 -0700 Subject: [PATCH 13/47] perf(web): transfer Pyodide-worker bytes to main via Transferable ArrayBuffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The worker→main `postMessage` path was structured-cloning every bulk payload (matplotlib PNG frames, etc.) — measurable cost at ~300 KB per frame. Switch to Transferable: extract the Uint8Array's underlying ArrayBuffer and pass it in the second argument to postMessage. Main thread receives the buffer with ownership transferred, no copy. The matching main→worker (Dart→Python) direction already used Transferable since the DataChannel landing. Both directions are now zero-copy across the worker boundary on Pyodide. This does not move the matplotlib bottleneck — that's WASM-compute-bound on mplot3d — but trims a few ms of structured-clone cost per frame and makes the perf budget closer to what the dart_bridge embedded path delivers natively. --- client/web/python-worker.js | 9 ++++++++- .../build/{{cookiecutter.out_dir}}/web/python-worker.js | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/client/web/python-worker.js b/client/web/python-worker.js index 31350fb9a7..5c743dc8e4 100644 --- a/client/web/python-worker.js +++ b/client/web/python-worker.js @@ -213,7 +213,14 @@ self.initPyodide = async function () { }; self.receiveCallback = (message) => { - self.postMessage(message.toJs()); + // `message` is a Pyodide JsProxy wrapping a Python `bytes`. `toJs()` + // gives us a fresh Uint8Array; transferring its underlying ArrayBuffer + // to the main thread skips the structured-clone copy (~hundreds of KB + // per matplotlib frame). Safe because the Uint8Array is freshly + // materialized here, and the original Python `bytes` is untouched + // (Pyodide keeps its own reference). + const bytes = message.toJs(); + self.postMessage(bytes, [bytes.buffer]); } // Same channel as `receiveCallback`, exposed under `flet_js` so the // Python python_output shim can post pre-encoded msgpack frames. diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js index 31350fb9a7..5c743dc8e4 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/web/python-worker.js @@ -213,7 +213,14 @@ self.initPyodide = async function () { }; self.receiveCallback = (message) => { - self.postMessage(message.toJs()); + // `message` is a Pyodide JsProxy wrapping a Python `bytes`. `toJs()` + // gives us a fresh Uint8Array; transferring its underlying ArrayBuffer + // to the main thread skips the structured-clone copy (~hundreds of KB + // per matplotlib frame). Safe because the Uint8Array is freshly + // materialized here, and the original Python `bytes` is untouched + // (Pyodide keeps its own reference). + const bytes = message.toJs(); + self.postMessage(bytes, [bytes.buffer]); } // Same channel as `receiveCallback`, exposed under `flet_js` so the // Python python_output shim can post pre-encoded msgpack frames. From 68e98e36677f2cc3d6c85b67b2a707af1318ca46 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 12 Jun 2026 22:07:24 -0700 Subject: [PATCH 14/47] fix(flet-charts): restore await-based backpressure for matplotlib frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync `apply_full` + side-channel `_on_frame_applied` callback was losing matplotlib "draw" events in pyodide mode. Sequence: 1. `_receive_loop` reads frame bytes, calls `apply_full(bytes)` — sync, returns immediately. 2. Loop iterates, reads next event from `_receive_queue`. 3. Next event is a `"draw"` notification matplotlib emitted just after the previous frame (figure dirty again from mouse drag). 4. Gate check: `_waiting=True` (ack hasn't arrived from Dart yet) → **drop the event**. 5. Ack arrives 200+ ms later, `_waiting=False`, but the queue is empty and matplotlib doesn't re-emit "draw" until next mouse event. Result in pyodide: ~1.5 fps observed, vs the 0.85 `_invoke_method` implementation's much higher rate. The 0.85 pattern wasn't faster because it lacked an ack — it had one (the INVOKE_METHOD reply). It was faster because `await self._invoke_method(...)` **blocked the `_receive_loop`** during the round-trip, so matplotlib events queued naturally in `_receive_queue` and were processed in order after the await returned, rather than being eagerly drained against a stale gate. Fix: re-introduce the await pattern at the canvas level. * `MatplotlibChartCanvas.apply_full / apply_diff / clear` are now async. Each enqueues a per-frame `asyncio.Future`, sends the channel packet, and awaits the future. * `_on_dart_message` resolves the head future when `[0xFF]` arrives. * `MatplotlibChart._receive_loop` awaits each `apply_*` call — matplotlib events that arrive during the wait stay queued and are processed after the ack returns. Same behaviour shape as 0.85's `_invoke_method` round-trip, but over the DataChannel transport (no msgpack on the bulk payload). * `set_on_frame_applied(cb)` is preserved as a pure observer callback for instrumentation (e.g. the 3D example's stats panel) — no longer load-bearing for backpressure. The 3D example's `apply_full` / `apply_diff` wrappers updated to `async def` + `await` accordingly. --- .../charts/matplotlib_chart/three_d/main.py | 24 ++---- .../src/flet_charts/matplotlib_chart.py | 35 +++----- .../flet_charts/matplotlib_chart_canvas.py | 81 ++++++++++++------- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py b/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py index 63c1a29ba1..98f369dcf5 100644 --- a/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py +++ b/sdk/python/examples/extensions/charts/matplotlib_chart/three_d/main.py @@ -216,29 +216,21 @@ async def main(page: ft.Page): orig_full = canvas.apply_full orig_diff = canvas.apply_diff - def apply_full(image_bytes: bytes) -> None: + async def apply_full(image_bytes: bytes) -> None: stats.record_send(len(image_bytes), is_full=True) - orig_full(image_bytes) + await orig_full(image_bytes) - def apply_diff(image_bytes: bytes) -> None: + async def apply_diff(image_bytes: bytes) -> None: stats.record_send(len(image_bytes), is_full=False) - orig_diff(image_bytes) + await orig_diff(image_bytes) canvas.apply_full = apply_full canvas.apply_diff = apply_diff - # Chain ourselves in front of the chart's frame-applied callback so the - # backpressure ack still clears `_waiting` on the chart. We record the - # Dart→Python ack timestamp here, which pairs with the send timestamp - # captured in `record_send` to give the dart-side decode + paint cost. - chart_ack = canvas._on_frame_applied - - def on_ack() -> None: - stats.record_ack() - if chart_ack is not None: - chart_ack() - - canvas.set_on_frame_applied(on_ack) + # Register an observer for frame-applied acks so we can record the + # Dart-side timing. Pure observation — backpressure is handled by + # the apply_*/await pattern in `MatplotlibChart._receive_loop`. + canvas.set_on_frame_applied(stats.record_ack) # Background task: refresh the labels at ~4 Hz so speed/fps decay # visibly when traffic stops and stay readable during fast drags diff --git a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py index 73c49f2992..edefff3963 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart.py @@ -131,10 +131,6 @@ def build(self): on_resize=self._on_canvas_resize, expand=True, ) - # Hook the Dart-side frame-applied ack so we only clear `_waiting` - # after the frame actually rendered. Without this gate, interactive - # drags pile up frames in Dart's queue and replay them in a burst. - self.mpl_canvas.set_on_frame_applied(self._on_frame_applied) # Rubberband (zoom selection) overlay drawn on top of the chart image. self._rubberband = ft.Container( visible=False, @@ -423,17 +419,6 @@ def download(self, format) -> bytes: self.figure.savefig(buff, format=format, dpi=self.figure.dpi * self.__dpr) return buff.getvalue() - def _on_frame_applied(self) -> None: - """ - Called from the canvas when the Dart side finishes rendering a - frame. Clearing `_waiting` here (rather than immediately after - sending the frame) gates `send_message({"type": "draw"})` so - matplotlib doesn't generate the next frame until the previous one - has actually painted. Without this, interactive drags pile up - frames in the Dart-side queue and play back in a burst. - """ - self._waiting = False - async def _receive_loop(self): """ Consume backend messages and apply canvas/state updates. @@ -449,17 +434,21 @@ async def _receive_loop(self): if is_binary: assert isinstance(content, (bytes, bytearray)) logger.debug(f"receive_binary({len(content)})") - # Hand the frame to the client widget — full PNG replaces the - # backbuffer, diff PNG composites onto it. `_waiting` is - # cleared in `_on_frame_applied` when the Dart side acks - # that it actually rendered the frame, not here — otherwise - # interactive drags push frames faster than the renderer - # can keep up and the queue grows unbounded. + # Hand the frame to the client widget — full PNG replaces + # the backbuffer, diff PNG composites onto it. `await` + # here serialises this receive loop on the Dart-side + # frame-applied ack: matplotlib "draw" notifications that + # arrive during the round-trip stay queued in + # `_receive_queue` and are processed after the ack returns, + # instead of being eagerly dropped against a stale + # `_waiting=True` gate. This is the same backpressure shape + # the 0.85 `_invoke_method` round-trip used to provide. if self.__image_mode == "full": - self.mpl_canvas.apply_full(bytes(content)) + await self.mpl_canvas.apply_full(bytes(content)) else: - self.mpl_canvas.apply_diff(bytes(content)) + await self.mpl_canvas.apply_diff(bytes(content)) self.img_count += 1 + self._waiting = False else: logger.debug(f"receive_json({content})") if content["type"] == "image_mode": diff --git a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py index c371824f68..76f25b064e 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py @@ -1,4 +1,6 @@ +import asyncio import contextlib +from collections import deque from dataclasses import dataclass, field from typing import Callable, Optional @@ -69,10 +71,16 @@ def init(self) -> None: # `init` is the @ft.control post-construct lifecycle hook (runs # before `did_mount`). Wire up the default channel-capture handler. self._channel: Optional[ft.DataChannel] = None - # Backpressure ack callback — invoked when Dart finishes applying - # a frame on its end. Producer-side widgets (e.g. MatplotlibChart) - # set this to gate the next frame so the Dart-side queue stays - # bounded under interactive load. + # FIFO of per-frame ack futures. Each `apply_*` enqueues a future + # and `awaits` it; `_on_dart_message` pops the head and resolves + # it when Dart's `[0xFF]` ack arrives. The await is what makes + # producer-side callers (e.g. MatplotlibChart._receive_loop) + # block — events queued during the wait are processed *after* + # the ack instead of being dropped by stale gate checks. + self._pending_acks: deque[asyncio.Future] = deque() + # Optional plain callback for observers that want to be notified + # on every frame ack (e.g. perf instrumentation). Fires alongside + # the future resolution; not load-bearing for backpressure. self._on_frame_applied: Optional[Callable[[], None]] = None if self.on_data_channel_open is None: self.on_data_channel_open = self._capture_channel @@ -87,38 +95,57 @@ def _on_dart_message(self, payload: bytes) -> None: # [0xFF] — frame_applied ack. Sent by Dart after each apply_full / # apply_diff / clear completes on its end. Restores the # round-trip backpressure that `_invoke_method` used to - # provide implicitly. - if not payload: + # provide implicitly in 0.85. + if not payload or payload[0] != 0xFF: return - if payload[0] == 0xFF: - cb = self._on_frame_applied - if cb is not None: - with contextlib.suppress(Exception): - cb() + # Resolve the head future so the matching `apply_*` await returns. + if self._pending_acks: + fut = self._pending_acks.popleft() + if not fut.done(): + fut.set_result(None) + # Then fire the observer callback (if any). + cb = self._on_frame_applied + if cb is not None: + with contextlib.suppress(Exception): + cb() def set_on_frame_applied(self, cb: Optional[Callable[[], None]]) -> None: - """Register a callback invoked when Dart finishes applying a frame. + """Register a side-channel callback invoked on every frame ack. - Producer widgets use this to gate frame emission — e.g. matplotlib - clears its `_waiting` flag here so the next `draw` message from - the figure is honored. Without this gate, the producer would push - frames into the Dart-side queue faster than they're rendered, - causing the UI to hog and then replay buffered frames in a burst. + Useful for instrumentation. Backpressure is handled by awaiting + the result of `apply_full` / `apply_diff` / `clear` directly — + this callback is a fire-and-forget observer, not part of the + gating path. """ self._on_frame_applied = cb - def apply_full(self, image_bytes: bytes) -> None: + async def _send_and_wait(self, packet: bytes) -> None: + """Send a channel packet and await Dart's ack. + + Awaiting blocks the caller until `[0xFF]` arrives on the channel, + re-creating the 0.85 `_invoke_method` round-trip semantics: + events that arrive in the producer's queue during the wait stay + queued (instead of being processed eagerly against a stale + `_waiting` flag). + """ + if self._channel is None: + return + loop = asyncio.get_running_loop() + fut: asyncio.Future = loop.create_future() + self._pending_acks.append(fut) + self._channel.send(packet) + await fut + + async def apply_full(self, image_bytes: bytes) -> None: """ Replace the current displayed image with a full PNG frame. Args: image_bytes: PNG bytes of the complete frame. """ - if self._channel is None: - return - self._channel.send(b"\x01" + image_bytes) + await self._send_and_wait(b"\x01" + image_bytes) - def apply_diff(self, image_bytes: bytes) -> None: + async def apply_diff(self, image_bytes: bytes) -> None: """ Composite an incremental "diff" PNG frame onto the current image. @@ -130,14 +157,10 @@ def apply_diff(self, image_bytes: bytes) -> None: Args: image_bytes: PNG bytes of the diff frame. """ - if self._channel is None: - return - self._channel.send(b"\x02" + image_bytes) + await self._send_and_wait(b"\x02" + image_bytes) - def clear(self) -> None: + async def clear(self) -> None: """ Clear the displayed image and discard the backbuffer. """ - if self._channel is None: - return - self._channel.send(b"\x03") + await self._send_and_wait(b"\x03") From b185f06eef2c4365c690897926f707319ca2db9c Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 08:01:18 -0700 Subject: [PATCH 15/47] ci: fix web client build after flet.version.pyodide_version removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The multi-version Python PR (#6577) removed flet.version.pyodide_version but the 'Get Pyodide version' step still read it, failing every 'Build Flet Client for Web' run. Resolve the version from the flet_cli.utils.python_versions registry instead (default release's Pyodide), and replace the hand-rolled tarball + wheel downloads with flet_cli.utils.pyodide.ensure_pyodide — the hardcoded micropip-0.8.0/packaging-24.2 filenames would have silently broken on the new Pyodide line (3.14's lock resolves micropip 0.11.1), since curl without -f writes 404 pages into the .whl files. Cherry-picked from 2d8f4a15c on fix-android-arch-filtering. Co-authored-by: ndonkoHenri --- .github/workflows/ci.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dbfd39668..2b7e0872c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -579,7 +579,7 @@ jobs: shell: bash working-directory: ${{ env.SDK_PYTHON }} run: | - PYODIDE_VERSION="$( uv run python -c 'import flet.version; print(flet.version.pyodide_version)' )" + PYODIDE_VERSION="$( uv run python -c 'from flet_cli.utils.python_versions import DEFAULT_PYTHON_VERSION, get_release; print(get_release(DEFAULT_PYTHON_VERSION).pyodide)' )" echo "PYODIDE_VERSION=$PYODIDE_VERSION" >> "$GITHUB_ENV" echo "Pyodide version: $PYODIDE_VERSION" @@ -587,10 +587,6 @@ jobs: shell: bash working-directory: client run: | - # Compute Pyodide URLs - PYODIDE_URL="https://github.com/pyodide/pyodide/releases/download/${PYODIDE_VERSION}/pyodide-core-${PYODIDE_VERSION}.tar.bz2" - PYODIDE_CDN_URL="https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full" - FLET_WEB="${SDK_PYTHON}/packages/flet-web/src/flet_web" flutter build web --wasm @@ -601,13 +597,9 @@ jobs: FLUTTER_JS_DIR="$(dirname "$(command -v flutter)")/cache/flutter_web_sdk/flutter_js" cp "$FLUTTER_JS_DIR/flutter.js.map" "${FLET_WEB}/web" - # Download the Pyodide tarball and extract its contents into the web build folder - curl -L "$PYODIDE_URL" | tar -xj -C "${FLET_WEB}/web" - - # Download the prebuilt pyodide wheels - for wheel in "packaging-24.2-py3-none-any.whl" "micropip-0.8.0-py3-none-any.whl"; do - curl -L "${PYODIDE_CDN_URL}/${wheel}" -o "${FLET_WEB}/web/pyodide/${wheel}" - done + # Download the Pyodide runtime (plus the micropip/packaging wheels + # resolved from pyodide-lock.json) into the web build folder + uv run --project "${SDK_PYTHON}" python -c "from pathlib import Path; from flet_cli.utils.pyodide import ensure_pyodide; ensure_pyodide('${PYODIDE_VERSION}', Path('${FLET_WEB}/web/pyodide'))" # Archive the web client into a gzipped tarball tar -czvf "flet-web.tar.gz" -C "build/web" . From d05ca0ab9994eac2ec992bf9f1a1bfd54f5d1f86 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 08:01:28 -0700 Subject: [PATCH 16/47] docs(breaking-changes): drop dead /docs/extending-flet/data-channels link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.86 protocol-framing breaking-change guide linked to a DataChannel API reference page that doesn't exist yet — there's no extending-flet/ folder, and no DataChannel doc has been authored. Docusaurus' broken-link scan failed the docs build on every push. Replace the link with prose pointing at the data_channel.py module docstring; dedicated reference pages can land in a follow-up once the API doc generator covers it. --- .../updates/breaking-changes/data-channel-protocol-upgrade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md b/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md index f0b41cff4c..588165486f 100644 --- a/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md +++ b/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md @@ -107,5 +107,5 @@ the channel directly. ## References -- API documentation: [DataChannel](/docs/extending-flet/data-channels) +- API: `flet.DataChannel` (Python) and `FletBackend.openDataChannel` (Dart) — see the module docstrings in `flet/data_channel.py`; dedicated reference pages will be added in a follow-up. - Release notes: [Flet 0.86.0](../release-notes.md) From b051cd9f991139da27c345914491889825e07253 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 08:08:27 -0700 Subject: [PATCH 17/47] ci: bump Node 20 actions to Node 24 versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions emitted Node.js 20 deprecation warnings on every job in run 27457389406. Node 20 will be removed from runners 2026-09-16. Bump the affected actions to their latest Node 24 majors across all workflows: - actions/checkout@v4 → v6 - actions/setup-node@v4 → v6 (v6 limited auto-cache to npm, the website uses yarn via corepack — no caching behavior change) - actions/upload-artifact@v4 / v5.0.0 → v7 - actions/download-artifact@v4 → v8 - astral-sh/setup-uv@v6 → v8.2.0 (v8 dropped the major @v8 tag for supply-chain reasons, full tag required) - dart-lang/setup-dart@ → v1.7.2 All six actions' action.yml now declare `runs.using: node24`. --- .github/workflows/ci.yml | 66 +++++++++---------- .github/workflows/docs.yml | 6 +- .github/workflows/flet-build-image.yml | 8 +-- .github/workflows/flet-build-test-matrix.yml | 6 +- .github/workflows/flet-build-test.yml | 6 +- .github/workflows/macos-integration-tests.yml | 6 +- .github/workflows/release-pr-changelog.yml | 2 +- 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b7e0872c0..2989a7211d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,10 +53,10 @@ jobs: python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 with: python-version: ${{ matrix.python-version }} @@ -86,7 +86,7 @@ jobs: - name: Upload docs-coverage logs if: matrix.python-version == '3.12' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: docs-coverage path: sdk/python/docstr_coverage.log @@ -99,10 +99,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20 @@ -110,7 +110,7 @@ jobs: run: corepack enable - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Build website run: | @@ -132,20 +132,20 @@ jobs: PYPI_VER: ${{ steps.versions.outputs.PYPI_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 # fetch all history fetch-tags: true # ensure tags are available - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Compute versions id: versions run: source "${SCRIPTS}/update_build_version.sh" - name: Setup Dart (OIDC for pub.dev) - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 + uses: dart-lang/setup-dart@v1.7.2 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -180,10 +180,10 @@ jobs: PKG_VER: ${{ needs.build_flet_package.outputs.PKG_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Patch build template flet version shell: bash @@ -208,7 +208,7 @@ jobs: zip -r "$GITHUB_WORKSPACE/flet-app-templates.zip" app/ - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: template-artifacts path: | @@ -230,10 +230,10 @@ jobs: PYPI_VER: ${{ needs.build_flet_package.outputs.PYPI_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -263,7 +263,7 @@ jobs: 7z a "${ROOT}/client/flet-windows.zip" "flet" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: windows-artifacts if-no-files-found: error @@ -285,10 +285,10 @@ jobs: PYPI_VER: ${{ needs.build_flet_package.outputs.PYPI_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -304,7 +304,7 @@ jobs: tar -czvf flet-macos.tar.gz -C build/macos/Build/Products/Release Flet.app - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: macos-artifacts if-no-files-found: error @@ -443,10 +443,10 @@ jobs: apt-get install -y git curl unzip xz-utils zip ca-certificates jq tzdata - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Get Flutter version from ".fvmrc" uses: kuhnroyal/flutter-fvm-config-action/config@v3 @@ -537,7 +537,7 @@ jobs: build_flutter "flet-desktop" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: linux-${{ matrix.distro_id }}-${{ matrix.arch }}-artifacts if-no-files-found: error @@ -557,10 +557,10 @@ jobs: PYPI_VER: ${{ needs.build_flet_package.outputs.PYPI_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -614,7 +614,7 @@ jobs: uv build --package flet-web --sdist - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: web-artifacts if-no-files-found: error @@ -637,13 +637,13 @@ jobs: PKG_VER: ${{ needs.build_flet_package.outputs.PKG_VER }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -695,7 +695,7 @@ jobs: done - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: flet-python-extensions if-no-files-found: error @@ -716,10 +716,10 @@ jobs: - build_flet_package steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Build Python packages shell: bash @@ -732,7 +732,7 @@ jobs: uv build --package flet-desktop - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: flet-cli-desktop-python-distribution path: | @@ -768,7 +768,7 @@ jobs: fi - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist merge-multiple: true @@ -801,13 +801,13 @@ jobs: - release steps: - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 with: ignore-empty-workdir: true cache-dependency-glob: "" - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist merge-multiple: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eb1b594afd..26c6e63040 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20 @@ -20,7 +20,7 @@ jobs: run: corepack enable - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Build website run: | diff --git a/.github/workflows/flet-build-image.yml b/.github/workflows/flet-build-image.yml index f64a3f13ba..14ce106dfa 100644 --- a/.github/workflows/flet-build-image.yml +++ b/.github/workflows/flet-build-image.yml @@ -26,7 +26,7 @@ jobs: flutter_version: ${{ steps.v.outputs.value }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Resolve version id: v @@ -55,7 +55,7 @@ jobs: FLUTTER_VERSION: ${{ needs.resolve_version.outputs.flutter_version }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Compute platform pair id: pair @@ -94,7 +94,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: flet-build-digests-${{ steps.pair.outputs.value }} path: /tmp/digests/* @@ -111,7 +111,7 @@ jobs: FLUTTER_VERSION: ${{ needs.resolve_version.outputs.flutter_version }} steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: /tmp/digests pattern: flet-build-digests-* diff --git a/.github/workflows/flet-build-test-matrix.yml b/.github/workflows/flet-build-test-matrix.yml index 00a8318c36..415e6d7046 100644 --- a/.github/workflows/flet-build-test-matrix.yml +++ b/.github/workflows/flet-build-test-matrix.yml @@ -140,13 +140,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Patch versions shell: bash @@ -178,7 +178,7 @@ jobs: uv run ${{ matrix.build_cmd }} --python-version ${{ inputs.python_version }} --yes --verbose --build-number ${{ github.run_number }} $FLET_BUILD_EXTRA_ARGS - name: Upload Artifact - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.artifact_name }}-py${{ inputs.python_version }} path: sdk/python/examples/apps/flet_build_test/${{ matrix.artifact_path }} diff --git a/.github/workflows/flet-build-test.yml b/.github/workflows/flet-build-test.yml index df9312743d..56ee425d24 100644 --- a/.github/workflows/flet-build-test.yml +++ b/.github/workflows/flet-build-test.yml @@ -111,13 +111,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -185,7 +185,7 @@ jobs: uv run --with pyinstaller flet pack src/main.py --yes --name flet-pack-test --distpath dist $FLET_PACK_EXTRA_ARGS - name: Upload Artifact - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.name }}-pack-artifact path: sdk/python/examples/apps/flet_build_test/dist diff --git a/.github/workflows/macos-integration-tests.yml b/.github/workflows/macos-integration-tests.yml index 396e5f1be5..6dad23a0de 100644 --- a/.github/workflows/macos-integration-tests.yml +++ b/.github/workflows/macos-integration-tests.yml @@ -55,10 +55,10 @@ jobs: name: ${{ matrix.suite }} Integration Tests steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v8.2.0 - name: Setup Flutter uses: kuhnroyal/flutter-fvm-config-action/setup@v3 @@ -91,7 +91,7 @@ jobs: - name: Upload artifact if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: integration-test-failures-macos-${{ env.SAFE_SUITE }} path: sdk/python/packages/flet/integration_tests/${{ matrix.suite }}/**/*_actual.png diff --git a/.github/workflows/release-pr-changelog.yml b/.github/workflows/release-pr-changelog.yml index f3b6d4aea8..e039e9c723 100644 --- a/.github/workflows/release-pr-changelog.yml +++ b/.github/workflows/release-pr-changelog.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 From 25c96c8bfd74121245870a6af1f72cb87a8b2793 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 08:23:04 -0700 Subject: [PATCH 18/47] fix(tester): preserve ValueKey value type in find_by_key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ValueKey(controlKey.value)` produces `ValueKey` because `controlKey.value` is statically typed Object. Flutter's `ValueKey.==` is runtimeType-strict, so `ValueKey('foo')` never equals the `ValueKey('foo')` that ControlWidget assigns to the rendered widget — making `find_by_key("foo")` from Python tests find 0 widgets. Mirrors the fix already applied in control_widget.dart (7367050f2). Switch-dispatch on the runtime type so String → ValueKey, int → ValueKey, etc. Resolves the cascade of "RangeError: no indices are valid: 0" and "assert 0 == 1" failures across apps, controls/core, controls/material, controls/cupertino, and controls/theme integration suites. --- packages/flet/lib/src/services/tester.dart | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/flet/lib/src/services/tester.dart b/packages/flet/lib/src/services/tester.dart index 18dc410319..cd747a1e55 100644 --- a/packages/flet/lib/src/services/tester.dart +++ b/packages/flet/lib/src/services/tester.dart @@ -50,9 +50,20 @@ class TesterService extends FletService { case "find_by_key": var controlKey = parseKey(args["key"])!; - var key = controlKey is ControlScrollKey + // Preserve the concrete value type so the constructed ValueKey + // matches the one ControlWidget assigned to the rendered widget. + // ValueKey's `==` is runtimeType-strict — `ValueKey('foo')` + // never equals `ValueKey('foo')`, which would make + // `find.byKey(...)` miss every Flet control. + Key? key = controlKey is ControlScrollKey ? control.backend.globalKeys[controlKey.toString()] - : ValueKey(controlKey.value); + : switch (controlKey.value) { + String v => ValueKey(v), + int v => ValueKey(v), + double v => ValueKey(v), + bool v => ValueKey(v), + _ => ValueKey(controlKey.value), + }; if (key == null) { throw Exception("Key not found: $key"); } From 09338dbf45adcab380a46aa23ae0867facafae30 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 08:23:55 -0700 Subject: [PATCH 19/47] fix(tests): update example imports after folder rename in #6545 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #6545 renamed 131 example folders (mostly basic/ → descriptive control name, plus example_1/2/3, nested_themes_1/2 collapsing, and removing the basic/ wrapper where there was only one example) but the matching imports in packages/flet/integration_tests/examples/ were never updated. Test collection failed with ModuleNotFoundError on every affected suite (examples/apps, examples/extensions, and examples/controls/{core,cupertino,material}). Rewrites the 45 test files referencing those modules to the new paths derived from the rename history of commit 1b2e914ae. --- .../flet/integration_tests/examples/apps/test_router.py | 2 +- .../flet/integration_tests/examples/apps/test_todo.py | 2 +- .../examples/controls/core/test_container.py | 8 ++++++-- .../integration_tests/examples/controls/core/test_hero.py | 2 +- .../examples/controls/core/test_pagelet.py | 2 +- .../examples/controls/core/test_placeholder.py | 2 +- .../examples/controls/core/test_responsive_row.py | 2 +- .../examples/controls/core/test_rotated_box.py | 2 +- .../examples/controls/core/test_shimmer.py | 4 +++- .../examples/controls/core/test_vertical_divider.py | 2 +- .../controls/cupertino/test_cupertino_timer_picker.py | 4 +++- .../examples/controls/material/test_auto_complete.py | 2 +- .../examples/controls/material/test_badge.py | 2 +- .../examples/controls/material/test_banner.py | 2 +- .../examples/controls/material/test_bottom_sheet.py | 2 +- .../examples/controls/material/test_button.py | 2 +- .../examples/controls/material/test_checkbox.py | 2 +- .../examples/controls/material/test_datatable.py | 2 +- .../examples/controls/material/test_date_picker.py | 2 +- .../examples/controls/material/test_date_range_picker.py | 2 +- .../examples/controls/material/test_divider.py | 2 +- .../controls/material/test_expansion_panel_list.py | 4 +++- .../examples/controls/material/test_expansion_tile.py | 2 +- .../examples/controls/material/test_filled_button.py | 2 +- .../controls/material/test_filled_tonal_button.py | 4 +++- .../examples/controls/material/test_list_tile.py | 2 +- .../examples/controls/material/test_menu_item_button.py | 2 +- .../examples/controls/material/test_navigation_bar.py | 2 +- .../examples/controls/material/test_navigation_rail.py | 4 +++- .../examples/controls/material/test_outlined_button.py | 4 +++- .../examples/controls/material/test_popup_menu_button.py | 4 +++- .../examples/controls/material/test_radio.py | 2 +- .../examples/controls/material/test_range_slider.py | 2 +- .../controls/material/test_reorderable_drag_handle.py | 4 +++- .../examples/controls/material/test_search_bar.py | 2 +- .../examples/controls/material/test_selection_area.py | 2 +- .../examples/controls/material/test_slider.py | 2 +- .../examples/controls/material/test_snack_bar.py | 2 +- .../examples/controls/material/test_submenu_button.py | 2 +- .../examples/controls/material/test_switch.py | 2 +- .../examples/controls/material/test_tabs.py | 2 +- .../examples/controls/material/test_text_button.py | 2 +- .../examples/controls/material/test_textfield.py | 2 +- .../examples/controls/material/test_time_picker.py | 2 +- .../examples/extensions/code_editor/test_code_editor.py | 6 +++--- 45 files changed, 68 insertions(+), 48 deletions(-) diff --git a/sdk/python/packages/flet/integration_tests/examples/apps/test_router.py b/sdk/python/packages/flet/integration_tests/examples/apps/test_router.py index 92ede9a6cd..f6f3c53287 100644 --- a/sdk/python/packages/flet/integration_tests/examples/apps/test_router.py +++ b/sdk/python/packages/flet/integration_tests/examples/apps/test_router.py @@ -6,7 +6,6 @@ from examples.apps.router.app_drawer import main as app_drawer from examples.apps.router.auth_dialog import main as auth_dialog from examples.apps.router.auth_page import main as auth_page -from examples.apps.router.basic import main as basic from examples.apps.router.dynamic_segments import main as dynamic_segments from examples.apps.router.featured import main as featured from examples.apps.router.featured_views import main as featured_views @@ -18,6 +17,7 @@ from examples.apps.router.nested_routes import main as nested_routes from examples.apps.router.prefix_routes import main as prefix_routes from examples.apps.router.recursive_routes import main as recursive_routes +from examples.apps.router.routing import main as basic from examples.apps.router.runtime_routes import main as runtime_routes from examples.apps.router.splats import main as splats diff --git a/sdk/python/packages/flet/integration_tests/examples/apps/test_todo.py b/sdk/python/packages/flet/integration_tests/examples/apps/test_todo.py index abd2fc4f15..28255e3bfe 100644 --- a/sdk/python/packages/flet/integration_tests/examples/apps/test_todo.py +++ b/sdk/python/packages/flet/integration_tests/examples/apps/test_todo.py @@ -1,6 +1,6 @@ import pytest -import examples.apps.todo.basic.main as todo_basic +import examples.apps.todo.main as todo_basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_container.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_container.py index f16da8e81d..a56bd4ac04 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_container.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_container.py @@ -1,9 +1,13 @@ import pytest -import examples.controls.core.container.nested_themes_1.main as nested_themes_1 -import examples.controls.core.container.nested_themes_2.main as nested_themes_2 import examples.controls.core.container.size_aware.main as size_aware import flet.testing as ftt +from examples.controls.core.container.inherited_and_overridden_theme import ( + main as nested_themes_1, +) +from examples.controls.core.container.page_dark_and_light_themes import ( + main as nested_themes_2, +) @pytest.mark.parametrize( diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_hero.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_hero.py index 78c4c7742e..2f9aabc20d 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_hero.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_hero.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.core.hero.basic import main as basic +from examples.controls.core.hero.hero import main as basic @pytest.mark.parametrize( diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_pagelet.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_pagelet.py index 0e1cccbcac..415e3ed9d0 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_pagelet.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_pagelet.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.core.pagelet.basic.main import main as basic +from examples.controls.core.pagelet.pagelet.main import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_placeholder.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_placeholder.py index fbd33b72ea..c09834faba 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_placeholder.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_placeholder.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.core.placeholder.basic.main import main as basic +from examples.controls.core.placeholder.placeholder.main import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_responsive_row.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_responsive_row.py index f148e6b155..064570d1c5 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_responsive_row.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_responsive_row.py @@ -4,10 +4,10 @@ import flet as ft import flet.testing as ftt -from examples.controls.core.responsive_row.basic.main import main as basic from examples.controls.core.responsive_row.custom_breakpoint.main import ( main as custom_breakpoint, ) +from examples.controls.core.responsive_row.responsive_row.main import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_rotated_box.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_rotated_box.py index 88f6717431..75391519b5 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_rotated_box.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_rotated_box.py @@ -1,7 +1,7 @@ import pytest import flet.testing as ftt -from examples.controls.core.rotated_box.basic.main import main as basic +from examples.controls.core.rotated_box.rotated_box.main import main as basic @pytest.mark.parametrize( diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_shimmer.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_shimmer.py index bf04598528..0da0b7515b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_shimmer.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_shimmer.py @@ -1,9 +1,11 @@ import pytest -import examples.controls.core.shimmer.basic_placeholder.main as basic_placeholder import examples.controls.core.shimmer.custom_gradient.main as custom_gradient import flet as ft import flet.testing as ftt +from examples.controls.core.shimmer.shimmer_basic_placeholder import ( + main as basic_placeholder, +) @pytest.mark.skip(reason="The test is flaky on CI") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_vertical_divider.py b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_vertical_divider.py index f5cd7a9072..34c021b7bc 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/core/test_vertical_divider.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/core/test_vertical_divider.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.core.vertical_divider.basic.main as basic +import examples.controls.core.vertical_divider.vertical_divider.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/cupertino/test_cupertino_timer_picker.py b/sdk/python/packages/flet/integration_tests/examples/controls/cupertino/test_cupertino_timer_picker.py index 58e4b96be8..2e550aa236 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/cupertino/test_cupertino_timer_picker.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/cupertino/test_cupertino_timer_picker.py @@ -1,8 +1,10 @@ import pytest -import examples.controls.cupertino.cupertino_timer_picker.basic.main as basic import flet as ft import flet.testing as ftt +from examples.controls.cupertino.cupertino_timer_picker.cupertino_timer_picker import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_auto_complete.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_auto_complete.py index a6c03d3132..20672364ee 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_auto_complete.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_auto_complete.py @@ -1,7 +1,7 @@ import pytest import flet.testing as ftt -from examples.controls.material.auto_complete.basic import main as basic +from examples.controls.material.auto_complete.auto_complete import main as basic @pytest.mark.parametrize( diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_badge.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_badge.py index a15a01ecf6..b8542f93f7 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_badge.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_badge.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.badge.basic.main as basic +import examples.controls.material.badge.badge.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_banner.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_banner.py index 2863d50e56..efd8926592 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_banner.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_banner.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.banner.basic import main as basic +from examples.controls.material.banner.banner import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_bottom_sheet.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_bottom_sheet.py index 4b3ff641db..2ea619d6be 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_bottom_sheet.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_bottom_sheet.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.bottom_sheet.basic.main as basic +import examples.controls.material.bottom_sheet.bottom_sheet.main as basic import examples.controls.material.bottom_sheet.fullscreen.main as fullscreen import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_button.py index cc6b68a756..f2a717eda7 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_button.py @@ -1,7 +1,7 @@ import pytest import examples.controls.material.button.animate_on_hover.main as animate_on_hover -import examples.controls.material.button.basic.main as basic +import examples.controls.material.button.button.main as basic import examples.controls.material.button.button_shapes.main as button_shapes import examples.controls.material.button.custom_content.main as custom_content import examples.controls.material.button.handling_clicks.main as handling_clicks diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_checkbox.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_checkbox.py index bc51199ab2..40069e49d8 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_checkbox.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_checkbox.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.checkbox.basic.main as basic +import examples.controls.material.checkbox.checkbox.main as basic import examples.controls.material.checkbox.handling_events.main as handling_events import examples.controls.material.checkbox.styled.main as styled import flet as ft diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_datatable.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_datatable.py index 50647fd3e9..7f9a6bf62a 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_datatable.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_datatable.py @@ -5,7 +5,7 @@ from examples.controls.material.data_table.adaptive_row_heights import ( main as adaptive_row_heights, ) -from examples.controls.material.data_table.basic import main as basic +from examples.controls.material.data_table.data_table import main as basic from examples.controls.material.data_table.handling_events import ( main as handling_events, ) diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_picker.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_picker.py index a0f8c1b2ce..b5480c617b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_picker.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_picker.py @@ -2,8 +2,8 @@ import pytest -import examples.controls.material.date_picker.basic.main as basic import examples.controls.material.date_picker.custom_locale.main as custom_locale +import examples.controls.material.date_picker.date_picker.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_range_picker.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_range_picker.py index c15a68c653..91e00a0780 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_range_picker.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_date_range_picker.py @@ -2,8 +2,8 @@ import pytest -import examples.controls.material.date_range_picker.basic.main as basic import examples.controls.material.date_range_picker.custom_locale.main as custom_locale +import examples.controls.material.date_range_picker.date_range_picker.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_divider.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_divider.py index 64b7acdb40..2abc54551c 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_divider.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_divider.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.divider.basic.main as basic +import examples.controls.material.divider.divider.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_panel_list.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_panel_list.py index 85133eb149..2caf641355 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_panel_list.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_panel_list.py @@ -2,7 +2,9 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.expansion_panel_list.basic import main as basic +from examples.controls.material.expansion_panel_list.expansion_panel_list import ( + main as basic, +) from examples.controls.material.expansion_panel_list.scrollable import ( main as scrollable, ) diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_tile.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_tile.py index 1f844a853f..ce6329ec82 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_tile.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_expansion_tile.py @@ -2,11 +2,11 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.expansion_tile.basic import main as basic from examples.controls.material.expansion_tile.borders import main as borders from examples.controls.material.expansion_tile.custom_animations import ( main as custom_animations, ) +from examples.controls.material.expansion_tile.expansion_tile import main as basic from examples.controls.material.expansion_tile.programmatic_expansion import ( main as programmatic_expansion, ) diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_button.py index 1c3def9627..8ce6b8dd5b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_button.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.filled_button.basic import main as basic +from examples.controls.material.filled_button.filled_button import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_tonal_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_tonal_button.py index a2cc355d93..a3195d4379 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_tonal_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_filled_tonal_button.py @@ -2,7 +2,9 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.filled_tonal_button.basic import main as basic +from examples.controls.material.filled_tonal_button.filled_tonal_button import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_list_tile.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_list_tile.py index 68ebec6a0f..6d5bbbdeae 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_list_tile.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_list_tile.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.list_tile.basic import main as basic +from examples.controls.material.list_tile.list_tile import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_menu_item_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_menu_item_button.py index 4be7b7353f..712bdafc01 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_menu_item_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_menu_item_button.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.menu_item_button.basic import main as basic +from examples.controls.material.menu_item_button.menu_item_button import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_bar.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_bar.py index 51e9469da9..c15ca1b15c 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_bar.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_bar.py @@ -2,7 +2,7 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.navigation_bar.basic.main import main as basic +from examples.controls.material.navigation_bar.navigation_bar.main import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_rail.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_rail.py index 02d597b9c4..4ea83a61a5 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_rail.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_navigation_rail.py @@ -2,7 +2,9 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.navigation_rail.basic.main import main as basic +from examples.controls.material.navigation_rail.navigation_rail.main import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_outlined_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_outlined_button.py index 0565913ffa..d3425775ae 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_outlined_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_outlined_button.py @@ -2,7 +2,6 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.outlined_button.basic.main import main as basic from examples.controls.material.outlined_button.custom_content.main import ( main as custom_content, ) @@ -10,6 +9,9 @@ main as handling_clicks, ) from examples.controls.material.outlined_button.icons.main import main as icons +from examples.controls.material.outlined_button.outlined_button.main import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_popup_menu_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_popup_menu_button.py index 191985b7ce..6dceba4df6 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_popup_menu_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_popup_menu_button.py @@ -2,7 +2,9 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.popup_menu_button.basic.main import main as basic +from examples.controls.material.popup_menu_button.popup_menu_button.main import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_radio.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_radio.py index 41450aef3d..cf39f13539 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_radio.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_radio.py @@ -2,10 +2,10 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.radio.basic.main import main as basic from examples.controls.material.radio.handling_selection_changes.main import ( main as handling_selection_changes, ) +from examples.controls.material.radio.radio.main import main as basic from examples.controls.material.radio.styled.main import main as styled diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_range_slider.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_range_slider.py index 7ecf08d0c3..59446a617b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_range_slider.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_range_slider.py @@ -2,10 +2,10 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.range_slider.basic.main import main as basic from examples.controls.material.range_slider.handling_change_events.main import ( main as handling_change_events, ) +from examples.controls.material.range_slider.range_slider.main import main as basic @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_reorderable_drag_handle.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_reorderable_drag_handle.py index dc2e70f640..5bdce04368 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_reorderable_drag_handle.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_reorderable_drag_handle.py @@ -2,7 +2,9 @@ import flet as ft import flet.testing as ftt -from examples.controls.material.reorderable_drag_handle.basic.main import main as basic +from examples.controls.material.reorderable_drag_handle.custom_drag_handle.main import ( + main as basic, +) @pytest.mark.asyncio(loop_scope="function") diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_search_bar.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_search_bar.py index e3d37a26c2..8ee14c9e86 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_search_bar.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_search_bar.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.search_bar.basic.main as basic +import examples.controls.material.search_bar.search_bar.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_selection_area.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_selection_area.py index f8d883f6e6..1ea39c3aec 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_selection_area.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_selection_area.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.selection_area.basic.main as basic +import examples.controls.material.selection_area.selection_area.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_slider.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_slider.py index e649f5065d..544444bc08 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_slider.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_slider.py @@ -1,8 +1,8 @@ import pytest -import examples.controls.material.slider.basic.main as basic import examples.controls.material.slider.custom_label.main as custom_label import examples.controls.material.slider.handling_events.main as handling_events +import examples.controls.material.slider.slider.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_snack_bar.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_snack_bar.py index 591e605d9a..e1342a1be8 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_snack_bar.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_snack_bar.py @@ -1,8 +1,8 @@ import pytest import examples.controls.material.snack_bar.action.main as action -import examples.controls.material.snack_bar.basic.main as basic import examples.controls.material.snack_bar.counter.main as counter +import examples.controls.material.snack_bar.snack_bar.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_submenu_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_submenu_button.py index 7197403852..c31c8efbce 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_submenu_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_submenu_button.py @@ -1,7 +1,7 @@ import pytest -import examples.controls.material.submenu_button.basic.main as basic import examples.controls.material.submenu_button.standalone.main as standalone +import examples.controls.material.submenu_button.submenu_button.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_switch.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_switch.py index 48a1c484b8..b28d0398e2 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_switch.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_switch.py @@ -1,7 +1,7 @@ import pytest -import examples.controls.material.switch.basic.main as basic import examples.controls.material.switch.handling_events.main as handling_events +import examples.controls.material.switch.switch.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_tabs.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_tabs.py index 7595678088..5815afe56b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_tabs.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_tabs.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.tabs.basic.main as basic +import examples.controls.material.tabs.tabs.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_text_button.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_text_button.py index 2bba34c9e6..b5fec2838c 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_text_button.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_text_button.py @@ -1,9 +1,9 @@ import pytest -import examples.controls.material.text_button.basic.main as basic import examples.controls.material.text_button.custom_content.main as custom_content import examples.controls.material.text_button.handling_clicks.main as handling_clicks import examples.controls.material.text_button.icons.main as icons +import examples.controls.material.text_button.text_button.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_textfield.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_textfield.py index e79a4f5eb7..b5de3d0a61 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_textfield.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_textfield.py @@ -1,6 +1,6 @@ import pytest -import examples.controls.material.text_field.basic.main as basic +import examples.controls.material.text_field.text_field.main as basic import flet as ft import flet.testing as ftt from examples.controls.material.text_field.handling_change_events.main import ( diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_time_picker.py b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_time_picker.py index 18d23b2b60..4e96305d88 100644 --- a/sdk/python/packages/flet/integration_tests/examples/controls/material/test_time_picker.py +++ b/sdk/python/packages/flet/integration_tests/examples/controls/material/test_time_picker.py @@ -2,9 +2,9 @@ import pytest -import examples.controls.material.time_picker.basic.main as basic import examples.controls.material.time_picker.custom_locale.main as custom_locale import examples.controls.material.time_picker.hour_formats.main as hour_formats +import examples.controls.material.time_picker.time_picker.main as basic import flet as ft import flet.testing as ftt diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor.py b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor.py index 433c34bdb7..1c701712f4 100644 --- a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor.py +++ b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor.py @@ -1,8 +1,8 @@ import pytest -import examples.extensions.code_editor.example_1.main as example_1 -import examples.extensions.code_editor.example_2.main as example_2 -import examples.extensions.code_editor.example_3.main as example_3 +import examples.extensions.code_editor.code_editor.main as example_1 +import examples.extensions.code_editor.folding_and_initial_selection.main as example_3 +import examples.extensions.code_editor.selection_handling.main as example_2 import flet.testing as ftt From 27a01da98335ce12c05212aebc584996b8c86354 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 09:37:47 -0700 Subject: [PATCH 20/47] Docusaurus 3.10.1 and Node.js 24 --- .github/workflows/ci.yml | 2 +- .github/workflows/docs.yml | 2 +- website/package.json | 10 +- website/yarn.lock | 541 +++++++++++++++++++------------------ 4 files changed, 291 insertions(+), 264 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2989a7211d..9b58f3d7ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 24 - name: Enable Corepack run: corepack enable diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26c6e63040..ccf87d36e5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 24 - name: Enable Corepack run: corepack enable diff --git a/website/package.json b/website/package.json index ce3c2442c6..1efad5ac9a 100644 --- a/website/package.json +++ b/website/package.json @@ -12,11 +12,11 @@ }, "dependencies": { "@docsearch/docusaurus-adapter": "^4.6.2", - "@docusaurus/core": "^3.10.0", - "@docusaurus/faster": "^3.10.0", - "@docusaurus/plugin-client-redirects": "^3.10.0", - "@docusaurus/preset-classic": "^3.10.0", - "@docusaurus/theme-mermaid": "^3.10.0", + "@docusaurus/core": "^3.10.1", + "@docusaurus/faster": "^3.10.1", + "@docusaurus/plugin-client-redirects": "^3.10.1", + "@docusaurus/preset-classic": "^3.10.1", + "@docusaurus/theme-mermaid": "^3.10.1", "@hcaptcha/react-hcaptcha": "^1.0.0", "@mdx-js/react": "^3.0.0", "clsx": "^1.1.1", diff --git a/website/yarn.lock b/website/yarn.lock index 2826c31389..138e853b70 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2241,9 +2241,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/babel@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/babel@npm:3.10.0" +"@docusaurus/babel@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/babel@npm:3.10.1" dependencies: "@babel/core": "npm:^7.25.9" "@babel/generator": "npm:^7.25.9" @@ -2254,12 +2254,12 @@ __metadata: "@babel/preset-typescript": "npm:^7.25.9" "@babel/runtime": "npm:^7.25.9" "@babel/traverse": "npm:^7.25.9" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" babel-plugin-dynamic-import-node: "npm:^2.3.3" fs-extra: "npm:^11.1.1" tslib: "npm:^2.6.0" - checksum: 10c0/d79bd3e8805036e35b09ec4c7ebbf6060b07c1a375b3d27c727cc3122d25c9fddd98d450a22b70eaa9f7f3be0a7c3c5bd36819a7c1abcde4c7b4d3356248a9e3 + checksum: 10c0/3f6d2fdd6bc9c3a47683c1517e2afc7bca4b95d7b1817d68e91c1c2eaf944971d925ec03f2de8d88d40f97a0d88a1415cb2b9c64ac335c7cb6e37e6258c6f97a languageName: node linkType: hard @@ -2286,16 +2286,16 @@ __metadata: languageName: node linkType: hard -"@docusaurus/bundler@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/bundler@npm:3.10.0" +"@docusaurus/bundler@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/bundler@npm:3.10.1" dependencies: "@babel/core": "npm:^7.25.9" - "@docusaurus/babel": "npm:3.10.0" - "@docusaurus/cssnano-preset": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" + "@docusaurus/babel": "npm:3.10.1" + "@docusaurus/cssnano-preset": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" babel-loader: "npm:^9.2.1" clean-css: "npm:^5.3.3" copy-webpack-plugin: "npm:^11.0.0" @@ -2313,13 +2313,13 @@ __metadata: tslib: "npm:^2.6.0" url-loader: "npm:^4.1.1" webpack: "npm:^5.95.0" - webpackbar: "npm:^6.0.1" + webpackbar: "npm:^7.0.0" peerDependencies: "@docusaurus/faster": "*" peerDependenciesMeta: "@docusaurus/faster": optional: true - checksum: 10c0/49af1eba5e45126e972f943148b891c9e167e4510e6f349060ef210c648f28b5ee6344280e1ade0c2e1317bdd165ed3615aa71f95e91bd11e00a7dbb6795a0e3 + checksum: 10c0/20655bd64a5716cc3603d9097e7ca3d2526ccd7265ce3e9d23dfc9679faa08752a5604b98ba2b47eea5e183a3c4f0e383300899ae2f3897513493ec97a6beab2 languageName: node linkType: hard @@ -2360,17 +2360,17 @@ __metadata: languageName: node linkType: hard -"@docusaurus/core@npm:3.10.0, @docusaurus/core@npm:^3.10.0": - version: 3.10.0 - resolution: "@docusaurus/core@npm:3.10.0" - dependencies: - "@docusaurus/babel": "npm:3.10.0" - "@docusaurus/bundler": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" +"@docusaurus/core@npm:3.10.1, @docusaurus/core@npm:^3.10.1": + version: 3.10.1 + resolution: "@docusaurus/core@npm:3.10.1" + dependencies: + "@docusaurus/babel": "npm:3.10.1" + "@docusaurus/bundler": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" boxen: "npm:^6.2.1" chalk: "npm:^4.1.2" chokidar: "npm:^3.5.3" @@ -2416,7 +2416,7 @@ __metadata: optional: true bin: docusaurus: bin/docusaurus.mjs - checksum: 10c0/2a00cd5f1a22a737d37d127f5e5e6aee3ed51563884136fc76d2fa97cb71a7d577e28959f25ec2065c0e232efc003def1d6db94fcee0533063de87484bb39c86 + checksum: 10c0/006d9f57357c3196d0c33f7c5d0ce33b9f032358ed070b8ce212186169c356847cf768b3ac95b54433815c0076eda2eb94805a64bf4b2f5e47376556164e5362 languageName: node linkType: hard @@ -2476,15 +2476,15 @@ __metadata: languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/cssnano-preset@npm:3.10.0" +"@docusaurus/cssnano-preset@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/cssnano-preset@npm:3.10.1" dependencies: cssnano-preset-advanced: "npm:^6.1.2" postcss: "npm:^8.5.4" postcss-sort-media-queries: "npm:^5.2.0" tslib: "npm:^2.6.0" - checksum: 10c0/635df6b05241f73b333b3d7d451d37ec56d7982a8c430afc2e8e8cf7c9e506b499b64d6bba14ccdf79b8afe84452d159516897741aa2fa838194964574da8881 + checksum: 10c0/549cf594fd9cfddd2f57bd177896c6bde8cc3821f7f07e7573d55d4c5841d7eb90d38c1b888b81479ffe33f0015b848c031ad4185f54feedf8ae2f1e2269e2ce languageName: node linkType: hard @@ -2500,11 +2500,11 @@ __metadata: languageName: node linkType: hard -"@docusaurus/faster@npm:^3.10.0": - version: 3.10.0 - resolution: "@docusaurus/faster@npm:3.10.0" +"@docusaurus/faster@npm:^3.10.1": + version: 3.10.1 + resolution: "@docusaurus/faster@npm:3.10.1" dependencies: - "@docusaurus/types": "npm:3.10.0" + "@docusaurus/types": "npm:3.10.1" "@rspack/core": "npm:^1.7.10" "@swc/core": "npm:^1.7.39" "@swc/html": "npm:^1.13.5" @@ -2516,17 +2516,17 @@ __metadata: webpack: "npm:^5.95.0" peerDependencies: "@docusaurus/types": "*" - checksum: 10c0/9e2b1b19a67443c23eceda80606a0e305a586addf991724b923f7756cfdced1a0d6d64426a1790fa81bbc0032ab56041823fe4a694d2392b0ef4ad85dc4089e8 + checksum: 10c0/98d8ca36cd4bcd775be37109c8cd3117ff8ba62446ec50b2901bca7653fbbf690ec9aa494524a27c17744a1744a3e520804100aba014e7aa5621ef3df4679359 languageName: node linkType: hard -"@docusaurus/logger@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/logger@npm:3.10.0" +"@docusaurus/logger@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/logger@npm:3.10.1" dependencies: chalk: "npm:^4.1.2" tslib: "npm:^2.6.0" - checksum: 10c0/f9bc2b7037fb7dff8a5aba06807e4f9601e422b91d0bb7e462ecdb33d71e1c9ee3d9dfb5c37af66f6f35c43310e461857af0dda96531928af3c22678fa77ec18 + checksum: 10c0/c78c676de0cf11ba5737abe8d13ebb67c4fdd8019ac8512ee18b34c27fdd5aaf32b703da3596271592be8615094507754791ac16587a24146f3830e1558a24c3 languageName: node linkType: hard @@ -2540,13 +2540,13 @@ __metadata: languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/mdx-loader@npm:3.10.0" +"@docusaurus/mdx-loader@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/mdx-loader@npm:3.10.1" dependencies: - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" "@mdx-js/mdx": "npm:^3.0.0" "@slorber/remark-comment": "npm:^1.0.0" escape-html: "npm:^1.0.3" @@ -2571,7 +2571,7 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/0b94f20398a2fd39e54215895d2607d277d0cf3a80728adbbadcbf2443063e8e1082929242ccdc4ebe393c6c4010a5ccdecf6f2a8478d90b20c74d032940d33a + checksum: 10c0/2764e3e1a6fc4856746aacd627d43a403cbad87d6ec7400d30092197f36c028207cc5d267e0af2ce34df31f0a68bb2f082bbd5d308d14bedc0d874bc95a89c7b languageName: node linkType: hard @@ -2610,11 +2610,11 @@ __metadata: languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/module-type-aliases@npm:3.10.0" +"@docusaurus/module-type-aliases@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/module-type-aliases@npm:3.10.1" dependencies: - "@docusaurus/types": "npm:3.10.0" + "@docusaurus/types": "npm:3.10.1" "@types/history": "npm:^4.7.11" "@types/react": "npm:*" "@types/react-router-config": "npm:*" @@ -2624,7 +2624,7 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 10c0/61952050bef257a0999db849a328655a4141d31b8d4fa4d54828da7ee8f710d7e592081a150c8b9750640bcaf78f3b7ca7165aefbcc0048c328407d582fe21b8 + checksum: 10c0/837faf66e24b9b0e2d2d276956f00cf5395a752682396013876935b6b0275b573d4cc3e9667a5110f9077c5943ddd1f47b462217a8418a5e6bdd013de8afd918 languageName: node linkType: hard @@ -2646,15 +2646,15 @@ __metadata: languageName: node linkType: hard -"@docusaurus/plugin-client-redirects@npm:^3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-client-redirects@npm:3.10.0" +"@docusaurus/plugin-client-redirects@npm:^3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-client-redirects@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" eta: "npm:^2.2.0" fs-extra: "npm:^11.1.1" lodash: "npm:^4.17.21" @@ -2662,22 +2662,22 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/06dced23c81d0008a9b0856cad74b76d20d93f8191bc6484770f23473e128e46524442f6f7569d2df9d1c745bea6d2c8d18a70834395bc88335b233febb314fe + checksum: 10c0/0bcd7387b6ae3132ab76c7715d440bb6b769ab509fa241720a90575b01541eefe3cd47281da812c546db0e31cffc002c4fb8629430fd1191e5d6f27ef2037483 languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-content-blog@npm:3.10.0" - dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" +"@docusaurus/plugin-content-blog@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-content-blog@npm:3.10.1" + dependencies: + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" cheerio: "npm:1.0.0-rc.12" combine-promises: "npm:^1.1.0" feed: "npm:^4.2.2" @@ -2693,23 +2693,23 @@ __metadata: "@docusaurus/plugin-content-docs": "*" react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/80295c4d217c45d2685d71e3e898e4e67715ce3ecf684063927e0f9c771a2156af2aefb813b61ed33d8a14bc0dbc820da9cd745b32fe4ef5baa03091165b3542 + checksum: 10c0/5c6d912bbcbbc9efa5c81e256f4074e70980c85623f5fd72a344b90bd5f23ed2c03030d8d0cb1ce38bdb2263fdf74ac3e7801a66050184ed11005adc5f49bcb2 languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-content-docs@npm:3.10.0" - dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/module-type-aliases": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" +"@docusaurus/plugin-content-docs@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-content-docs@npm:3.10.1" + dependencies: + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/module-type-aliases": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" "@types/react-router-config": "npm:^5.0.7" combine-promises: "npm:^1.1.0" fs-extra: "npm:^11.1.1" @@ -2722,7 +2722,7 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/d1d61c85363231216e7f02731806c1519804c14b1a59bab84c386f4dfb45433081ed516cca42d8d891b9855a9ec996d53fe1a7624474a70d64515e7205beb791 + checksum: 10c0/a9d78f6979cc64aef1210f51979f2ed5100ce4a49ba266386e35e9109e4415e66bd9a9448696078f247d204949a29197faa9396bd1f014c88fcdd1aa1e98a3f8 languageName: node linkType: hard @@ -2755,129 +2755,129 @@ __metadata: languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-content-pages@npm:3.10.0" +"@docusaurus/plugin-content-pages@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-content-pages@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" fs-extra: "npm:^11.1.1" tslib: "npm:^2.6.0" webpack: "npm:^5.88.1" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/780bf847a37a2bd7732870f2f8e7395aa82c0f9cba61353225fe6c1abfe48b1403b21f2ad67983db0f0712b01be277796e8d4d51d16e082447c269fe5afadb6c + checksum: 10c0/00dab7af101b0607746d820c399bc3062279165b1b79009f2c6c8439a627818748da52f43eee0a4a874923707c89c9fce17aab72a7c88d298fa36b6efc0bacc7 languageName: node linkType: hard -"@docusaurus/plugin-css-cascade-layers@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-css-cascade-layers@npm:3.10.0" +"@docusaurus/plugin-css-cascade-layers@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-css-cascade-layers@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" tslib: "npm:^2.6.0" - checksum: 10c0/ebbfadc70293ff30878f263a166cd0c1e0bea24067acfc8ccb5d45adb9cc653c753fa9a27d874cd5e7855e2f7e5a35f1d337f07b6b28edabc77524f3533f47ea + checksum: 10c0/fd11a7488029226276bbdbeb1de4035ed425b8d7be0fbad3d5a0aa3a18646064f1930835bfc951837bfc813873548e1a8b23d9f52cb20120e1239a1d93128d79 languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-debug@npm:3.10.0" +"@docusaurus/plugin-debug@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-debug@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" fs-extra: "npm:^11.1.1" react-json-view-lite: "npm:^2.3.0" tslib: "npm:^2.6.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/575c364dcd2595928ebbc8ce6e90113e6bdcc2658ae59f3ddcd0fa2699880a81648765dc7083058bcc957bafd0f7e116c61c62e0cb6b678af97f7e719b5d2db7 + checksum: 10c0/d2978e40e83c1c4fd904dc3bc62ef1b6d52cf28e391e9dacad40712d7b14ce9ad22f0ad710631021066c296d10c364621b2c11a186694d8f5c6e59f35043f99a languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-google-analytics@npm:3.10.0" +"@docusaurus/plugin-google-analytics@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-google-analytics@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" tslib: "npm:^2.6.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/f3814d3ec0c7e2040ac5f3a21a9e1dbb19d58af5a1096fe8376a8661fac92e9e77d3d48742ed7dfb0a1e635360bf1a4e2dd456b5d9d8746e490a250f9b7da097 + checksum: 10c0/c9aff3f3467d3e76a1dd8e1518e8a11b0d488b1761f0302a6cc9fdb94635b6e5673b3d993434205dc65cd6eb9677f795157887d508f01ba175c3f5e411aab2e6 languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-google-gtag@npm:3.10.0" +"@docusaurus/plugin-google-gtag@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-google-gtag@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" "@types/gtag.js": "npm:^0.0.20" tslib: "npm:^2.6.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/31739d936f9ffa4c500d518816b17ac4f2ba2e75e20e5a6708eb2ed5d488465146b5a632899ab894cf8d8233306d212ac79d89c4c6a26c45f6dd15d31638444d + checksum: 10c0/6792713f813cb06dcc54ece92ac81faed01017a71079726f97adade1fa2b6665626369c788e108dee9c02e0cbfc4cc71a2e87db5d3e43f47944e7b0921bb71db languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.10.0" +"@docusaurus/plugin-google-tag-manager@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-google-tag-manager@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" tslib: "npm:^2.6.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/6937bd384653ef938a5b66cf56bce458cc39c33aa35a6ebc43139abb393cfc7cf7865dcf6af60a2dbf65ebb06d40303530430a52e30a119b9c3d7419e53f3a6d + checksum: 10c0/a1a01de5f876d63fec022d312c711525523ee380af3382e35d1953a577450da36039dfcd1ff7f0af0cd189cc14d268c64276def573bdc70d091db2cad56edf9f languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-sitemap@npm:3.10.0" - dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" +"@docusaurus/plugin-sitemap@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-sitemap@npm:3.10.1" + dependencies: + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" fs-extra: "npm:^11.1.1" sitemap: "npm:^7.1.1" tslib: "npm:^2.6.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/a0538da02713caaf844cd3b489a360408bb6868ceefbe3e51e7d02223919e8349b219aac1d111e258a29be5eeaea53d712448abf9d7d860f0af89b12d6652a86 + checksum: 10c0/22c04be000a1577f9a0c169fb67ff0d4deaf618aa2a3839336cfaed667558f1db3ada5bed23ee7245596a307bbddbed62ede8e342120aa8efebe0b9d35f4da75 languageName: node linkType: hard -"@docusaurus/plugin-svgr@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/plugin-svgr@npm:3.10.0" +"@docusaurus/plugin-svgr@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/plugin-svgr@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" "@svgr/core": "npm:8.1.0" "@svgr/webpack": "npm:^8.1.0" tslib: "npm:^2.6.0" @@ -2885,53 +2885,53 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/31a049eaf82c80296b0dc4d7d7bd292bda13dbcf9f07943db4cd2b721276185cb95f6058c406ff4602f4ff408f0fb042f3ade8c8e1d009054ecfa55d99960a88 - languageName: node - linkType: hard - -"@docusaurus/preset-classic@npm:^3.10.0": - version: 3.10.0 - resolution: "@docusaurus/preset-classic@npm:3.10.0" - dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/plugin-content-blog": "npm:3.10.0" - "@docusaurus/plugin-content-docs": "npm:3.10.0" - "@docusaurus/plugin-content-pages": "npm:3.10.0" - "@docusaurus/plugin-css-cascade-layers": "npm:3.10.0" - "@docusaurus/plugin-debug": "npm:3.10.0" - "@docusaurus/plugin-google-analytics": "npm:3.10.0" - "@docusaurus/plugin-google-gtag": "npm:3.10.0" - "@docusaurus/plugin-google-tag-manager": "npm:3.10.0" - "@docusaurus/plugin-sitemap": "npm:3.10.0" - "@docusaurus/plugin-svgr": "npm:3.10.0" - "@docusaurus/theme-classic": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/theme-search-algolia": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" + checksum: 10c0/d866efe42351d1a66febacd6c33753f5ace2056fe86259365408edd354fb67994208a4e2c9939a921c2a20cb11b64fdd3584d34dde8d0329a63a55c9a74c488f + languageName: node + linkType: hard + +"@docusaurus/preset-classic@npm:^3.10.1": + version: 3.10.1 + resolution: "@docusaurus/preset-classic@npm:3.10.1" + dependencies: + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/plugin-content-blog": "npm:3.10.1" + "@docusaurus/plugin-content-docs": "npm:3.10.1" + "@docusaurus/plugin-content-pages": "npm:3.10.1" + "@docusaurus/plugin-css-cascade-layers": "npm:3.10.1" + "@docusaurus/plugin-debug": "npm:3.10.1" + "@docusaurus/plugin-google-analytics": "npm:3.10.1" + "@docusaurus/plugin-google-gtag": "npm:3.10.1" + "@docusaurus/plugin-google-tag-manager": "npm:3.10.1" + "@docusaurus/plugin-sitemap": "npm:3.10.1" + "@docusaurus/plugin-svgr": "npm:3.10.1" + "@docusaurus/theme-classic": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/theme-search-algolia": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/c7d9ce9b76f309b65a3cdba6702f49adb4c518da3e3c4a4f745c5ad659cab9a9d1bf3841d49817fa4a1e3d226c2f683d6e263bb36d9d9bb6143f9fc4d36add42 - languageName: node - linkType: hard - -"@docusaurus/theme-classic@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/theme-classic@npm:3.10.0" - dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/module-type-aliases": "npm:3.10.0" - "@docusaurus/plugin-content-blog": "npm:3.10.0" - "@docusaurus/plugin-content-docs": "npm:3.10.0" - "@docusaurus/plugin-content-pages": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/theme-translations": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + checksum: 10c0/524675229e33f24f678ca104eab1b6328ca31c2572e5cb2a598a330bd990a8d50eb1929aaef9031c37827a22ddaf6bb19484b75ac2a5b0936f6322d951f33559 + languageName: node + linkType: hard + +"@docusaurus/theme-classic@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/theme-classic@npm:3.10.1" + dependencies: + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/module-type-aliases": "npm:3.10.1" + "@docusaurus/plugin-content-blog": "npm:3.10.1" + "@docusaurus/plugin-content-docs": "npm:3.10.1" + "@docusaurus/plugin-content-pages": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/theme-translations": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" "@mdx-js/react": "npm:^3.0.0" clsx: "npm:^2.0.0" copy-text-to-clipboard: "npm:^3.2.0" @@ -2948,18 +2948,18 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/920df8c75701cd462cc414440b446157b6c831432bb2fe0e506268a5a72ef7fefe58568d8fb12bfc61845e8809f5fe6900314f39e9867a0aedabd184cbaa05b9 + checksum: 10c0/55be80ca9a1880c0a923c519243233bb52025e4a0ae312907c9df52b8ab3594819da164a71377e6ed5b02eef740496eff006da41462603195c0284b977515ceb languageName: node linkType: hard -"@docusaurus/theme-common@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/theme-common@npm:3.10.0" +"@docusaurus/theme-common@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/theme-common@npm:3.10.1" dependencies: - "@docusaurus/mdx-loader": "npm:3.10.0" - "@docusaurus/module-type-aliases": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" + "@docusaurus/mdx-loader": "npm:3.10.1" + "@docusaurus/module-type-aliases": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" "@types/history": "npm:^4.7.11" "@types/react": "npm:*" "@types/react-router-config": "npm:*" @@ -2972,7 +2972,7 @@ __metadata: "@docusaurus/plugin-content-docs": "*" react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/16cda69e916adfc2cfdeea6940264c01d56e8b87e87fca887d7d28933712333b5b60ce60a64d505ddda8da2c6538b50f3aa4e16351e3d05df9f8e590b407be6e + checksum: 10c0/f66e25b6449e03b5c8812be407e80c1c9ffc87b07395273d009a40761302e6230ab357096de95b47f77aef1d7e4d0363220cca07b7fbdeb9679770e20e0ab7a4 languageName: node linkType: hard @@ -3000,15 +3000,15 @@ __metadata: languageName: node linkType: hard -"@docusaurus/theme-mermaid@npm:^3.10.0": - version: 3.10.0 - resolution: "@docusaurus/theme-mermaid@npm:3.10.0" +"@docusaurus/theme-mermaid@npm:^3.10.1": + version: 3.10.1 + resolution: "@docusaurus/theme-mermaid@npm:3.10.1" dependencies: - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/module-type-aliases": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/module-type-aliases": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" mermaid: "npm:>=11.6.0" tslib: "npm:^2.6.0" peerDependencies: @@ -3018,23 +3018,23 @@ __metadata: peerDependenciesMeta: "@mermaid-js/layout-elk": optional: true - checksum: 10c0/2bf6c0b0c7a7a55f7a89e6af7abef5ce2ca8f2e3c4a5e5be5b99cc9d8043135253dd73a588f008d3a5abf4133a3cc631983153e60fa05236398a7a2bac3f3cf7 + checksum: 10c0/73839669eb3e69ec565a16853b6a8fa636f3fd94d6656976835a0304bb6e7402b5dcbd7808b8b158bb0f34e61fee1d835511d88f59f48fa613eb51532614992c languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/theme-search-algolia@npm:3.10.0" +"@docusaurus/theme-search-algolia@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/theme-search-algolia@npm:3.10.1" dependencies: "@algolia/autocomplete-core": "npm:^1.19.2" "@docsearch/react": "npm:^3.9.0 || ^4.3.2" - "@docusaurus/core": "npm:3.10.0" - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/plugin-content-docs": "npm:3.10.0" - "@docusaurus/theme-common": "npm:3.10.0" - "@docusaurus/theme-translations": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-validation": "npm:3.10.0" + "@docusaurus/core": "npm:3.10.1" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/plugin-content-docs": "npm:3.10.1" + "@docusaurus/theme-common": "npm:3.10.1" + "@docusaurus/theme-translations": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-validation": "npm:3.10.1" algoliasearch: "npm:^5.37.0" algoliasearch-helper: "npm:^3.26.0" clsx: "npm:^2.0.0" @@ -3046,17 +3046,17 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/63dd5f7e99457a71f0eb7916e18fa421e3194018975a52e8d8bd197abfdf5f19d85348a8a7e0713bccac413e9d5b1cb54b9c69c28a868e0473c1cf0b806f3faa + checksum: 10c0/dd558ac0c50f2374b8285f463ec23e1996247f4436cd9147fd313689d02d9113214e0368c64fdc8ca106f10b6ef93589814980ea0121cb3583ca87feae4b19c3 languageName: node linkType: hard -"@docusaurus/theme-translations@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/theme-translations@npm:3.10.0" +"@docusaurus/theme-translations@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/theme-translations@npm:3.10.1" dependencies: fs-extra: "npm:^11.1.1" tslib: "npm:^2.6.0" - checksum: 10c0/62fa157763e2ad4d8c7afea0edebce895f85da5384c48222a1f697932716c550eeda34310d473643d037ae6d41720909174abf409971fcddd0eadb63daafced6 + checksum: 10c0/2a1c871e883a82c4c5071820dcdc3bbeea5a3571978d022c1d1b820ae8b676ea5dd173b9c17542a032b9a5ec7e06f5d8a3b9de31a1e1f474f527baccfb5f9deb languageName: node linkType: hard @@ -3070,9 +3070,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/types@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/types@npm:3.10.0" +"@docusaurus/types@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/types@npm:3.10.1" dependencies: "@mdx-js/mdx": "npm:^3.0.0" "@types/history": "npm:^4.7.11" @@ -3087,7 +3087,7 @@ __metadata: peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/0d0f5f57bb82f190385a506192d882a5072e833af55a35cb5fb69048bb4258012eebe51448b8ace9d77d05d69a99d7fd2dcae25bb4babfa205abfbca222de8d5 + checksum: 10c0/63cc1d92ec775fb0e58f156b4edc7075612943a94d15d4648b60effcbc34c70a1092569d66d552878779b48d5111abe2fc154ba6a3bca2af0383550c9f9a015b languageName: node linkType: hard @@ -3112,13 +3112,13 @@ __metadata: languageName: node linkType: hard -"@docusaurus/utils-common@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/utils-common@npm:3.10.0" +"@docusaurus/utils-common@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/utils-common@npm:3.10.1" dependencies: - "@docusaurus/types": "npm:3.10.0" + "@docusaurus/types": "npm:3.10.1" tslib: "npm:^2.6.0" - checksum: 10c0/12e54b8e29d1d8d78f85598a154fc122f4d93bdd143b55fd7a474c2d9eab431bbf13ac61e008f1c4f34ffce76578fe95b441f6a6469a752d7396f9d9c000f6e4 + checksum: 10c0/1dde7a5c538a2cbe9eba46c9a3991a773e2d9d5b9c489cb010f9284c33cba7d494c1300557f850fa6a6e857747ca835b91f6036b02a726e13d296d7a65f8d8db languageName: node linkType: hard @@ -3132,19 +3132,19 @@ __metadata: languageName: node linkType: hard -"@docusaurus/utils-validation@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/utils-validation@npm:3.10.0" +"@docusaurus/utils-validation@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/utils-validation@npm:3.10.1" dependencies: - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/utils": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/utils": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" fs-extra: "npm:^11.2.0" joi: "npm:^17.9.2" js-yaml: "npm:^4.1.0" lodash: "npm:^4.17.21" tslib: "npm:^2.6.0" - checksum: 10c0/ab1aee9c9b236d4c5247f33b245c016a2ef501ef154f5f5392a98e706d448ee60c32746b4c58e4954be24393eee6db06cb3192efa8df00343176c558fca33924 + checksum: 10c0/6368eb2a879fc1c4a507873689bd0a08257e2bc72a2ba53b3629d633d97d368168720fc504a54f96a0652982fc098f29675076e1fbcaf0244947645131a3d2f9 languageName: node linkType: hard @@ -3164,13 +3164,13 @@ __metadata: languageName: node linkType: hard -"@docusaurus/utils@npm:3.10.0": - version: 3.10.0 - resolution: "@docusaurus/utils@npm:3.10.0" +"@docusaurus/utils@npm:3.10.1": + version: 3.10.1 + resolution: "@docusaurus/utils@npm:3.10.1" dependencies: - "@docusaurus/logger": "npm:3.10.0" - "@docusaurus/types": "npm:3.10.0" - "@docusaurus/utils-common": "npm:3.10.0" + "@docusaurus/logger": "npm:3.10.1" + "@docusaurus/types": "npm:3.10.1" + "@docusaurus/utils-common": "npm:3.10.1" escape-string-regexp: "npm:^4.0.0" execa: "npm:^5.1.1" file-loader: "npm:^6.2.0" @@ -3189,7 +3189,7 @@ __metadata: url-loader: "npm:^4.1.1" utility-types: "npm:^3.10.0" webpack: "npm:^5.88.1" - checksum: 10c0/0f3488c38fbc985378f93f6573cf080559207ae367b0052df2ad42d667726ec766900db68184ec1746bcf4c38c9a1289d9f54fbd71a857dc592363996295afff + checksum: 10c0/97d51da30035ef34eced1dbc937f829309a8165a07a9c66d87c5deec03fb62150cbc70b2763ffdec0286b21f1be53f4011a036e4265cb971a892f0ab12172e0a languageName: node linkType: hard @@ -5946,6 +5946,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^3.2.0": + version: 3.17.0 + resolution: "ansis@npm:3.17.0" + checksum: 10c0/d8fa94ca7bb91e7e5f8a7d323756aa075facce07c5d02ca883673e128b2873d16f93e0dec782f98f1eeb1f2b3b4b7b60dcf0ad98fb442e75054fe857988cc5cb + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -8829,11 +8836,11 @@ __metadata: resolution: "flet-dev@workspace:." dependencies: "@docsearch/docusaurus-adapter": "npm:^4.6.2" - "@docusaurus/core": "npm:^3.10.0" - "@docusaurus/faster": "npm:^3.10.0" - "@docusaurus/plugin-client-redirects": "npm:^3.10.0" - "@docusaurus/preset-classic": "npm:^3.10.0" - "@docusaurus/theme-mermaid": "npm:^3.10.0" + "@docusaurus/core": "npm:^3.10.1" + "@docusaurus/faster": "npm:^3.10.1" + "@docusaurus/plugin-client-redirects": "npm:^3.10.1" + "@docusaurus/preset-classic": "npm:^3.10.1" + "@docusaurus/theme-mermaid": "npm:^3.10.1" "@hcaptcha/react-hcaptcha": "npm:^1.0.0" "@mdx-js/react": "npm:^3.0.0" clsx: "npm:^1.1.1" @@ -15543,6 +15550,26 @@ __metadata: languageName: node linkType: hard +"webpackbar@npm:^7.0.0": + version: 7.0.0 + resolution: "webpackbar@npm:7.0.0" + dependencies: + ansis: "npm:^3.2.0" + consola: "npm:^3.2.3" + pretty-time: "npm:^1.1.0" + std-env: "npm:^3.7.0" + peerDependencies: + "@rspack/core": "*" + webpack: 3 || 4 || 5 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10c0/03ed85edca12af824319dfcd0fe5c3a90b9e3c86400a604a55589abe0a66a682033e7de027e89aae03652b6fb8ca7fd2831d86829179304ea3121f807808f7c6 + languageName: node + linkType: hard + "websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4" From e63a4b48279b43fba4185db1ae5a8b5ba4460149 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 10:19:46 -0700 Subject: [PATCH 21/47] feat(cli): add 'flet --version --json' and source CI version/dep reads from it Add a --json flag to 'flet --version' that emits a machine-readable document (Flet/Flutter versions, supported Python/Pyodide table, Linux build deps). CI workflows and the publish docs now read it via jq instead of importing Flet internals with 'python -c'. Move the canonical Linux apt dependency list from flet.utils.linux_deps (runtime package) to flet_cli.utils.linux_deps (build tooling), where it sits next to python_versions.py and is a same-package import for the CLI. --- .github/workflows/ci.yml | 4 +- .github/workflows/flet-build-test-matrix.yml | 2 +- .github/workflows/flet-build-test.yml | 2 +- CHANGELOG.md | 1 + .../packages/flet-cli/src/flet_cli/cli.py | 65 +++++++++++++++++-- .../src/flet_cli}/utils/linux_deps.py | 0 website/docs/publish/index.md | 2 +- 7 files changed, 64 insertions(+), 12 deletions(-) rename sdk/python/packages/{flet/src/flet => flet-cli/src/flet_cli}/utils/linux_deps.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b58f3d7ba..7c849aac45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -476,7 +476,7 @@ jobs: sed -i.bak '/apt.postgresql.org/s/^/# /' /etc/apt/sources.list fi apt-get update --allow-releaseinfo-change - LINUX_DEPS="$(uv run --project sdk/python/packages/flet python -c 'from flet.utils.linux_deps import linux_dependencies; print(" ".join(linux_dependencies))')" + LINUX_DEPS="$(uv run --project sdk/python/packages/flet-cli flet --version --json | jq -r '.linux_dependencies | join(" ")')" apt-get install -y $LINUX_DEPS - name: Build Flutter Linux clients @@ -579,7 +579,7 @@ jobs: shell: bash working-directory: ${{ env.SDK_PYTHON }} run: | - PYODIDE_VERSION="$( uv run python -c 'from flet_cli.utils.python_versions import DEFAULT_PYTHON_VERSION, get_release; print(get_release(DEFAULT_PYTHON_VERSION).pyodide)' )" + PYODIDE_VERSION="$( uv run flet --version --json | jq -r '.python_versions[] | select(.default) | .pyodide' )" echo "PYODIDE_VERSION=$PYODIDE_VERSION" >> "$GITHUB_ENV" echo "Pyodide version: $PYODIDE_VERSION" diff --git a/.github/workflows/flet-build-test-matrix.yml b/.github/workflows/flet-build-test-matrix.yml index 415e6d7046..e0c4554306 100644 --- a/.github/workflows/flet-build-test-matrix.yml +++ b/.github/workflows/flet-build-test-matrix.yml @@ -160,7 +160,7 @@ jobs: shell: bash run: | sudo apt update --allow-releaseinfo-change - LINUX_DEPS="$(uv run --project sdk/python/packages/flet python -c 'from flet.utils.linux_deps import linux_dependencies; print(" ".join(linux_dependencies))')" + LINUX_DEPS="$(uv run --project sdk/python/packages/flet-cli flet --version --json | jq -r '.linux_dependencies | join(" ")')" sudo apt-get install -y --no-install-recommends $LINUX_DEPS sudo apt-get clean diff --git a/.github/workflows/flet-build-test.yml b/.github/workflows/flet-build-test.yml index 56ee425d24..51c0f13c3c 100644 --- a/.github/workflows/flet-build-test.yml +++ b/.github/workflows/flet-build-test.yml @@ -137,7 +137,7 @@ jobs: shell: bash run: | sudo apt update --allow-releaseinfo-change - LINUX_DEPS="$(uv run --project sdk/python/packages/flet python -c 'from flet.utils.linux_deps import linux_dependencies; print(" ".join(linux_dependencies))')" + LINUX_DEPS="$(uv run --project sdk/python/packages/flet-cli flet --version --json | jq -r '.linux_dependencies | join(" ")')" sudo apt-get install -y --no-install-recommends $LINUX_DEPS sudo apt-get clean diff --git a/CHANGELOG.md b/CHANGELOG.md index 2672eeb9fd..41751d0ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide//` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * `flet --version` now lists the supported Python versions newest first, each with its matching Pyodide release and a `default` / `pre-release` annotation where applicable, instead of the single static `Pyodide: …` line. The global `flet.version.pyodide_version` export is removed (the only external consumer was the CLI version output, now updated) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* `flet --version --json` emits a machine-readable version document — Flet/Flutter versions, the full supported Python/Pyodide table, and the Linux build dependencies — so CI workflows and the publish docs read it via `jq` instead of importing Flet internals with `python -c`. The canonical Linux apt dependency list moved from `flet.utils.linux_deps` (runtime package) to `flet_cli.utils.linux_deps` (build tooling) by @FeodorFitsner. * `client/web/python.js` and the build template's `python.js` no longer hardcode `defaultPyodideUrl`. `patch_index.py` now injects `flet.pyodideUrl` per build (CDN URL by default, or the local `pyodide/pyodide.js` path under `--no-cdn`) so the runtime URL always tracks the resolved Pyodide release ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Stream-oriented Flet protocol transports (UDS / TCP used by `flet run` dev mode) now use length-prefixed framing instead of streaming `msgpack.Unpacker.feed`. Combined with a new 1-byte type discriminator at the head of every packet (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket, `dart_bridge` FFI, Pyodide `postMessage`). `StreamingMsgpackDeserializer` is removed from `package:flet`; each inbound packet is one complete MsgPack value, decoded one-shot via `msgpack.deserialize(bytes)` by @FeodorFitsner. diff --git a/sdk/python/packages/flet-cli/src/flet_cli/cli.py b/sdk/python/packages/flet-cli/src/flet_cli/cli.py index 6c9f6e355b..6a0d68ccb3 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/cli.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/cli.py @@ -1,4 +1,5 @@ import argparse +import json import sys from packaging.version import Version @@ -14,6 +15,7 @@ import flet_cli.commands.publish import flet_cli.commands.run import flet_cli.commands.serve +from flet_cli.utils.linux_deps import linux_dependencies from flet_cli.utils.python_versions import ( DEFAULT_PYTHON_VERSION, SUPPORTED_PYTHON_VERSIONS, @@ -39,6 +41,49 @@ def _supported_python_versions_block() -> str: return "\n".join(lines) +def _version_info() -> dict: + """Build the machine-readable `flet --version --json` document. + + This is the single source CI reads (via `jq`) for Flet/Flutter versions, + the supported Python/Pyodide table, and the Linux build dependencies — + avoiding `python -c 'import flet...'` calls that couple workflows to + internal module paths. + """ + sorted_versions = sorted( + SUPPORTED_PYTHON_VERSIONS, + key=lambda r: Version(r.short), + reverse=True, + ) + return { + "flet": flet.version.flet_version, + "flutter": flet.version.flutter_version, + "default_python_version": DEFAULT_PYTHON_VERSION, + "python_versions": [ + { + "short": r.short, + "standalone": r.standalone, + "pyodide": r.pyodide, + "pyodide_platform_tag": r.pyodide_platform_tag, + "prerelease": r.prerelease, + "default": r.short == DEFAULT_PYTHON_VERSION, + } + for r in sorted_versions + ], + "linux_dependencies": list(linux_dependencies), + } + + +def _render_version(as_json: bool) -> str: + """Render `flet --version` output as JSON or the human-readable text block.""" + if as_json: + return json.dumps(_version_info(), indent=2) + return ( + f"Flet: {flet.version.flet_version}\n" + f"Flutter: {flet.version.flutter_version}\n" + f"{_supported_python_versions_block()}" + ) + + # Source https://stackoverflow.com/a/26379693 def set_default_subparser( parser: argparse.ArgumentParser, name: str, args: list = None, index: int = 0 @@ -93,16 +138,17 @@ def get_parser() -> argparse.ArgumentParser: formatter_class=argparse.RawDescriptionHelpFormatter ) - # add version flag + # add version flags parser.add_argument( "--version", "-V", - action="version", - version=( - f"Flet: {flet.version.flet_version}\n" - f"Flutter: {flet.version.flutter_version}\n" - f"{_supported_python_versions_block()}" - ), + action="store_true", + help="show version information and exit", + ) + parser.add_argument( + "--json", + action="store_true", + help="with --version, output version information as JSON", ) sp = parser.add_subparsers(dest="command") @@ -136,6 +182,11 @@ def main(): # parse arguments args = parser.parse_args() + # handle `flet --version [--json]` (no subcommand/handler is set) + if getattr(args, "version", False): + print(_render_version(args.json)) + sys.exit(0) + # execute command args.handler(args) diff --git a/sdk/python/packages/flet/src/flet/utils/linux_deps.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/linux_deps.py similarity index 100% rename from sdk/python/packages/flet/src/flet/utils/linux_deps.py rename to sdk/python/packages/flet-cli/src/flet_cli/utils/linux_deps.py diff --git a/website/docs/publish/index.md b/website/docs/publish/index.md index 5d8cf6e3ac..4a512776b4 100644 --- a/website/docs/publish/index.md +++ b/website/docs/publish/index.md @@ -1535,7 +1535,7 @@ jobs: shell: bash run: | sudo apt update --allow-releaseinfo-change - LINUX_DEPS="$(uv run python -c 'from flet.utils.linux_deps import linux_dependencies; print(" ".join(linux_dependencies))')" + LINUX_DEPS="$(uv run flet --version --json | jq -r '.linux_dependencies | join(" ")')" sudo apt-get install -y --no-install-recommends $LINUX_DEPS sudo apt-get clean From fa0a27b92102a23a5fd0ef5158af886d93f9ac96 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 10:22:25 -0700 Subject: [PATCH 22/47] ci: pin Windows runners to windows-2025-vs2026 GitHub is redirecting windows-latest to windows-2025-vs2026 by June 15, 2026. Pin the label explicitly in the Flet Build Test and Build & Publish workflows to silence the redirect notice and make the image deterministic. --- .github/workflows/ci.yml | 2 +- .github/workflows/flet-build-test-matrix.yml | 8 ++++---- .github/workflows/flet-build-test.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c849aac45..a0e43b27a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,7 +220,7 @@ jobs: # ============================= build_windows: name: Build Flet Client for Windows - runs-on: windows-latest + runs-on: windows-2025-vs2026 needs: - python_tests - build_flet_package diff --git a/.github/workflows/flet-build-test-matrix.yml b/.github/workflows/flet-build-test-matrix.yml index e0c4554306..29d3b187e8 100644 --- a/.github/workflows/flet-build-test-matrix.yml +++ b/.github/workflows/flet-build-test-matrix.yml @@ -52,7 +52,7 @@ jobs: needs_linux_deps: false - name: windows - runner: windows-latest + runner: windows-2025-vs2026 build_cmd: "flet build windows" artifact_name: windows-build-artifact artifact_path: build/windows @@ -74,7 +74,7 @@ jobs: needs_linux_deps: false - name: aab-windows - runner: windows-latest + runner: windows-2025-vs2026 build_cmd: "flet build aab" artifact_name: aab-build-windows-artifact artifact_path: build/aab @@ -95,7 +95,7 @@ jobs: needs_linux_deps: false - name: apk-windows - runner: windows-latest + runner: windows-2025-vs2026 build_cmd: "flet build apk" artifact_name: apk-build-windows-artifact artifact_path: build/apk @@ -132,7 +132,7 @@ jobs: needs_linux_deps: false - name: web-windows - runner: windows-latest + runner: windows-2025-vs2026 build_cmd: "flet build web" artifact_name: web-build-windows-artifact artifact_path: build/web diff --git a/.github/workflows/flet-build-test.yml b/.github/workflows/flet-build-test.yml index 51c0f13c3c..2c459bc24d 100644 --- a/.github/workflows/flet-build-test.yml +++ b/.github/workflows/flet-build-test.yml @@ -107,7 +107,7 @@ jobs: runner: macos-26 - name: windows - runner: windows-latest + runner: windows-2025-vs2026 steps: - name: Checkout repository From f738809348606dc3b5071ebabf540d1f034d2d75 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 13 Jun 2026 10:30:53 -0700 Subject: [PATCH 23/47] Allow flutter_secure_storage updates (^10.0.0) Update pubspec.yaml dependency for flutter_secure_storage from fixed 10.0.0 to caret ^10.0.0, allowing compatible minor/patch updates instead of pinning to a single patch version. This lets the package accept backwards-compatible releases without manual changes. Fix #6586 --- .../src/flutter/flet_secure_storage/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/packages/flet-secure-storage/src/flutter/flet_secure_storage/pubspec.yaml b/sdk/python/packages/flet-secure-storage/src/flutter/flet_secure_storage/pubspec.yaml index eb1322748e..55c3901355 100644 --- a/sdk/python/packages/flet-secure-storage/src/flutter/flet_secure_storage/pubspec.yaml +++ b/sdk/python/packages/flet-secure-storage/src/flutter/flet_secure_storage/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter - flutter_secure_storage: 10.0.0 + flutter_secure_storage: ^10.0.0 flet: path: ../../../../../../../packages/flet From 394e19e226bb76c34c2cabc4173b46be70e3a4a9 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 14 Jun 2026 16:29:56 -0700 Subject: [PATCH 24/47] Resolve Python/Pyodide versions from python-build's manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop flet's hand-mirrored SUPPORTED_PYTHON_VERSIONS table and load the supported Python/Pyodide/dart_bridge set from python-build's date-keyed manifest.json — the single source of truth shared with serious_python. - python_versions.py: pin one PYTHON_BUILD_RELEASE_DATE; fetch that release's manifest.json (cached immutably under ~/.flet/cache/python-build, offline fallback to cache) and parse it lazily. Module constants become get_supported_python_versions()/get_default_python_version(); resolution logic unchanged. Dev/CI overrides: FLET_PYTHON_BUILD_RELEASE_DATE, FLET_PYTHON_BUILD_MANIFEST. - flet build: pass only SERIOUS_PYTHON_VERSION; serious_python derives the full version, build date, and dart_bridge version from its committed snapshot. Drops the SERIOUS_PYTHON_FULL_VERSION/SERIOUS_PYTHON_BUILD_DATE exports. - flet --version: drop the Python/Pyodide matrix (stays offline); --json keeps flet/flutter/linux_dependencies. - ci.yml: read the default Pyodide version via the manifest-backed resolver instead of jq over `flet --version --json`. - Docs: update the removed-pyodide-version-export guide + CHANGELOG to the new accessors; document the pin in CONTRIBUTING. - Add offline tests driven by FLET_PYTHON_BUILD_MANIFEST. --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 2 +- CONTRIBUTING.md | 1 + .../packages/flet-cli/src/flet_cli/cli.py | 55 +----- .../src/flet_cli/commands/build_base.py | 9 +- .../src/flet_cli/utils/python_versions.py | 162 +++++++++++------- .../flet-cli/tests/test_python_versions.py | 107 ++++++++++++ .../removed-pyodide-version-export.md | 18 +- 8 files changed, 230 insertions(+), 126 deletions(-) create mode 100644 sdk/python/packages/flet-cli/tests/test_python_versions.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0e43b27a5..7df924bd7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -579,7 +579,7 @@ jobs: shell: bash working-directory: ${{ env.SDK_PYTHON }} run: | - PYODIDE_VERSION="$( uv run flet --version --json | jq -r '.python_versions[] | select(.default) | .pyodide' )" + PYODIDE_VERSION="$( uv run python -c "from flet_cli.utils.python_versions import resolve_python_version; print(resolve_python_version(None).pyodide)" )" echo "PYODIDE_VERSION=$PYODIDE_VERSION" >> "$GITHUB_ENV" echo "Pyodide version: $PYODIDE_VERSION" diff --git a/CHANGELOG.md b/CHANGELOG.md index 41751d0ef4..2a0c40bd6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ ### Breaking changes * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. -* The `flet.version.pyodide_version` module attribute and the `PYODIDE_VERSION` constant are removed. Reach for `flet_cli.utils.python_versions.SUPPORTED_PYTHON_VERSIONS` if you need the per-version Pyodide mapping programmatically ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* The `flet.version.pyodide_version` module attribute and the `PYODIDE_VERSION` constant are removed. Call `flet_cli.utils.python_versions.get_supported_python_versions()` if you need the per-version Pyodide mapping programmatically ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. ### Bug fixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60f68e536e..fd2ad45cd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -259,6 +259,7 @@ For patches to the current stable release, branch directly from `main`, fix, ope * Ensure every merged PR on `release/v{version}` added a new record to the active root `CHANGELOG.md` section. * Open terminal in `client` directory and run `flutter pub get` to update Flet dependency versions in `client/pubspec.lock`. * Templates are in `sdk/python/templates/` and automatically packaged as zip artifacts with the GitHub Release. No manual branch creation in external repos is needed. +* The supported Python / Pyodide versions are loaded on demand from [python-build's](https://github.com/flet-dev/python-build) date-keyed `manifest.json`; flet pins one release via `PYTHON_BUILD_RELEASE_DATE` in `sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py`. When bumping it, keep it aligned with serious_python's `pythonReleaseDate` (both should track the same python-build release). ## New macOS environment for Flet developer diff --git a/sdk/python/packages/flet-cli/src/flet_cli/cli.py b/sdk/python/packages/flet-cli/src/flet_cli/cli.py index 6a0d68ccb3..565dd1cdd2 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/cli.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/cli.py @@ -2,8 +2,6 @@ import json import sys -from packaging.version import Version - import flet.version import flet_cli.commands.build import flet_cli.commands.create @@ -16,59 +14,18 @@ import flet_cli.commands.run import flet_cli.commands.serve from flet_cli.utils.linux_deps import linux_dependencies -from flet_cli.utils.python_versions import ( - DEFAULT_PYTHON_VERSION, - SUPPORTED_PYTHON_VERSIONS, -) - - -def _supported_python_versions_block() -> str: - """Render the multi-line `flet --version` listing of supported Python releases.""" - lines = ["Supported Python versions:"] - sorted_versions = sorted( - SUPPORTED_PYTHON_VERSIONS, - key=lambda r: Version(r.short), - reverse=True, - ) - for r in sorted_versions: - suffix = [] - if r.prerelease: - suffix.append("pre-release") - if r.short == DEFAULT_PYTHON_VERSION: - suffix.append("default") - tail = f", {', '.join(suffix)}" if suffix else "" - lines.append(f" {r.short} (Pyodide {r.pyodide}{tail})") - return "\n".join(lines) def _version_info() -> dict: """Build the machine-readable `flet --version --json` document. - This is the single source CI reads (via `jq`) for Flet/Flutter versions, - the supported Python/Pyodide table, and the Linux build dependencies — - avoiding `python -c 'import flet...'` calls that couple workflows to - internal module paths. + Exposes Flet/Flutter versions and the Linux build dependencies. The + supported Python/Pyodide set is no longer surfaced here — it now comes from + python-build's manifest (see `flet_cli.utils.python_versions`). """ - sorted_versions = sorted( - SUPPORTED_PYTHON_VERSIONS, - key=lambda r: Version(r.short), - reverse=True, - ) return { "flet": flet.version.flet_version, "flutter": flet.version.flutter_version, - "default_python_version": DEFAULT_PYTHON_VERSION, - "python_versions": [ - { - "short": r.short, - "standalone": r.standalone, - "pyodide": r.pyodide, - "pyodide_platform_tag": r.pyodide_platform_tag, - "prerelease": r.prerelease, - "default": r.short == DEFAULT_PYTHON_VERSION, - } - for r in sorted_versions - ], "linux_dependencies": list(linux_dependencies), } @@ -77,11 +34,7 @@ def _render_version(as_json: bool) -> str: """Render `flet --version` output as JSON or the human-readable text block.""" if as_json: return json.dumps(_version_info(), indent=2) - return ( - f"Flet: {flet.version.flet_version}\n" - f"Flutter: {flet.version.flutter_version}\n" - f"{_supported_python_versions_block()}" - ) + return f"Flet: {flet.version.flet_version}\nFlutter: {flet.version.flutter_version}" # Source https://stackoverflow.com/a/26379693 diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index 964e4b0dd4..b1e57a52a9 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -1912,10 +1912,11 @@ def package_python_app(self): ["--arch"] + self.template_data["options"]["target_arch"] ) + # Only the short version is passed; serious_python derives the full + # version, python-build date, and dart_bridge version from its own + # committed snapshot of the manifest. package_env = { "SERIOUS_PYTHON_VERSION": self.python_release.short, - "SERIOUS_PYTHON_FULL_VERSION": self.python_release.standalone, - "SERIOUS_PYTHON_BUILD_DATE": self.python_release.python_build_date, } # requirements @@ -2203,10 +2204,10 @@ def _run_flutter_command(self): ] ) + # Only the short version is passed; serious_python derives the rest + # from its committed manifest snapshot. build_env = { "SERIOUS_PYTHON_VERSION": self.python_release.short, - "SERIOUS_PYTHON_FULL_VERSION": self.python_release.standalone, - "SERIOUS_PYTHON_BUILD_DATE": self.python_release.python_build_date, } # site-packages variable diff --git a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py index 8f3065d56e..dea7821f1a 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py @@ -1,29 +1,48 @@ """Supported Python versions for `flet build` / `flet publish`. -This module is the single source of truth on the Python side for which -Python releases the Flet toolchain can bundle, and the matching -CPython-standalone + Pyodide artifacts. Mirror any change here in -serious_python's `_pythonReleases` map (bin/package_command.dart). +The set of bundlable Python releases — and the matching CPython-standalone + +Pyodide artifacts — is defined by python-build's date-keyed ``manifest.json``, +the single source of truth shared with serious_python. This module pins one +python-build release date and fetches that release's manifest (cached under +``~/.flet/cache``, immutable per date), so nothing here is hand-mirrored. + +Pin (``PYTHON_BUILD_RELEASE_DATE``) overrides, for dev/CI: +* ``FLET_PYTHON_BUILD_RELEASE_DATE`` — use a different published release date. +* ``FLET_PYTHON_BUILD_MANIFEST`` — read a local ``manifest.json`` instead of + fetching (mirrors serious_python's ``gen_version_tables --manifest``). """ +import json +import os +import shutil +import urllib.request from dataclasses import dataclass +from functools import lru_cache from typing import Callable, Optional from packaging.specifiers import SpecifierSet from packaging.version import Version +from flet_cli.utils.template_cache import get_cache_root + +# python-build release this flet pins. Keep in sync with serious_python's +# `pythonReleaseDate` (lib/src/python_versions.dart) — both should track the +# same python-build release. +PYTHON_BUILD_RELEASE_DATE = "20260614" + +RELEASE_DATE_ENV = "FLET_PYTHON_BUILD_RELEASE_DATE" +MANIFEST_PATH_ENV = "FLET_PYTHON_BUILD_MANIFEST" + +_MANIFEST_URL = ( + "https://github.com/flet-dev/python-build/releases/download/{date}/manifest.json" +) + @dataclass(frozen=True) class PythonRelease: short: str standalone: str - standalone_date: str - # Release date tag of the matching `flet-dev/python-build` release - # (e.g. "20260611"). Combined with `standalone` to construct the - # platform-plugin download URLs. - python_build_date: str pyodide: str - pyodide_platform_tag: str # When True, this release is supported via `--python-version` (and an # explicit `requires-python = "==X.Y.*"` specifier) but is not picked # automatically by the default or by open-ended `requires-python` @@ -31,61 +50,82 @@ class PythonRelease: prerelease: bool -SUPPORTED_PYTHON_VERSIONS: list[PythonRelease] = [ - PythonRelease( - short="3.12", - standalone="3.12.13", - standalone_date="20260610", - python_build_date="20260611", - pyodide="0.27.7", - pyodide_platform_tag="pyodide-2024.0-wasm32", - prerelease=False, - ), - PythonRelease( - short="3.13", - standalone="3.13.14", - standalone_date="20260610", - python_build_date="20260611", - pyodide="0.29.4", - pyodide_platform_tag="pyemscripten-2025.0-wasm32", - prerelease=False, - ), - PythonRelease( - short="3.14", - standalone="3.14.6", - standalone_date="20260610", - python_build_date="20260611", - pyodide="314.0.0", - pyodide_platform_tag="pyemscripten-2026.0-wasm32", - prerelease=False, - ), - # Add future pre-release CPython lines with `prerelease=True`. They are - # opt-in via `--python-version 3.15` or an explicit - # `requires-python = "==3.15.*"`; never the auto-resolved default. - # - # PythonRelease( - # short="3.15", - # standalone="3.15.0", - # standalone_date="...", - # python_build_date="...", - # pyodide="...", - # pyodide_platform_tag="...", - # prerelease=True, - # ), -] - -DEFAULT_PYTHON_VERSION = "3.14" +def _resolve_release_date() -> str: + return os.environ.get(RELEASE_DATE_ENV) or PYTHON_BUILD_RELEASE_DATE + + +def _load_manifest() -> dict: + """Return the python-build manifest as a dict. + + Reads ``$FLET_PYTHON_BUILD_MANIFEST`` if set; otherwise fetches the pinned + release's ``manifest.json`` (cached immutably under + ``~/.flet/cache/python-build``). Falls back to a present cache when the + network is unavailable; raises with an actionable message if neither the + network nor a cache can supply it. + """ + local = os.environ.get(MANIFEST_PATH_ENV) + if local: + with open(local, encoding="utf-8") as f: + return json.load(f) + + date = _resolve_release_date() + url = _MANIFEST_URL.format(date=date) + cache_path = get_cache_root() / "python-build" / f"manifest-{date}.json" + + if not (cache_path.exists() and cache_path.stat().st_size > 0): + cache_path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = cache_path.with_suffix(cache_path.suffix + ".tmp") + try: + with urllib.request.urlopen(url) as resp, open(tmp_path, "wb") as out: + shutil.copyfileobj(resp, out) + out.flush() + os.fsync(out.fileno()) + os.replace(tmp_path, cache_path) + except BaseException as e: + tmp_path.unlink(missing_ok=True) + if not (cache_path.exists() and cache_path.stat().st_size > 0): + raise RuntimeError( + f"Could not obtain the Python build manifest for release " + f"{date} from {url}: {e}. Check your network connection, or " + f"set ${MANIFEST_PATH_ENV} to a local manifest.json." + ) from e + + with cache_path.open(encoding="utf-8") as f: + return json.load(f) + + +@lru_cache(maxsize=1) +def _load_data() -> tuple[tuple[PythonRelease, ...], str]: + manifest = _load_manifest() + releases = tuple( + PythonRelease( + short=short, + standalone=info["full_version"], + pyodide=info["pyodide_version"], + prerelease=bool(info.get("prerelease", False)), + ) + for short, info in manifest["pythons"].items() + ) + return releases, manifest["default_python_version"] + + +def get_supported_python_versions() -> list[PythonRelease]: + return list(_load_data()[0]) + + +def get_default_python_version() -> str: + return _load_data()[1] def get_release(short: str) -> Optional[PythonRelease]: - for r in SUPPORTED_PYTHON_VERSIONS: + for r in get_supported_python_versions(): if r.short == short: return r return None def supported_short_versions() -> list[str]: - return [r.short for r in SUPPORTED_PYTHON_VERSIONS] + return [r.short for r in get_supported_python_versions()] class UnsupportedPythonVersionError(ValueError): @@ -100,12 +140,14 @@ def resolve_python_version( Priority: `--python-version` CLI arg → `[project].requires-python` (parsed as a PEP 440 SpecifierSet, highest matching supported short version wins) → - `DEFAULT_PYTHON_VERSION`. + the manifest's default. Raises `UnsupportedPythonVersionError` if the CLI arg names an unsupported version, or if `requires-python` excludes every supported version. """ + supported = get_supported_python_versions() + if cli_arg: release = get_release(cli_arg) if release is None: @@ -130,7 +172,7 @@ def resolve_python_version( # `>=3.14` never silently jumps to a beta CPython line. stable_matching = [ r - for r in SUPPORTED_PYTHON_VERSIONS + for r in supported if not r.prerelease and Version(r.standalone) in spec ] if stable_matching: @@ -147,7 +189,7 @@ def resolve_python_version( spec_with_pre = SpecifierSet(requires, prereleases=True) prerelease_matching = [ r - for r in SUPPORTED_PYTHON_VERSIONS + for r in supported if r.prerelease and Version(r.short) in spec_with_pre ] if prerelease_matching: @@ -162,6 +204,6 @@ def resolve_python_version( f"({', '.join(supported_short_versions())})." ) - fallback = get_release(DEFAULT_PYTHON_VERSION) + fallback = get_release(get_default_python_version()) assert fallback is not None return fallback diff --git a/sdk/python/packages/flet-cli/tests/test_python_versions.py b/sdk/python/packages/flet-cli/tests/test_python_versions.py new file mode 100644 index 0000000000..0b86d81a33 --- /dev/null +++ b/sdk/python/packages/flet-cli/tests/test_python_versions.py @@ -0,0 +1,107 @@ +"""Tests for manifest-backed Python version resolution. + +These run fully offline: `FLET_PYTHON_BUILD_MANIFEST` points the loader at a +local fixture manifest instead of fetching python-build's release asset. +""" + +import json + +import pytest + +from flet_cli.utils import python_versions as pv + +_FIXTURE_MANIFEST = { + "release": "20260614", + "default_python_version": "3.14", + "dart_bridge_version": "1.2.3", + "pythons": { + "3.12": { + "full_version": "3.12.13", + "pyodide_version": "0.27.7", + "prerelease": False, + }, + "3.13": { + "full_version": "3.13.14", + "pyodide_version": "0.29.4", + "prerelease": False, + }, + "3.14": { + "full_version": "3.14.6", + "pyodide_version": "314.0.0", + "prerelease": False, + }, + # A pre-release line: opt-in only, never auto-resolved. + "3.15": { + "full_version": "3.15.0a1", + "pyodide_version": "315.0.0", + "prerelease": True, + }, + }, +} + + +@pytest.fixture(autouse=True) +def fixture_manifest(tmp_path, monkeypatch): + manifest_path = tmp_path / "manifest.json" + manifest_path.write_text(json.dumps(_FIXTURE_MANIFEST), encoding="utf-8") + monkeypatch.setenv(pv.MANIFEST_PATH_ENV, str(manifest_path)) + pv._load_data.cache_clear() + yield + pv._load_data.cache_clear() + + +def test_default_and_supported_versions(): + assert pv.get_default_python_version() == "3.14" + assert pv.supported_short_versions() == ["3.12", "3.13", "3.14", "3.15"] + + +def test_release_fields_mapped_from_manifest(): + r = pv.get_release("3.14") + assert r is not None + assert (r.short, r.standalone, r.pyodide, r.prerelease) == ( + "3.14", + "3.14.6", + "314.0.0", + False, + ) + assert pv.get_release("3.99") is None + + +def test_resolve_default_when_no_arg(): + assert pv.resolve_python_version(None).short == "3.14" + + +def test_resolve_explicit_cli_arg(): + assert pv.resolve_python_version("3.12").short == "3.12" + + +def test_resolve_unsupported_cli_arg_raises(): + with pytest.raises(pv.UnsupportedPythonVersionError): + pv.resolve_python_version("3.99") + + +def test_resolve_from_requires_python_picks_highest_stable(): + get_pyproject = lambda key: ">=3.13" # noqa: E731 + assert pv.resolve_python_version(None, get_pyproject).short == "3.14" + + +def test_resolve_from_requires_python_exact(): + get_pyproject = lambda key: "==3.12.*" # noqa: E731 + assert pv.resolve_python_version(None, get_pyproject).short == "3.12" + + +def test_requires_python_skips_prerelease_for_open_specifier(): + # `>=3.14` must not silently jump to the 3.15 pre-release line. + get_pyproject = lambda key: ">=3.14" # noqa: E731 + assert pv.resolve_python_version(None, get_pyproject).short == "3.14" + + +def test_requires_python_opts_into_prerelease_explicitly(): + get_pyproject = lambda key: "==3.15.*" # noqa: E731 + assert pv.resolve_python_version(None, get_pyproject).short == "3.15" + + +def test_requires_python_no_match_raises(): + get_pyproject = lambda key: ">=3.99" # noqa: E731 + with pytest.raises(pv.UnsupportedPythonVersionError): + pv.resolve_python_version(None, get_pyproject) diff --git a/website/docs/updates/breaking-changes/removed-pyodide-version-export.md b/website/docs/updates/breaking-changes/removed-pyodide-version-export.md index ba65634155..ec6e96f870 100644 --- a/website/docs/updates/breaking-changes/removed-pyodide-version-export.md +++ b/website/docs/updates/breaking-changes/removed-pyodide-version-export.md @@ -26,11 +26,11 @@ Earlier Flet releases used a single Pyodide pin in `flet.version` to drive the web` would bundle. With [multi-version Python support](/docs/publish#choosing-a-python-version) the mapping is now Python-version-aware (`3.12 → 0.27.7`, `3.13 → 0.29.4`, -`3.14 → 314.0.0`) and lives in -[`flet_cli/utils/python_versions.py`](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py). - -`flet --version` now lists every supported Python version with its matching -Pyodide release, newest first. +`3.14 → 314.0.0`). It is loaded on demand from +[python-build's date-keyed `manifest.json`](https://github.com/flet-dev/python-build) +— the single source of truth shared with serious_python — via +[`flet_cli/utils/python_versions.py`](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py), +so it is no longer hand-maintained. ## Migration guide @@ -48,9 +48,9 @@ print(f"Bundled Pyodide: {flet.version.pyodide_version}") Code after migration: ```python -from flet_cli.utils.python_versions import SUPPORTED_PYTHON_VERSIONS +from flet_cli.utils.python_versions import get_supported_python_versions -for release in SUPPORTED_PYTHON_VERSIONS: +for release in get_supported_python_versions(): print(f" {release.short} → Pyodide {release.pyodide}") ``` @@ -59,11 +59,11 @@ the Python release the same way the CLI does: ```python from flet_cli.utils.python_versions import ( - DEFAULT_PYTHON_VERSION, + get_default_python_version, get_release, ) -release = get_release(DEFAULT_PYTHON_VERSION) +release = get_release(get_default_python_version()) print(f"Default Pyodide: {release.pyodide}") ``` From ea7cf3d5a3a7cad2ca0eb1d1bbf6bd9503c3e448 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 14 Jun 2026 16:29:57 -0700 Subject: [PATCH 25/47] Pin screen_brightness_macos to 2.1.2 (SPM macOS deployment-target regression) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 SPM floor, so `flutter build macos` fails to resolve with Swift Package Manager enabled. Pinning the app-facing screen_brightness alone doesn't help — the federated macOS implementation is separately versioned. Override the impl to the last good 2.1.2 in both the build template and the client app. Upstream: https://github.com/aaassseee/screen_brightness/issues/99 --- client/pubspec.lock | 12 ++++++------ client/pubspec.yaml | 7 +++++++ .../build/{{cookiecutter.out_dir}}/pubspec.yaml | 6 ++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 8028a32cb3..30b9cf07bc 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1364,7 +1364,7 @@ packages: source: hosted version: "2.0.3" screen_brightness: - dependency: transitive + dependency: "direct overridden" description: name: screen_brightness sha256: "5f70754028f169f059fdc61112a19dcbee152f8b293c42c848317854d650cba3" @@ -1388,13 +1388,13 @@ packages: source: hosted version: "2.1.2" screen_brightness_macos: - dependency: transitive + dependency: "direct overridden" description: name: screen_brightness_macos - sha256: "4edf330ad21078686d8bfaf89413325fbaf571dcebe1e89254d675a3f288b5b9" + sha256: "278712cf5288db57bd335968cbfb2ec5441028f1ee2fcbdc8d1582d8210a3442" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" screen_brightness_ohos: dependency: transitive description: @@ -1407,10 +1407,10 @@ packages: dependency: transitive description: name: screen_brightness_platform_interface - sha256: "737bd47b57746bc4291cab1b8a5843ee881af499514881b0247ec77447ee769c" + sha256: "2de60c0ba569b898950029cc1f7e9dd72bda44a22beb5054aac331cb6fce2ff2" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" screen_brightness_windows: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 79ebf5030c..09a7815bfa 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -95,6 +95,13 @@ dependency_overrides: flet: path: ../packages/flet + screen_brightness: 2.1.7 + # screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a + # Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 floor, + # which breaks Swift Package Manager resolution. Pin the last good impl until + # upstream fixes it: https://github.com/aaassseee/screen_brightness/issues/99 + screen_brightness_macos: 2.1.2 + dev_dependencies: flutter_test: sdk: flutter diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index 48b6ab6c6d..4019c41dfe 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -46,6 +46,12 @@ dependency_overrides: path: ../../../../../packages/flet device_info_plus: 12.3.0 # remove that in May, 2026 once CI moved to macOS 26 connectivity_plus: 7.0.0 # remove that in May, 2026 once CI moved to macOS 26 + screen_brightness: 2.1.7 + # screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a + # Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 floor, + # which breaks Swift Package Manager resolution. Pin the last good impl until + # upstream fixes it: https://github.com/aaassseee/screen_brightness/issues/99 + screen_brightness_macos: 2.1.2 dev_dependencies: flutter_launcher_icons: ^0.14.1 From 2459240530a5f568a7f5c33ed1119ac6b2cef961 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sun, 14 Jun 2026 17:22:26 -0700 Subject: [PATCH 26/47] flet build: clean build dir when the bundled Python version changes Switching --python-version (or requires-python) between builds left the previous version's compiled bytecode in the reused build directory's native bundles (stdlib/site-packages .pyc), crashing the app at runtime with `ImportError: bad magic number`. Record the resolved Python short version in the build dir and, when it changes, wipe the build dir so the native bundles are regenerated for the new interpreter. --- CHANGELOG.md | 1 + .../flet-cli/src/flet_cli/commands/build_base.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a0c40bd6f..1d4a1d6c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * Fix `flet build` failing on Windows when a dependency is pulled in via `[tool.flet.].dev_packages` (or any local-path install): the rewritten ` @ file://` URL now uses `Path.as_uri()`, producing the correct `file:///D:/...` three-slash form instead of `file://D:\...`, which pip on Windows parsed as a UNC path and aborted with `OSError: [Errno 2] No such file or directory: '\\\\D:\\a\\...'` ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Fix `flet build web --python-version 3.13` failing to match any Pyodide-built native wheel. The 3.13 row in the Python version registry was set to Pyodide platform tag `pyodide-2025.0-wasm32`, but Pyodide actually publishes 0.29 wheels under `pyemscripten_2025_0_wasm32` (the `pyodide_` → `pyemscripten_` prefix transition happened at 0.28/0.29, not at 314.0). Corrected to `pyemscripten-2025.0-wasm32` so pip's wheel selection picks up the correct tags by @FeodorFitsner. +* `flet build` now cleans the build directory when the bundled Python version changes between builds, preventing stale compiled bytecode from the previous version crashing the app at runtime with `ImportError: bad magic number` by @FeodorFitsner. ## 0.85.3 diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index b1e57a52a9..d5d44a5223 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -712,6 +712,22 @@ def initialize_command(self): except UnsupportedPythonVersionError as e: self.cleanup(1, str(e)) + # Changing the bundled Python version invalidates the compiled bytecode + # baked into the previous build's native bundles (stdlib/site-packages + # .pyc). Reusing the build directory would mix versions and crash at + # runtime with "bad magic number". Force a clean rebuild on a switch. + version_marker = self.build_dir / ".python-version" + if self.build_dir.exists() and version_marker.exists(): + previous = version_marker.read_text(encoding="utf-8").strip() + if previous and previous != self.python_release.short: + console.log( + f"Bundled Python version changed ({previous} -> " + f"{self.python_release.short}); cleaning the build directory." + ) + shutil.rmtree(self.build_dir, ignore_errors=True) + self.build_dir.mkdir(parents=True, exist_ok=True) + version_marker.write_text(self.python_release.short, encoding="utf-8") + def validate_target_platform(self): """ Validate whether current host OS can build the selected target platform. From 68d98e49a663087355a1bd71d564a04f03787216 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 15 Jun 2026 15:35:42 -0700 Subject: [PATCH 27/47] Correct 0.86.0 changelog for the manifest-backed version model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier 0.86.0 entries described `flet --version` listing the Python/Pyodide matrix and `--version --json` carrying the full table — both removed when version resolution moved to python-build's manifest. Update those entries to the shipped behavior (flet/flutter only; --json drops the Python table), add the manifest-model entry (constants -> get_supported_python_versions(), `flet build` forwards only SERIOUS_PYTHON_VERSION), and fix the stale "central registry" / `serious_python >= 2.0.0` references. --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d4a1d6c5c..ef672ef6cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,15 @@ ### New features -* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from a central registry. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 2.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from `flet-dev/python-build`'s date-keyed manifest. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 3.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Add `ft.DataChannel`: dedicated byte channels for widgets that move bulk binary data (image frames, audio buffers, ML tensors) between Dart and Python, bypassing the MsgPack control protocol. The Dart side opens a channel via `FletBackend.of(context).openDataChannel()` and announces it to Python by firing a `data_channel_open` control event with `{channel_name, channel_id}`; the Python side declares `on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]]` and captures the channel via `self.get_data_channel(e.channel_id)`. Backed by a dedicated `PythonBridge` per channel in embedded native mode (4–7 GiB/s on M2 Pro) and by the default `ProtocolMuxedDataChannelFactory` in dev / web modes (raw-byte frames muxed over the active Flet protocol transport with a 1-byte type discriminator). Pyodide gets zero-copy outbound sends via `postMessage` Transferable ArrayBuffer. First consumer: `flet-charts` `MatplotlibChartCanvas`, migrated from `_invoke_method` PNG dispatch to a 1-byte-opcode data channel by @FeodorFitsner. ### Improvements * Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide//` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. -* `flet --version` now lists the supported Python versions newest first, each with its matching Pyodide release and a `default` / `pre-release` annotation where applicable, instead of the single static `Pyodide: …` line. The global `flet.version.pyodide_version` export is removed (the only external consumer was the CLI version output, now updated) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. -* `flet --version --json` emits a machine-readable version document — Flet/Flutter versions, the full supported Python/Pyodide table, and the Linux build dependencies — so CI workflows and the publish docs read it via `jq` instead of importing Flet internals with `python -c`. The canonical Linux apt dependency list moved from `flet.utils.linux_deps` (runtime package) to `flet_cli.utils.linux_deps` (build tooling) by @FeodorFitsner. +* The supported Python / Pyodide / dart_bridge versions are loaded on demand from `flet-dev/python-build`'s date-keyed `manifest.json` (fetched once and cached under `~/.flet/cache`), the single source of truth shared with `serious_python` — replacing flet's hand-mirrored version table. `flet build` forwards only `SERIOUS_PYTHON_VERSION` and lets `serious_python` derive the full version / build date / dart_bridge version from its own committed snapshot. The module exposes `get_supported_python_versions()` / `get_default_python_version()` (the previous `SUPPORTED_PYTHON_VERSIONS` / `DEFAULT_PYTHON_VERSION` constants are removed) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* `flet --version` shows just the Flet and Flutter versions; the static `Pyodide: …` line and the global `flet.version.pyodide_version` export are removed (the supported Python / Pyodide set now lives in python-build's manifest, not the CLI output) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* `flet --version --json` emits a machine-readable document — Flet/Flutter versions and the Linux build dependencies — for CI to read via `jq` instead of importing Flet internals with `python -c`. (The supported Python/Pyodide table is no longer included; it comes from python-build's manifest.) The canonical Linux apt dependency list moved from `flet.utils.linux_deps` (runtime package) to `flet_cli.utils.linux_deps` (build tooling) by @FeodorFitsner. * `client/web/python.js` and the build template's `python.js` no longer hardcode `defaultPyodideUrl`. `patch_index.py` now injects `flet.pyodideUrl` per build (CDN URL by default, or the local `pyodide/pyodide.js` path under `--no-cdn`) so the runtime URL always tracks the resolved Pyodide release ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Stream-oriented Flet protocol transports (UDS / TCP used by `flet run` dev mode) now use length-prefixed framing instead of streaming `msgpack.Unpacker.feed`. Combined with a new 1-byte type discriminator at the head of every packet (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket, `dart_bridge` FFI, Pyodide `postMessage`). `StreamingMsgpackDeserializer` is removed from `package:flet`; each inbound packet is one complete MsgPack value, decoded one-shot via `msgpack.deserialize(bytes)` by @FeodorFitsner. From 95c8e5ac9a05da6950384fc970bd4deb7ef8278c Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Mon, 15 Jun 2026 16:25:42 -0700 Subject: [PATCH 28/47] Bump Flutter to 3.44.2 Move the pinned Flutter from 3.41.7 to 3.44.2 (.fvmrc; CI/Docker/version.py derive from it) and absorb the 3.44 breaking changes. - Android built-in Kotlin migration: the Flet client and the `flet build` template no longer apply the Kotlin Gradle plugin themselves (Flutter applies kotlin-android); Java 11 -> 17, `kotlinOptions` -> the `kotlin { compilerOptions }` DSL. Client AGP 8.12.1 -> 8.11.1, KGP 2.1.0 -> 2.2.20, Gradle 8.13 -> 8.14, hardcoded NDK -> flutter.ndkVersion. - No Dart changes needed: flet already uses the modern Color/WidgetState/Material3/ SharePlus APIs and the page-transition builders survived the 3.44 reorg. Verified: flutter analyze clean (packages/flet + client), packages/flet tests pass, flet --version reports 3.44.2, client web (wasm) build succeeds. --- .fvmrc | 2 +- CHANGELOG.md | 1 + client/android/app/build.gradle.kts | 21 +++++++++++-------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- client/android/settings.gradle.kts | 5 +++-- client/pubspec.lock | 8 +++---- .../android/app/build.gradle.kts | 15 +++++++------ 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/.fvmrc b/.fvmrc index 94d87f3405..01d72c9029 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.41.7" + "flutter": "3.44.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ef672ef6cb..aa4c8d092d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * `flet --version --json` emits a machine-readable document — Flet/Flutter versions and the Linux build dependencies — for CI to read via `jq` instead of importing Flet internals with `python -c`. (The supported Python/Pyodide table is no longer included; it comes from python-build's manifest.) The canonical Linux apt dependency list moved from `flet.utils.linux_deps` (runtime package) to `flet_cli.utils.linux_deps` (build tooling) by @FeodorFitsner. * `client/web/python.js` and the build template's `python.js` no longer hardcode `defaultPyodideUrl`. `patch_index.py` now injects `flet.pyodideUrl` per build (CDN URL by default, or the local `pyodide/pyodide.js` path under `--no-cdn`) so the runtime URL always tracks the resolved Pyodide release ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Stream-oriented Flet protocol transports (UDS / TCP used by `flet run` dev mode) now use length-prefixed framing instead of streaming `msgpack.Unpacker.feed`. Combined with a new 1-byte type discriminator at the head of every packet (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket, `dart_bridge` FFI, Pyodide `postMessage`). `StreamingMsgpackDeserializer` is removed from `package:flet`; each inbound packet is one complete MsgPack value, decoded one-shot via `msgpack.deserialize(bytes)` by @FeodorFitsner. +* Bump the bundled Flutter to **3.44.2** (from 3.41.7). The Flet client and the `flet build` template migrate to Flutter 3.44's built-in Kotlin (the Android app no longer applies the Kotlin Gradle plugin itself) and Java 17; the client's Gradle wrapper moves to 8.14 by @FeodorFitsner. ### Breaking changes diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 41100f0452..855a314b73 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -1,22 +1,19 @@ plugins { id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + // Kotlin is provided by the Flutter Gradle Plugin (Built-in Kotlin), so the + // app no longer applies the Kotlin Gradle Plugin itself. + // The Flutter Gradle Plugin must be applied after the Android Gradle plugin. id("dev.flutter.flutter-gradle-plugin") } android { namespace = "com.appveyor.flet_client" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } defaultConfig { @@ -37,6 +34,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties index 02767eb1ca..e4ef43fb98 100644 --- a/client/android/gradle/wrapper/gradle-wrapper.properties +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts index 38573a4d5b..fba4abce7b 100644 --- a/client/android/settings.gradle.kts +++ b/client/android/settings.gradle.kts @@ -18,8 +18,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.12.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + // Plugin applies kotlin-android for us (Built-in Kotlin) — uses this KGP version. + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") diff --git a/client/pubspec.lock b/client/pubspec.lock index 30b9cf07bc..2d14a27cfc 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -991,10 +991,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mgrs_dart: dependency: transitive description: @@ -1652,10 +1652,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" torch_light: dependency: transitive description: diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts index 23ecd224b5..fbc14d0f87 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts @@ -2,8 +2,9 @@ import java.util.Properties plugins { id("com.android.application") - id("org.jetbrains.kotlin.android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + // Kotlin is provided by the Flutter Gradle Plugin (Built-in Kotlin), so the + // app no longer applies the Kotlin Gradle Plugin itself. + // The Flutter Gradle Plugin must be applied after the Android Gradle plugin. id("dev.flutter.flutter-gradle-plugin") } @@ -39,10 +40,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - sourceSets["main"].java.srcDir("src/main/kotlin") {% set min_sdk_version = get_pyproject("tool.flet.android.min_sdk_version") %} @@ -99,6 +96,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } From 5c057ad24c4e50cab06b4edd1eb9aab2985dc7f3 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 08:56:36 -0700 Subject: [PATCH 29/47] Refresh date-picker locale goldens for Flutter 3.44.2 Flutter 3.44.2 slightly changed Material/Cupertino date-picker rendering, so the *_locale screenshot goldens drifted past the 99% similarity threshold. Regenerated on macOS (matching the macos-26 CI runner) via FLET_TEST_GOLDEN=1; re-verified at 100% similarity without golden mode. - controls/material: date_picker/locale, date_range_picker/locale - controls/cupertino: cupertino_date_picker/locale - examples/controls/material: date_picker/custom_locale, date_range_picker/custom_locale --- .../macos/cupertino_date_picker/locale.png | Bin 66108 -> 66234 bytes .../golden/macos/date_picker/locale.png | Bin 58479 -> 58634 bytes .../golden/macos/date_range_picker/locale.png | Bin 62218 -> 61220 bytes .../macos/date_picker/custom_locale.png | Bin 55839 -> 56391 bytes .../macos/date_range_picker/custom_locale.png | Bin 55878 -> 54851 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/sdk/python/packages/flet/integration_tests/controls/cupertino/golden/macos/cupertino_date_picker/locale.png b/sdk/python/packages/flet/integration_tests/controls/cupertino/golden/macos/cupertino_date_picker/locale.png index 52c139f4a855fd1900334a6399f5d05a379bb001..d7cc84a8d0ca05b5b9cf3d0694de27f1fbc36c29 100644 GIT binary patch literal 66234 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%0DR%n1f+&9qA=*W@c8=HnwwWMHzdxh`_GJ(yxwc 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 diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/date_picker/locale.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/date_picker/locale.png index 48c185d1517f7796af651fe25b3739e1ac9809ec..26ece416dee5a8de4ec5af6732cc219394fd68c8 100644 GIT binary patch literal 58634 zcmeFZS5#Bm8$B98M5Wmf1!+oA5m2gh1OWj7rMG~HQl%3*grf*HiXb3eq}R|w34|yp zh)C}x^d3TJp@opUa?bxg+%fLseY-!$V8oQ2?7h}k=bYbu_gGJp`3%Py2n52c_3(iK z1VYCPft)BmbrQTnS0S1U9!_|xY8jmZ|AJ3Fe+!<|cpGS{Ldv?Xu0SBX5UmIIjr`Nr zuzvnVFD6?z<>M!V9)^WasxCkLB+5%`V%0D*1QW5eHiu!M3HFxi6N_aHE^2G^LHEmF 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 literal 58479 zcmeEuXH-*N*KNRpjiM+j9l?r#g(4l{kzS;C5E1FUgbs=nsfq&9K{^24_cZ$8%�)M0%>4=OgrN7VuoLz(B<99En&F*@M;+g_-4qL z@3X74)F(AuCz+^^g*>6uhX?dDC&5Fg{TXiROLYYZ4S0CLN_&R-a+pbu`tT(JLOu4o z@P7~eKTp$MJut*E~iCc==+Jom96P_CxeR-=OfBEudz7Psz|KQ-41Lz_bVz%>i?H@ZKyxwVy?ot-T&FMpZYIuO@-g03cnB4cAiL}%fU z`euP+D>_(qT4m<;_D+5g1WdvkFaHNzNxM9WpY0_IW*G-N1R)MazSLC7nxI2bjxp3U z?k__1J2IL6u}Zax(KeLi)=(*2FLj35V3XkxHR-cr8N?%w&A-SI>MW0D@0n8{lH@Y z{-M~=!NIVg&<9ODcaOj4oQ+U@xSM|W{pBufEB6ol;ZeB4%uHAX`wKE~&c>E&Xwyaw zVLx{onlkIYL{oC}?p@b6xJGq}RaD_qs+nYLqA>VBx6QIp!L-8Cwaj^&Di+!_0QvsW0V9{t<%6Bh??& zDV{6@7lrzYq}=ZYy<6yLD9x=9!GI|qw4OAlMi%avc2nK{Cu+I5RwlLEx1lN4X+Faz zJSOVTzGDP$`(|k8buQJoGf$dF*iSrzqze0$9xz^8T73lGgeZ{dE{x=N>*CBK?oI?k zQXi4jNh@e9&W8FX2|o_2D^!;r#@W==i*m@_(#R^^j?G#Xqz=$c)nBn+beehS^@%R~ zxmyEUV5&n6ifrLi&iB~fl2xCiPH{U12e0FdCzJZAVa*;Vdx6*#sEH! zRk~1Pm3gR;R@o_%Ax)~lIUyB%xZL4Gzh8RoK1+XAADNYEoci#dxv1;qh7a^QI4V>P ztn#!c?$O8|w-Qlixel!_#SHH5|8rI);3=!D)mQLls^~?zp~l_k)4^oizcV#(11xTM z&8jq*3bmR8$Si>Xt1OE5{P$z!Vps$4Pr$ZpM8>{$B+lkNF4WfTo%J+@y^-q6@&1$|(UD^L$nYyU=@AVb? zEx-q>nAkv;r$ci7^TCs)2pQ(OM#xZs+C9lEFk3k|0@KwZL316PxndpyyaCg)gr-o> z!S~(hp3`J0t+KE0yMOoJ1@Oo3y!nMb`rt3f9=p#5SL95#JgmW=<<#t)`@72Tg?gMf>$gh%I8LfeVdBPt-c6H+mBm z*3+V+&)E>MMPHWePMtojniy={pmUxj@V-jpej(cxHp0>I?WCk6f6oPV6`e)ROmFC*11pV+e z4<5Jq_3PJA-*52cK0ZEqw95M4h~r(<8lOsoc!Lkke8_ZAQGr^DIJz$)j_sz3Lga

p))>nv)0IKA=P&2XsgfDmzaidB(?ttN1=ti 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 diff --git a/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/date_range_picker/locale.png b/sdk/python/packages/flet/integration_tests/controls/material/golden/macos/date_range_picker/locale.png index ac6c92a9840ef13fd58297343e166371bdf32448..303b5132202f967b5c49d6c6f0ea7dc793e3ce0e 100644 GIT binary patch literal 61220 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 K(3hyH1bTl+H^cqhd z8qm<3;-aBB{pH+Q@PAHKi57y7(>`h%M(4nvkaG_IfS*tJ7^th!!252l(9m$vXgs`c z6p*zx2yyRf#<&Efhu8|~9{x?68z5T|7RswHrD<-Cc$c;&>EGp8R# z(4D+>`^Z6#=F0d#w+e-wUyIX)M;sm{JRah8AL6ff4n*g4E@|kjE0H9JhRKbj9G{b- zMHZDO!P#j}onG`iMg51B$~i9T=fwv!C&0(E8z;|Ee{8#~O8vMIK|}rZuXj&hrv4bg z{fhc=_u`4u;Nz9u|1n?H%DKQ*Zh{zVg@pyQOvA`W17)M-EjD7*yEwkyg+CkccX&V7 zd0im4%Rzjlx60mVp@Xf(fRNJ?OmsQMI8>P>C;J*OP!Khyt7pKMp7l$<*!W`CICajy zAd*3&+M@EnqnCBG(o%hJa8Ry{G_k?0f%cq%e|@DoTA|-2ga?uE>eynxS0;nB5|Scn zt@$X1NouWOsW01b=7>U^i&t6MnB^%mE-$j{>~jzNd#Q_)Ou~Lwmyc=xY)VF_<|}Zs zw5c~W=7xMeOTQ`vHz>S`WCIs+)Z>C&oon3om6tO|yG;~)J4O2fvp#j;-EHtlUAE$0!N)xnvqSmF5mb1Z@PcwOA`L{}ibvcE6oXpE8{GxBOKQV1SipHSy`6J|u zX)D*(<7v2}0QV2$y@Ud$-pq1?_Gmc|8F0Qs%%X@T^<;_HuimV1F)nwRU&iohbjEXH z{`RB0s^g4jLdzQ(WFu*wBP~)wkRFi5u)43iO?Ge3%EJz7)!oJPKE0x26r3MaKDwoQ zI^oqT8sh5sgT&^mYSna{EkV0_s;aNjEEvG4!X(4KsbO)n>uB3EbOD4BSt%*wc*WfY zORP@DA-t2ACTeXlJd>Fio4U}dctv%-{j%B~_?qhX*|v!qNIY*hc#`Y8{^KwXaHUoD zHvWyrTa30#s~5{mv$A>?83XxWbzR_ow?Pei4U5`zXn{i4j9Kl`&)inA5`$EPDBBg-vD%Q))MM%{@VggLP-BO3inYUgID zyDG63($7(Ow5xz6gUj!?W5Y4J#>HK^e!YMjtV44D;fqv}SCeLq)gAG{Lh7B}Fk3kV3fb`KTSk@{6Iy$N#% znTr0jyxAy!HQp=A;LKbatSVlg`*M_AgKdY}cN$TC1d~V|Qd33ec%!4qbf$D~oh>DZ zc-^!#@qpn*$-D$)azaH}*(Oo)#aGuRVydO0ax1#4=EOsfcL~!lTWF&g^(y`xY)xfI zP3+C-`Z=~oGx{{b{L~_F_F)JjoAkuA3YN&CF$8v%@5&Hg{Hms={0h=RW1H8n6GjyE}d6gq+vkcT?m~pFU~Tll%jfUv;(p_tYDXgv}bd z{M6eV#N}F0`kz!!Xa@vmJqV1qX>lu0)y))t#Ldm^!6_pmqWvI}zFuKEDPF}XSjqV? zAdPHMX(D3Q@Wf}a51E2=8?>slMEa8Vj&AE-QuF6mdS_Ij_iaI%QOJlZ1T3&sq|0d8 z9c$~EIifxfUb)7iQd3(Sx4n>XO?&@A#OlTep1NwVeT44b#Tjno ztH-Z~uZv;B<3-~YKien;HzCm~J6_ zLF-#&GJ9F&0}@t8;CK@@iuH7yE~d+^Y|6Kyg9@J8om}TX;t;IR!EA-;ZqMJy`pB zu!}=n6VT~$lP-6Nx?0zHh@BbL=}AQ-@G5N$JSel0>&t8X^XFMC>peBZ&Pu)jN3fQc zm)E5e^TaC8hS9p=?(U#Dk)`rNYAiVZO!gx$?Jx%(T&buqktIyw7>Vn|Xsl2Xb@zgU z4#1~Sd>Ymqfo5mk9HE?M_P^0ON@wzEfb}0+=t;{S({J?7Qa*eZ!O?K6{MI7de|}tn z%RD^Z6@v1HwEM2V1BZDAlDf<4&Prmztt(fWiXYHYzIbHgw-f$6QzFh^%aEqPvK674 z!;oJCPsN)5)WUzA%_4>DJ6bpX1y=>dVX$!+Vmjnz-5tHJ1&Mswh`;NEEeD!S-jJ?X z#^Lka;0$IIynn`CpF$l;DR?KL+rj(yI=s#u`Rtn-^~kn}!^danPOmgVP(i*Gzs9z1 zn}2=>(vDXrdsDu)Yy9f2f8hERVO>yGb}oOPGXn?gTJGU{ivQj@g~yiLy*<#Wr@we{ z<(L6G~Ld7Pt?zWwD1*KNBHd=Y+e2Ee2cR8 zJInIenV=q#pvBT+H$pv#kT#d)@hTsTDAu(`?sg|}KU4AE*?Eu~Y|eA8OtM@Phph$+ z>Nhtq|5q<|HRIEWMi_{3mD-t>dyCNUckdcnJd5K<%Nrls4uTeBf{?nNjKXFEb8}(` zhd#ag0dV)`?!u>OZn-J9KgiH#K=?sLQnRy*elPs_#}8iA7Pa(|gp_KGAx_-9;?@7q zg@-SEYNsxg&~tlxsi65M#^n}Lmi{+wLXHBZN^EWGCqsPd;y6Ke6=sSVj={@FxqYmZ zrkF4T5|>-E#O-sP1xN=n{`WR(Qhl^6eH0yEot4+FAy@!EyK4lDetkl34Kk z-o3;Ed8BLOWa}Zl%wzyi+$vWi6f=<>Z7IBJ$9M-opH_+J0w00{6yVouZ)yr1q?isqiC8pH&NaT z(H*V(snTwfC8!DH@b*-WF#u0(emEJo0ZY&LG~TJH)|Q;$y7Ipa>_h)nTy~Yv$$T2` zg`bR-R!Ggno&Kn6ekbNLjmwdKQ}Xqz`|en24TG8@ZIIP%>vuIYHHQG&gx zt|t3Rea({f`ktT5EfKuCMzpX7ryqYrb3bmh+|pF6W#i2>jG&}3)R&DKDYgxZ4Yseq z|2XvZuJ&7>bi@iA4REuo9L2Zpc}G>cZ7$g^6VMD3qt?Yb?o)d3tWI}9R&yi%Bn_sR zg^cPak@z9qUA{Ya2fUt))X!87W)RT*J7!CU20Py(Ih~qf+n;+iX=`a|75P7Y{`|Rk zrHoLTnDx-g{QS*Pkof*+pS~RM2&+KQ10h1scFzFRUfN4H^$4wqReb%l(aV=#lr6t} z^&rb}_CrhNFqM`>+*qZb<4U|sVA=Lth7#&uPxKKr;&@B_5vE4Pe(oqdfOIc{AC?*x z$8m6WrbW=Re2$=BRiL<0I|AP$NMb=JC+@Q*qmcZRC;-zW<&95bndiaPTse8JxyWX! ziB;-(gAJSPlf-1-tpd@aBEfriG@ha@7E|mGL1;dHf0Iw6DJ#`U=opGKfd(F)*DzS3 z`CI0M z9m%El-oFG*6*bl@e40|tu5j<3J}4Dq;TM?QP88g42`aPh@wyxYHk`<-kxI+X6=`#G zWDrt0OA4EJxHeu>he?`FuV7HgSooqHxr>L+v}7wP^MiCMc186Ml%7p;ptI&!iCafY zYv@SjxE3ajZ@ClQ!f&)Tj>0ghu8vk_E@%XSFmM;{T&R^qUEAE*GALYw#H|ie_}szn zD^5yE`S`J5$#&I)w7)&oRJ!k8?-(?>z0_ZBI%ArWknm{<1vMnP7(>bKy}TMJf^ulG zUhNBgtn_G^NpEeaX7rzKkhd6mO=3oNoDWrY>l9CnJnywV19TElMpxe z{X|LVfv=SFy z$ze4?NPGbIw{I?^)rD=+$PmTh8vv&IT~L(IU-$FOnZz_ej-zWo$O^s zRiBUKU#dQezCH+IUdnk3Bdj6@6iFu7Q6sN2B_dWH z8kF#uo|C&quFH`EZ}2gFkXUAlC=xXGFw)eFwhlD%O?K+d;n6slIhM!8jaA~6#f(Z4 zPM$oOoRP7rj{>0^5(^SxFi86B%HBWVYh8o+^8V}12j~e?6ltyM0xO)CpI>eY{^Y~C z_>)~2A#|1Y`7C`mw+fGz1t#hv4?7~JO;u4>@Bqy@CMYavbYU^frQR0AU{1(iuhA0y z2LPX@wm%;P2~N`C_uW_+tahqy<zgLrM}!GcuoZa+r6RnZQ6B| zE`GgvcsF!Bn$hCR%MZLOE5$h@?%J6yhd(sxP^S!MvMauPxr^P+%gZybo1JhDP?(&u zp&aZEzCIOJ9=MG)E}IW~I~2ULS_tS4_Uq`7m8(eSDGPVFa;xvovcCI1w47C{X|xVz zVUcl`o}~=0;P(P_*sKHST#v?mC+&>8HpM#G1}#m{n*GW~#3cQKWWXO9R5hb5VkFfz zECt@TGrHTSu%?VK%X`z01-(@Q`{~Q`W(z;P>M(sBYNu&wu_g~Oqu=IMK)Zw}DY@PL z<01Cv=g*(lPW~eSD(Dac%UQo-p=W}hI$~K%oqEG)?Q$EPn%l=~mfR5i4mTpNn3N=} zyI@RCoH*g1y4r$Dd>|?4*$6G{GCkIRO;7XZnKP$Sr}9wX)+cvuQXBG>B>(ZTUgsB6>B414WB&urwA?cXih+9!w$0>1sI#*(K=zsd)$2Lcm;+7=LKdE5r#ICqT04%;c>8O7ni-tW6r67) zvCwwh#uQbWG#!C4@45dLlURwTq$Z%X>$%76*R7% zG)?5!&H8kKSyVn5ak$6^I~WJ8J`aPE16$`U4eQ)s9csyW@saX7{i`ZT4X|C*vheI< ztjbXdp!l8#AJ1Qlhcr>D<~pH(YXhBJ2a_tRluH5I&lJNHL1W+jEk{cluf-3MoPM{- zj@wRHE?-D4q|mr0^o#f=};Smuo z74`;SO7!JetyE*oD@Xvzl~fG4N9V$nSW6DfeJ0H*5q&D5nCy%4Wbl1vSS|Xf|$L zdE|x&-d(L67|^q{&gqk;-UN{f${MXnIZD~7{%ZsGt#bn>@(zEd2xY*aL@y1Op#rbe z&VQn!^tIko?5#^nOIekiDtJrHhl^G*^}4A<2R?v=&>mAR!JJobu6f^o_+P$yjrqYT zU!V-LMYgDbtrI$3tMR(%xPxP))Y|gFS$d;yZ_eO%-N$N6nM7@bHRQ9iDgX^p*S|Ve zYX5{hY;L7xO2+%oj89Ozw|El>dMxkVX)MkVbGjyLd1R9Vl&r_EPrNR(X~_VvsT4fB z!lI&}!cb64Jh@+e71GI3ETx_tb?TPJa1k0&q@D3%hBItAuP=MF$@pn8*|bE@1`*F% z%4i!_2@nmphM-MzLx!|-g{4R9_=+-o3|V4`O6|GhjcAeh>yp_7Wk010w&is0=4(6! zFl39qGf&gRdY)G~$fGbty_xQar^&Zxy-~j&K4voB-&!avuZaTC8k6wo3W%J4fj%I_ zSQULgii7~#>^^9JOU}eFf&Bks>VPPot~IfRLuhJg84Z4Z#K^={cJd5emxCZz(j7gM za${#_rm3-t^r0c)YOAW-c<+q9EV+EXr|2Q~dMQJI@HB=1_PT!aWB&Q;1#SaP8;CF)n>xrKUVT$rpsi> zhX?0Ct0I4c)6?>dO`>3Ddv}T*&*KshLgsbu9lsc)?2ES5`x;IG>En?8#A&&@7n@55 z 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 aA9_!Bt^ce2LnX@iDLhnpfWH6q<^KZBDD-sz literal 55839 zcmeFZS3r|N+btT3f(21&(m_E%1XQGV5a}RYx+1;z5~_-zQbn3{q?ZKg9TWtV-UEct zA+!*BfKc}2`_8^UH)sFnVsl|Ic{A_4&&)itp0(ES`K5*;#Z`u@5D0`qS?Rep1VW+& zfso!LzYN})NcSHAFQguF%DUv>CxG1Y6Zk)ohqj^|1m4fI27$0al%LD$`lfB7d;*Ne z&MtOpmzsSnY()KiEx(0+RUkHud}@@%IhdG8v-;XAY0?~TRbxfXMJ+c$ZJQk#8~alr zgu@aW%F$o*R@|(dcMYcqr{RfnnoRT*G#lvppx>}K9m9+qHbzf-JQ7BEmdJzKLm-BI zeE)fUe~IV{;g{>A9E6w0H)9Dezhq>H39lso-+nk`O$Q<63Fi=HdcZ;gxw-JDpDd9o zhNzB>jpzl$U4+Sf0^|Od{olgwU1Ergi0j4n0DHuvidV3Tn<>EJ5D;*EaK=5kC{Md&40dv_2YeWR@LQ_7t-&wX}^7&GwOI<5CYkb z%14anBaGOlZ^qu#XX<2ij2P=aQGh^>B9@P)l%t}e9_2^>l$Vj4aJ`}k#tKWWOs?4U z?fEMamS_e`P9`GA&6oNmdQnjnp&X$UGwOGjL^&oI7_F+^E4d&L>z_tBmN^fuym=oI z8f#NKSvN}#fm9kk>N5ZK?xv!QoKmGYQ-wHFuRa+h%nPo6-}wB!O=Ci|=IWPF z?6AS9{P{|Gx3cEp$+Y~kUh$KDb(K$g-s1h2AZ)g;9=T6!#*O`!Y$I0&H)68EYUytG z8P(b$WD}SkCUGoV<#F58;n-~!`$?t@jM$aeGrnnn`5F4iK{wv9=7oYn*QZaRFR4se zTS?4ZMVJ=OH6f7qsqJp3f{f&XAnv)OZ?RgK^2^M8W!ixs)DyoBfeg`pC z$`SNBv0wb`@gRmND=jkq27;c;tBXO`y-@CZ(dTX@^=FMtAb=^*udkv>vO_^T^RY{= z6CYM$O55iL=^SdvmW<3xC$smu=jHa&{s{wth! zLxAa#cyWxr^VdlHk_PJ(!p5n!*0MQzoC&KS8E7l*HK4p_h{U|96}R+Zx}dKyDy=X= zo~L_=FjY*(WWN2%xd06?aGZj{{BJ17T|V6WF;C*3Y#KLkyyr0OUnrLeo4K#tJ+y2LME?o=^dV2n9|32?X zxinTZXkhtGXII>cly|zZDMdl3N*czE^MahJA5+Wt^0KP70-~a05pMxH_9`SZ3v0M| zBPzR07~?I)zxfS4a+rFtnd~>vwtFyZk9hY~Sv%9&P|QjRN#xtJC2v%16=Qjtp~h-3 zhZFnoxd;3FvDXehR=Ty*d9tWmO<8wxh#|1CRV}d5rq5aFRRdi$zoI)SSED}tz3^5tiudwXzF`bYW6o;rH+u? zT;KE#Kga*GM+aNcj9DjfC{I=3vns<2%yEu+1sou3=Fct|7crTs(P<=oA zhlN}v*6I4W&EY)pU*qo7xMZCTWWS||>bFssukF?KFO{8j+0xuQKDH}8Zv(E!odis8mE1GF(IRklqsa_U@dW(skq)r z)Atfb$=Ka+6yKBH8|;MS0%jLA4KovtRUzfi(W@xlO<35A)I%1jmSoJ<2gPT_*##DF zHBRY^;F2p$C7JOzc}l96wVG)3s*B+;1Jl3{aSLw~GU5tFC~-p7it-=Sy$&9PU2y*C z;%Xqll?pza(lyg7NtuFnJXhov|n~#bDu*-#ra1T zpXdp8HL=iA(^%Ob%YV*T>IpcaxL zd9165uD)T;)LV*T7h7Eu$H+J;i-g3@z?e$Z*5KD1LGT0yZ*xq!mEBY{HC|RFNpCP8 z3){v-pYt8A#%*0!q8S50lg!TzJ-k3&c|jX-aP*@*`)nhE9PDUHASHr?zTi?)e|g!* z>oSLOaj|56uQkzHI{&yRt0s%!;9^u~cr?@47j=JKEz^w}es(_nb}~6fWyqE?k3$&$PD=kRRQamMSVLoL=THRK*YkKVnewEAK&dCXm=VS#xxEI1tY zYtB3gw65tp%D`yNP5%$SaKJ`@gvD!3iq*A~q6+X>>oCr#CVW&%xJuZxC6II2i~x$A-=jB+(Qa`bs%Ikd6or5b&+|J$p( z2?>cyI}8UDQ77*0d`T?7SHccA;IhZ2r?l}kN%8i5HMeUUtkj&JSjbR%_1OnnB!Nt5 zhdK;oLH<=z9!-9KO53h)b57ATG&$P=CO4RB;um2U9@zB~Gmggg-=4GN^ogm?4GH*_ z_i1DHdfpn*6%`c;85!ACRnIsJa=Ey*IT+a9sp;tQ>gq)Da&x%~KHm66g2y?`wlqKFp3FftdKVS17othtFc53U{_X40 zxi}{oEzp!E28c!`c|9ey1YTdyAZ3wjNt(|rUSqd0&O6oWk~z8jF2oYPzki*An`?BA zT|PdcqpBX?Q!>kehAp?hUr%qUa*u)yKTYrvjLn=AO;CHd>;8looH_3cgveL~jz1ZwUYXdP135 z^KP~M^3}lI9g8spBGpuTf}QPnBI~#OGrL{tyxh7nAxd!VQQP*ddt+r6kie=Vd8etm z-<$4jSBd_G=Vu5;ncV{ykUm#Y`O+_m6>MO}TQBF{Y8RO9atvZ4?DY3{W10N6ofZB9c! zi+OY#S|{DAAXj339N76Nib-&4^C{uWF)yMSU~2-_-%VdA-DRsD=`}mmfh1oxSP)z_V77E3sT>ka*s%82PO6I{5Gxe^|_&BH0h&$)+z}-epVY5E9b=c3<MZN66_yfYCJpPE!qTI%@U z8@*T2*ROHk+wJ9Re+DYI*NHD%aewDsZF>@V8AM!PM|3?C5-PUDnk`$}@H}~<6}Nxz zg$B9-tL0SofVXOxHu$!lf9tazsT`1epuCPOD7&wjGO<>6?~dbCm7#gySv7}U=e}Kc zZ?EST4=?W+eos1wklOuA2*`liY$R@ z=0OJu`5K}+2E7@)5giNO)4TkwqtYzFa&exAhB|`y-5xz{?H?F%Z>6{!%wnVP5KAAC z{UJqG)i@Q(qz2O57^(ADs!4o%#pzxf2-UdGDCQDTggIipJVxRqIg!_#Px;{^uI+~L zhPR+_aBBQfs-Nz^CEBpscFkDgFmH)#`&Ni zbbm^5W4sKJWFE+=Y;-h#7o;x5+8OuCg@px^XyYo|@;x_M@iqm;06d1zW_xG6*hRnE zuh*axQDptQSklqEQOI#(Vr$Px$J`uyT`|stMYuMw%RAGI4p0s+!J>Hxwh%o%#XdgH zIdI2=&9R=%+hTcjg-X&V^9j(E$@;il^;F>vxc}A=YYKAs zSCRc+A1K#AywZ_d!E>?)8%V@)0A{MXzl*>5wBismTd>zJ`e3daEDlnm0kdYpmI4-K z-KXnHsK)T~Kn@#?46ss|B)5NksF(y_bg12Gz_Xm~P_`Nb1T>vHOxuhuRR_0jjd!M0 zJ5=WzLS9y8`(nJ#{m|>Dd;Mc5+^KSEt>ae?Q~v`^y;rMZ>PwmDr*ux!zD**yRYgQ8ZOxJJ6zPQc`PYpVbph6oM zY&2}3eN7*LE%fI)7^LTyb3dhZ@bQ2ZrDM$4h{qb-6bzIY#gr-{M|;GjvNrjnxGfSSWZe*db&C8MV{^z6{OE| zJz+;h@O}b>FDsgFn4q;HNdLIN3k@ z>ahJY#Roiho<0|z-B*=7E3@(JV!g;39o3aQLbQtEy8irG1!%=xDtNE;!YeG^P{794 zcIRc%VMS%7Nd~L9cdyke6AyJH4-ZcoGK_+`xcqIW?d0yVy{+w_sn2W<(z)J{zv(DW z)nzQfz$`mCIawq4EFC23gH3^p;gYg5W0wnTa9pmq^F>b2%|;jfvhg!Y2`=S^&0$qR zk4{f!G0(rHabOh?=Q}vV0h6%V=bZhC^cG!gg@=1D-*0pnC1Za0g|`HCLPCNaIy${S z5O=;j(A_UpJ~J}zWSmY^1ZM4}OE7*?S~@y(Q&M4hxt5-uUVorE(nIrVnsoho*VIq9 z958wH^!4-2FAjb9@MX!8#s%q6`v)x+7oq`2KYN;W!YPw#pr_I4;?*AL&&mwNB_&3Z zEMT$3TcVd%|Fc8idI>t!7U(w7+p8wIKllN<^4(^v#6UC2b@W!?$-Y}ySy<_xl9DzkYiGA=p&;w_ z#wR4G%UtyV8QVH(5MVEu2fTU_EScZ$KZ4{lxTO4Tw9%)<8#9#$#x5brV6~{=V%dGG zD>E&5;QU`fhCY~U#(16nv{I?Sld6*Z;^K#}SM}OGNx?}Yp&AO7dveRo^p72xR)<`BqPu{;}AQGt-$>c6WD6uq36VOu<8{Yn=%kUSjMkD+lR;uhuX*u35L!q2aM=6qIMMOD?ykc69uN0q8GGkZi+ zT%9&TfhHk=LEJm?>C>lG-hD=amC$PN%mpb^M#{tAZ8Fxz4uTFgW{(awM9q3Elav|2 zbw(6Q_rfR0-{M8NR!jMzulB9Bcd+SWOi*HBXkz*w{g93iXiy z)!|8-^t_F-R*gm8)`3#964BC=1R&H81AB|0x+ z)<<#eNn0&=mIM2mSXUhkm?mGjm}bOl4gOrpJvyqHJtb>gkz^7|?Q;qS`EOxyF@1oM zkug0c9IVsG$m>>C>B;@#_wKcGviWJ7nZY?#lc3Xd%*=UD3etYOXYaE4;~+I+s^@j^ zxl0c&&;*{>ZarSWs{EBvv-dZ*w_Q-LG>!xVT@jlN2g+2G1{atkk3-x2(T88^LY6SZ zKjtk*^40sh>g!FBURz|>Xjea6y5{w(eUr1OsHoxS$2Gw>r@o?0H%x`mVfx73s*9Ej zd<62@d`515zJ31z!q3!4LE)qG(d0n0REu)_STT#FzgVVlt4p!+$e@g}QN4XaPp{8| za2c@wzoh-|m~{MkKAvP^Y+ORv%sU$tw6wH%SvFinrlB4}&eKazn3RX_1z)7@`G&X8 zk2Lh{hD)EebbPc()zz)jfRsBP?}m&4^mbF}>*0*y*E_ML$wKpKp9fT3(}4g$P=IiI&{l=P}YdxvGPWZ~6}y z#H}Ou{Peu4;f+DjtR(;~he+RwZF?CQ*qq_#4VS{7dZ2462U3bRHyvZT5aZ(y+1X`N zgb%+?(U-^_`OB>1qH{Bk79hL zi$X(5(Mu$fsp{HuQ3FyJCj2e??x|g8=aj+wbs(2j9(=$Cx0)jMKmPu3d9B9SiHF0xX%y zym)tVxRwJNIlzo4Iw0U)HMc&OsDjNoQ~(+)H~Mwg9p!~X@#$!&4twlnASRx?tqSbA z5r{tBdj?ib3>uAF&zn76H#l(}y2~sT@G1@zMRZY;R*^5FndWzx6tBtV|ICt^ZQaz4 z!KdIJReP#wy+OV!&9BjyZdrfe#DchmZN6ynun3z8gz|`ss{%~~6g43K>Hr{_LdgY7u6??`HYeN4yA&5h5gdS4PmOU9Os?IT-wdp3Tnah5)McW+P7;^=Yn+2Ms{ zC9>M_*j>R8X4DLgQc>-R|N0gFC-k|3q9V`3hbrS-B}_NM9ixOmHcVr1imE_j>>V9P zhl+DjE`F1Vc}`*-jR@bVw-WInFDE0Oi_)Q z$ys;B#f|CA1HPL19SAB9x5r35Z0uW^4xTD1P>DM~!G%|QZRpn??(OY0!vX3BG;av? z`;@Rh%EJS}-Z^{cKUgH8t1bQ)ntSL0L|k5vk;z;&FM!RJid>*NU0Yk@`zEDi?{+7n z6{O4cesO2q$(HXk+ zVhmszy;m=@a&tM9hqvFfOI*&SAIbMK(JcQF%g0m&v77C*j3 zM2rq3kHL6nMK1y{6ZP_1TFl0ljz^#7i%VcS-rjiH_^8jHwcg0S-xzbFffjXoy9M3%KV0 zu(0_*A*TM{d;XtNv2Qwg6Uk74+z84!W%u(|j*iaG%S$I2aC$1LdnE}2w}^{CDmkE_ z0F(b4%J~}yCkMyh+jGI;Z@Z%ZjZbiu2&(K{*@?@|{qr0Eh|H?l!3nN#;%}n>>i*XY zfZ_wBMrY>>Wo=JS&zI*-fQ#(0DCfF=|3#c-wGj<-Uk@3CO=9P2cvkso5C6g6oS5&X zVG)cgCS{;91N_z>5#Q}BXZ%>1RVA{IuOp{bazE>-fI#5zI5zl#41=m2c;IwtYH+~< z1?}l0a84oSq_c_%pI)^<7e5cumJ*o5=a-2IR2JmU9Sbt$;WkR?(TZhrkOY1Dr8PfZ zx)2CBJ$WeAl8>;ZLu{0TENG&KMlt3$%0^ z6+%kbeW%eY)qc~p?cr3hr*i-0$%E4-v zLeBo`nJ+me{QwL2m@Sllzzn?gI*WvoK)xn{S(6F58GAPkh^n)!K@*!#jl2-dwLF}h z9c2h0d^D^j8lOR(lmm96{Ot+W*actOEHlw;LkWBlcgcyk+nNs!FWv{; zhXF={^T7lB0RvzP$}pLZeN)vZ_;$(?_*SxcnMdyFCvzla{4~6NeK=3`7u-Ngi~gsB zAYzjKPbT!l`~=!@HOYz2)c0M!#`od+Fe1n&Hqu=38lPFIGyDMp(11TC9EF;XhE-2q z|85I0!7BOr$-qC%{S3r=gEF_?u>=kmg0Vn8aR{ZV_q3jB;}0=9vn_#w0TYiaRprQT z#omo4#OM6br?Tf07aoU2oKZ^Ul91gX-AqyJmcX$o9Q!^Q9@V|L@s1S2mOM5-zD~eo zXLy`=t$L<+P0;okHr0quEG&dNd&Uok%3?L5(*W4Ce5F5~aa%Cx;lpXHtcse_AXUu8 zy(}+?IxtMgIm^YQr8xpV@#Y_ikN1=b06Xrv(p6dTWS*;o?9IFD4lFntTH1#~_h0ZB zz^?k5&KKtAzlb?obOS}3S4o!_cE*&3hQ_%)M{MKoEgVbkb=swmpMT`!9E@a|Sb5V! z=BRi-=G+g>9eghHMJDFF-2*ENDu@kuy^-d{#`h2eb+nMvR4ExNR7tS98pa~*(Rtl; zw;8)0EsovwlPQ*fjyf4zzX;PTdly~qpK@m@*O0;9!Qr4$`gCYRh>}@ssRlD!)|wq7 zeLPySS9bnSk3qusCjvndqJ%IKv6oZ2DhZxtZ>Wpa0a=4#!I^rE#Q>l{yPeWDR#MhH zZZGmBCT4^`c;_0;b^3)`28m1XUNvIv!k?2E7I{s7KV;{Zgc5Qk$lwjI(>q<}pl9b6 z6MK;qfNdlyx|Y6(6tBKD`taey{mn`b$t^~3Yd{^m_~r0L7c4*M>Tj|$hUoS6bqVKz z?(SWrAQ6Pz$IHvB-#;MOYlGJ;_~(AB&&vzjD4`vcX8;}R2V zFlApunl_%A-4lyp_r7J(@g;#*gL?Y|Yk)buY=C*UmxuHAan-2x-`?I)nDA^afH$N| zP+r)(_}@1hV{b9j(P!6%~6S0U8>J{(JiEM=k zI{#^(`Ac+M!otFHf(l=-W&vMW0_K87&e@4XG8c>kEr+!j02 zJQX|_0(3-^=z)Lz`nWBWpjG$x_1Ut_*$skw$CoNP#TRsPpde$MJ4cZ!TJ9zhcVzS4 zmOn2%*nN@0gBq;4?G&+vjK(cpAbgmY%>xE1BGvhDLy#EOD^ICCB4z+83DqRt5r>PQ z<}pF{;^Ja}r1O}?eby@nqzw>oQ$dH7>*{x;r7VMjAQ0QK@ir|ew4tKnDQY}HLX^X_ z>-ukY5Se_y-yEE5l=)8!+YLVlR4|B)m#($7wG6R#-OlKT`0roYQ~UDqTfs7b2zT*G zCA+~i*qY&|k8PDUZ#_RKi~pLjm1A#gV5DChuX}NRz0Rbp>e*|A{Xd|@!1xm%IUz** z{4Y{Y@dv{Dv$ZqM2I2(N+LtWVY>I}+)Ys;2O) zdx>-70WAf~vqBvwzs0iPh)BAW6&xGLW>HIc`?f@%$q}K}921^B$~_AU zyL`5Gac0OURMoTs3d-ysFjvn3Q=f-|f_i1GJ7F`vd(te(*K%RoGL+iMMP+4(Z<^;L zi!GoQ1!~e=LWB0k$>YtOpRZi$-bgGsJq^e!$Sd0XdkZrgoHcd{n<8<92;Uzhq4Z6I z5CiIEPF(~HmOvP9>vB0oo7NF14!;|i1@^riAf(*P^QDJ6_HuL8RhNhV5$bPva$vp3 zZrC>6Yq zbsrjm^SmA1b`4BR0>S_YL1GI(gKOy_&c2oVqb=}#*vjz8h}T&)!v1gUc)?vw6_w#$ zdI8VQwStXvGL}(L^hmPQx-GuoHEuC^+ZDN{i~71W@dWpap2}lEbJx^o=GNR72-|KI zZM3$Iju$T7hl`h2z3dF{)qEf{&!v4cw(alR80jrHe+`Wr0@@16_gJidIz08=etmXQ zV~iA3_p!Gx-l{u`X~3P6RXL-?V!E27FGyN1<(34WP#s{X>b}znyG)h_OYW7LmuZ<} zSz>SBzI_T+hy1$Y^f5;o6_^D2f6!f0wib+SS|r5N7f>ai!9Mf6(HnDE=K_ea`rhb| z+i1HlF7zYcW}tiO%tq~h?W@;qgnNhhT^c4AANRwIYe7k*_Wk>J=HhxmN&kIau+{@+ zoK@}Z?N5WE!l1bC%gHjAAjCOfZp7S*HVZn?Yu@W`WI?hqedH(BzEfCKGz74i-?Z?X z)1z3!y}v`9lJwhV8r}r2!gS57Z#xqJw%&E9KYyP0efMbIYP=wd!PD`cE76&Iz6nRh zaAf4qyA+Yx^(4ZIi(o^573NU>_wQdfHeq(&0f9g`^o;WY22?k5CXhqf(C|9}{DdP( zLtYM9i!^L#SmhRMEE|{&0a^(CpCH_0`}mH|9Qtoj>;a(7PtL89QnYmxeb*CWbI~i3 zWp#DYtwp#ObucP%=ss8L**qWLLjv;y($Dmv(^S(WKjOMDFHCibn~gB?1h_ZrAhy^0 zuM|bZwpmRZ9rx{9On6S@b&%Y9ptxq7WR0xI)nL-xo3$S3+LNPinvz?ofyb0!(ous1 zSlrMswRo7{4rn|lFC=tM;Po#^4AQs( zcLi=_RPgYfe=Ja`o31E=A%)I&B<%x5kuX>aH%cZiWi1l8z{4#h+HKYgVaDiAC7^47 zzv%Qmdg1L|(}kg+py0j1+J1%X2Fre(q*R9CCgc}lib)@RU6KEGG~3Vz>M4ELNq$G5 z&$ORSIP$=Aua4tCaadbULe=;Hs1^K|aAdC=ti9uXV0FAbQJO7@@j0jOE6gpNH?h$}(BjQNk0<}>Rj z!6Ye+_2Ye|B=Vkz`1bqvV5l-sTh~xv7fROFPv!DK+SZ5Vp$W7(jRJZpF{I~SIy%SfYgf*{8pAIg%h35S2~lsOGK-6$T7W37$Qr!qW}cLmrcukfwdvjXQ1ZIg=1@*J z_W?wP<9LWNxDXJtk^(+_^W?lg$hbqJ?mRK^mY#N~^2Qj&yx|0CKM>m3db~B1vzHm* zf~px?JR~B`^^S;%G0@jf>=OJuEdfJNc4<9$J+`jNJucK zWRNGWI~GpUbwi6mZMUAwTy-Kua#E7B?`(Mn7)()OwHP2ij`q@z1O`bePc?X@B5Occ zPz_#tJqK;5x!)i5kJ09JPUg+lQR8K^S?}MoGuzizm8xkAG+j0xI%9odIjdthW3uS_9>VFlC0n0mz(LiVo6t zYo5ICR_tp62uX;q8(0@~{RPokze09=e9T~EQaa`^JsXMK^akHTH5{&&2wL}Yn^o5W zM0$37v31~!Rr|I!(!ZtK_w^TWJbVa$!wei34nD@AOptAgwp$u6WZth47HAiSgj^|o z_pFC+?@lbBq_bryrB4bk2aCKyX9%b&eEe8pghD$`R{Phk3|z=$2AtTHAW{f9r=66- zX{y=3whLeTB8tIIMe_V`5}mmLL^tcCc{(6#jr2>UdbNhI(b6#@R-T03^OYmqlJ}4) z7SPZnW~w4rTksQw_F*gW?lOrOcEBW?z6z}bbw!XTGwBe%qaV`Gk zjzXzj9dE_o_Hq}ju4kSMGtUhbO`AP zlZ5X}08KrREI`J87&U-R1$##EL3)JN?zJiW$UV>h?3MZff;(ck9Od+)g*%gG3-u zUusG`EG%?oB~JZ1kHRWwXhh%efAv$ugqp@JlLHcZkNEcV>8XwI$r%HWe(Ao*v{GlF zK+8T*lI(zy?#t)T`f8q$78Y-*!O*U*+7gO&FiTFh+Z9*_U@nC?J6_l^$|VScbNKUI zaf9G_0Od}JX`}ZVKw@KH&3i5>)j#a+Nq1lZ9KMRGrUB4`-5b5fiVC82+_Y|OXnPH= z@B&lIGz}ficcE)WK%fIVpz$@7OJt@`3zd?!RRDZ z26t;u+csVR+doBf$BKbEITmuZFtlnOM>Xp#}IJ5n&tWFR}J zR`OrG>k&%b=@g!z4M-RC2m`VSCIbd&x91vw^k5bf zu5~cTM34Z_1vr;6HxnG((j{yS$~1VxdY4q|4B(GXPBv)3bv%(IaSwjeMy5nEifc6c zqsca*^z`&n6iVhn$9c#aAm!6gU*DM&oZ@LU;E!pRpIM!(EEw1zg0Q_JJ$ykaW4{x% zg=@h{TIJ%;GcvXAiF;F>j&j>Pyq_{~bI^~@)H54d1IYJOZPjnU$mqG+1X_$@8Y=ey zCHW;%4pI|>1LR_n6%5$o^mw&{0VqKF_z@d_@1_BP;SG_}QExpuEeE)b&;cw~)%L^B zJ#2`Wo&@+d4txd#f)~Q1uh|w7^0TvR@v*{1&=~<^jGN-7bAY^W>Vpvs@*hcvi~I4J zs&nJlZ}~Wm@F%xqN2V0L!{tFVfV@3g?Lc?1fhd!AVVP$Iz{+eOg|?UAZvy>1TfW#D zvjYPkaIKQ|GT_~#8+@?vdrrO9$zQ)0fzon)q@WgvG0spFSf;7V`Kitnvoud7>y~R~ zlTC?JiV6!~dU;hhcx_mFbIZu+>gsYm6f^pFzt0RD#qmc4V>A2s*t1q;-xmQtL40DO zwKu)YukF1Xpuk;Z`5!oO^glt81FzgoTcme@gZF<9o zz%TsTn_HIPRsIqkt&g1v1O{Q|=Q47y9DwlnATy|xV7r>EH2y$FA=JoG2JA*kK*Ar4 zW=1B&C+7y8;r+d4*2iX*;=*n)=dX?A^W4ARIgmgMx!VDpBm1CEQ;Op|>c8}mi|biq zZdOImnOO62rZA^6pz3bRS$-naZa~lk#x6~=8=D58BH7(-;1&?jp^eBh#cZM)4SM9H8}(YN+|;)V{?lJSU4rs0D8$D92^jY0S;g*tzGAER$LsoQ|7j);Z&L4?2ohm-F9iJ(xw*K0`O(J;DO@- z28qBP5IQ~3ADG{_@l5orYDCa(c!?IrZ`knXxUM1Jh`)LN2g1q!sne9Fm5q(Rmm`2= zEWxL&#qc8U$#dlFDIE2i1_BXCXNpc~0+AXk&@=}UNV2xU&q5f?>x!Z}xpMaB5b8D| zmzh$;`I&tLeq&?fVEzZCS8kKZNPot-g=SBNr2jKqy^XOWdV9Vdy0%P5&?FtU=MuvU zkAGyOq@>Ue;QkDCw}7y30OZMs^bTsQG{t0YzB+qS7GqkqDqrh5swH6EJ36?KZN`jW zcPJC{&wxNyOB36^a`dG=5m;#dN>FBOS0cZIYS0IDcq>khl8E~`FsJu%Msjf82cGel zUjNp9wC9CaTlXa*A6v{yl;TnJney~iSw zlJb={1N!zV#U4mN=Q+&m4V09Y>c&-__AHAnM(%w1^2OcGX0jrWk7#~al~1oV=)rvw zu1AlCr8A+!$EV*krH2&g%$91Q#U+~iZ0ae`l-t8&Y!NtxIDq8#PZ-O@{apw~dGrZm zd~)*Y&JljA)c{OV;2|DN+dx<0p&9Ut!;2YjDb{DUKLOu9vE~i-J&ral{H?e)k`1BtZc*{XyIQRXX5gvY>yag7CFSf&Nv`)UmO# zVi@dTy%iw7lerS0VLffr>H?*bHqdnT>#zw1=0jjb6Pr;@g|p<|^xS2fV?p`*O2AbS zaJ~UX1{0fb9$?+|ld@mpioslG7Sp{(8#EWtvWIj@`1*Bi4{AyJ?!K}zjIwNR306Bh z+!E-YK2V_Xy*TLXq`EOZdMfyJYvQwO>F8cD1Fi8w{ehj%HcC4C`S5GvC=ErBffNBM z-!}9wcXMM1?)1H10tsPzF0w8Gjgw;pV!V}=)tXQ1Szv0Ho^IXYprxf{oy2VQhB0~( z7}H~Lw94AR;002P=!dR;X7Oh~CI^Mh$L=YQ0i$D=vT~PyAV?*8z~U9wc{z`x030zX z>JT2MdY~~{<7CXK3~IS??t;i6U?(o(R80gQYkj<=;EaAo@#GP|^p8B%q?%V3vG3Vc zRMj2aT0t4yh|M^X7xCT{0hXP$!EAZn4629P+S*k??U!u^UfBS8_p2)@MD~;Gk~olH z6aYy|V1*OYk{85)^A*{Lw^EE2VJ#>qsGTwcDvJPHLi)2m{RRh0+Igxj@e0_1U~}`} zvjhj;?}lQF`y+arZ}e-MrX_&=|RBLc>iC0iv;j&qZ zGin!D_JIAvH7Ex32w2QQ^~=Us%FD}v&8gI&D+o*ssYd5-;H-_QUq?a;etKFXJon1t z1N+mX9jOuu$Q)znz1e#lAMX=uCmXc{eBTI^)yc189tlvvtjMoOD65L!x*n}xbHPIA zvi>!sxv_;_2~mTCXl zHEEN=!d2TS7Qv|!vAEmc_S;#{|2pAwVk8#=B*3DT$Z#lEDuV5^@R~LKHN?lVUwVTN zhYPTO24nZTsH&=}!`1V|u7D~!;PszRU%@Ulh&B({s`<}asYaLp_x~du`w!wEP?Pl@ z1%OxfLhm&-Ij0CYL6Hw?Y8Hxx@qsTj_kP`126EzX8RZZ-jW zRM2&fEhdL@9A;iNl%vQaI=R>IVOv9QObce*IKk1*{drXP{MOZ4<{*>%Nq{LM{aB5U@!0 zWdwd?30`3BcS;p<8vRRaJ+~(e^mp8h^pKU5*B3lBnt5!(*CMf%hQ~l<*IAn+_O2;yD4b1)vEpq4&Uce>R7gw)Hy8B-r5IYQpqd_x`sRptY(`FFup}rFv!0aYF|1 z;RWIrur-t3fYxd7Tw7?8E~+X$`}X3f`%40k=h60?f8-%KikSm%0ou0gN;5;xZL_w3nNDXp)`&+l72%R9dA-?hFDLe5{u?6LVOy=Si=RaJSzs=;>AJ#j?-YsC1=j$P}Kp95*4s(|)zs^R^| znRfYo!GGiNTxl1O@3vq!{n?$90Ck>`K&S2=hTc+MABUy(?3J~is8m{~FM3ZG>y?;f zpwW#3GUx~>I4!DQdNg(1l$G_%>Q2_4SV98dJ6?gk=Xl^|gxyeZa7a!OZl3@w-d_3G zdTWP9YS@;t_U~9?V0rWNG6VRO*qv3|qfdVL)BHWcd3%)0irvy=&m;&1vSpqr3i@tv)26|sDnF|Px zz#}b!d{u9s*R|K~W#t0i8ITfp^O#_JyYAs4%EW9^brllw&6g?;e!ezl7Wn#3Kfp@l zif{hddlT8V)<9W}ES%<>fZGF{qmkBb4LXzD8tB;|_}nHJg3QO?mcQ~``L5&H53H#f z8S^_$@$tHZ@shjGWH{xQIec=kkv9JnIZcY zLDEO4<1Rywe(_22Mj;p$4}|Wup9Nl<=XwUn{`cS%MGy`-i?8Yf5Y!atyJ{{vMP%UBq%X)B~QOKk{j6&FY5LwXOef2E(a7$d`i@VF>QMz0=@s&q< zB&2uWv6=n}Taeu;`M_8kl?wmENDOeNL%(c#5GWrHIC1$E(VpsWo2bkRn9rs;3G4=hX(y)t6qR!@wrpjrYQAL@1d^N+KYRnkgUfh z%{uRUX?XP2F3tvmQKpx#0{*lN7l7v5;`{v$w83I~E(1pzl)ij2awQ0G0I@z2dcc?t zf&6dh74U|DpV#28p@5f=cOl?3<^TQg|5riyRr05UdGz$sUP4OB`j)mU^RAB%k!uvH z@nWxFtsa@@iN(mulGX@7C^E5K0Fjb4vlk>`8{`J~*@porz?3V)T6sPjMUfZ>U z3AgZt-o>D!dBumO4W8L7vsC+QNJZnC)0I)&&sUG8ex5%lXbCS6bK4CCqA8~`I0-QR z?P4tp*b9r^w1;i+Q6R1WdteNB>|$)|g;^56Ip6+Uf{O#VX+8#-Xzuf={;1Dqlr{BwnBEK7E#EUx$bbnGbALJDB?@2v__l99x9xR$BKlAp=j=h&}4Ha*REy2lSSE z5_)_BW9>%^vp+`Af{pd}$?-){A_ql)Q`Th)vAi0msfI?0I(nji*AlK@(fWLwbGOM{ zN$Hc$I?>#BuvrvFB=LzYPyCksEfM_n+QEkK;Lq&#r;gIl{X!w!S;>W;8uMw zkoM!HhWYrDlcQB|kR+bt_~=Na{NzUoII3Yk^g|&U6LfY!hna0Pk9!8n2D#RVk71#p zTEQKxq{{DOd}dYR0L8$q`jgY3Roa)25PH3G3k5(Vk8pRvwQ|CAqBoR_b&D3ZZqHdx z%XYUeEqMTq{p?VjT+w*}i!H02ff@&$2N~77=W!IdFL!V8U2}dRLp)dh{=CdS99LZpVOIMxdn?5{;j`hj_dqh5vvL$#3398I zKA*7SLo^&)gNv10jut$a#e*cw@rjQNwOgI13(bX{d-eRK&CR_wk4iyXy9YsaLASu) zm)d^X6-D)TZf=g4Ih@b?xOO8(x_7y-kc-gF4C6(HXlN}DkQ>8psY~8uHX{PpD@HSY z5|muHiPXJ&58T|ystmkzh0 z`u3~ecE#|_+rFQ4M)$9(A(e+g?GSHYS#+W7XDWT}4DddXp{)Nbe8`&4wsQ5kxwncY;XoD$=C)-g_@01QG&!-T!C)JNwMp zo!K*c&b*kjuf^QS{cYFvX?e`Zp>0;IWUr$qj_?OWm!iA8oQ@kof89tgS zufuQE->%EJ(R&Jfi)p=|mQLy|!}5DkAKE!P{3Ur~trD|xERQgTs;(lr1PE9?&z+Ky zq0H^^%S-{kXvC6e_!m@%2r8=S%a<=UIkh_3W8d{j0blNMe9etNc@VpsFTJo!BwEZz z*K{v_s%MtDD!7lhZ*;A-%U!-|mz+e>+*7V-gGtL|&E zA-A8o8XOS&s@3YW>&)?!#0Rt@hYpGAf*66{da*#)=hpo;z+IYWi1@|w!b`S!W*v`K zMm=XrdHib=r5UXw)5t@`YwdR=q_e=v9TBo=0v4HgRVi|BnQq-+?Bhm-z@>x*eEL_(}?rJ#_iEG1i^}R zUfhal8f~k7eplYr`%%@0){}n1aS#7qJz-`S%lE3)tUbQq%RDriS(Gax(@W3!EuUmq zM;B?AS!FPp!-9wsM0tHX8XFH-uUi5wx_W%|!lAT5d_7jTfL-)eYs`)7blmzCFT~HT zu!pg$YrxqAyNXop_R7#bm*t2@VzU7BX{FcWZV#cX1jDn`vu1m>MFGQ^+cK)AZ85Cm zy5v9V_9}UgjPo8GN|IJYtZ;BnAsPfYszPTT=d%)_02K|aHL@RYKYsjp=@uD_I@rSn z#i5C8u$O`U*14VmRkkJOA5x3qazUNePw6{T=`@Y;h9l5%`*TQV07XAmZ%CJcXH_Yt zbgvaGyT>>1wAa7 zW@QzX5b)39YiTh+mv8$xSm_#^da6GUPjBj*7YgIh>@6@?ja8vO)TWg1ND3GlInZx% zyGbxY8`165=zdWODFLJzz0}g{>F*ldA6fpPoYmVRE95=Oympa>zL{a!Qgq`FwdsS! zk@BE8x73#j3?i(oPvKzc_;|jCIqnzE40f_F``#0iwf^OK7Y@a7!awW9txc}-R*dE| zV0t!^nbg5Y+8(5rt1mY2xnt?bks}o39jTG*$b5$miq_3P)H%*CpGO4VW=e-j2>vNZ zd^5d@yshKxF-uprjlP2k*GKnc)y;dD{EXKvKwNUyW!3^2W>zog`fZ#>I*tED;gA1s z_`m<9X_Wu-Uub`o8`5VgFHln#+K%Zn-@g6VCj9R=uB<3!es$QMMpE1YU_)TIZ}(s> zq-_BT0JX%&r%~a@4%tY7GgYQ~pXd)kJQ?5ulnW6?EoL9=%~h{*H@!qmpJ0aRX*(b| zrsfJ-_CB+%5PuG3j*FZ1==gA{Bhj_Bo(Ppep(jbex9@k>`z(c6VG9U*TI|kPD!NkH zg4Gl0$@P}-wFXP0T6C}VutsRwKq;R#*xMxGbW^#h4n=Cm@ozU!-bT60CX(ByQHnPT%bK+vLd&$&5%4`h&2 zs}F1|%P?BILwYyhCqgr~$}6hh`KiL^mJ9_2g@Y_tNflJ5t|>fKdyi}@`F|Tm*BUi$ zV^2`jndX}}1pk_SWd)HIzm}Bjwg~ZCl0dQnl4|ViKw( z`H8LL?xxAPt8th1hlrcpr4=0%AoTPtE=bF z->@vBZU9Q)exDbhSH2NAYFd=P)Mq3|;1-qCT%tsLh=$ ztVid8Fpj!so>(fr)sk>FZ`D|ux|2Kb?xWj3Bw>y_FZN}n!I8_#%i9T`9R`3;x+jUU z)Pkg0hIIv@-Aq$B%E)9iK_x{Nsm!s1t3$7RGk6y)RCV}JeA8x4K!jSc(R(+psE5EX1s0H z?h_36UbkTb>HGIPg&qYdajuDlf4`+%jVrzhr3;qhs(@8HR5Z=_IS~}how#KXhLw5j z{s1VNlEF`Pt2Gv3yM#LRlAaV>?0lbC*PPzaDv2b2jygF;=2;@;p*&!q50o z1i*cn7$@g5kP51o7p+8`ObwQg7VbG4A8=K$Hz}yd2_>UH@wiMV80i1yb?udC%OxoE zC>@m{d?LfLkDItNC6uX@@K(;!hlI3?eLg^9rYnw#Q{tHLE2PBorcGHEg%~A3NswQq zrpAQ|pSla+27-b(dbV4>yUbK4r4Vy};k!-V^Lyhxhtbntq-U1gO_b(9%I=xJc~w46 zR3_xB-Mxo;<<1?i{KM@Mel{UzjSXsl&kgULfvFLdp=NnDNq2~4siV?Y#af`zrf(y8 zN*ID-5YYnAFgf~_vRRtBoo6cbmF8O8%FDZNLYjg4+Gmd;-L;NN=~Ue7UqYQ!!bY%W zZCCfsCh>ja&{Sv>BgMRZ`}UsAaM*dqsi`RdZK%*Y6PLJi=~iKO9x4HqTsW{)hmd`1 z=(a*cdbDlTK#sH9JFH&0N&h$a%BpG4?y`i%3R`C;CR!|D%U2yFed1m&{79LNk%n$! zv42Ag6+W#FsRTKOlDW_R>Lfi0u=2%v|ELK?&?MqaxB-)lWiJ*2LFjJ#9Y0hO0F`!o zg6-nm0cl`ZU^7Wpa<^)BV;rFa4x0Nix=@a zmYV_qV<|O#3(9~=4hV}{;EJuKM|7#j&iV}&P2Z(+DCDgg!kTdze(p&cDV{C5AvxOp zj!JyKx9mEIZ!@|aW1ADw32S4yAdZNuX-EUBlu-8CaG{(CYUOXi%D1uNh$e~sog^Wm zc0j`wBBA{U;6?`gGVmnGjD}>bm=jT+XO@2J5(aV>JNCBi6&Uoj=>`|Vr)*>><5n5*0 z)iYsdM2gKrhr$tvX4G>{&#_W7mv*bUs|`NZwtc6!&J_tt=XfG)Tf1=Wl2l6*zSv^}&^QSdK2g-nwGrdH4n*+N zSP|!MaJUsfN90~;x^l&lj1{4a|1*TF0q%UmniI&0Svp#1DJa(VPbYUrs{B>H>o6s= z+=FZoIFj!6uV*qc9XhtvDG+}YITYuPrN-fD9w05#h?sqiqD1833_z^TK392q4Lxrt zA1N#q@--$VyR{9~`JTXHdbx$0do>=ty=>T8e_Mu2TXb?0J)w^bquk!^9A>d^%zm=Z zr0JW5R@o=M{LY0^1>%}F#=*!rE*G%7o&?X6T>Dme#!fdZHX3Ryy5}{dda~K%HYSNM zvUt*(4!r_p^l;DP1Gh~Hn{0a4lB+f)58=4sXL$qh+3Mxn);$Fam6@vLMTnwdw|K1i zjSMzQ=w+%bzz3=9X%`(1S7v88(MwT$DK*1o_M5!je+NJcj_JQX8v@u9cANROx_ z3E~t92dBp*eb(~ouXUXh9F8v@y_T@Ev-51IyQ;KE-)rV#Pp;_D za=D|rH<_8GNZV_nnB3f(H*W$~IKLWF@fNo8(MorG+ZS)A#c8%MHk~J5dTyy#dAOm% zPtt9_QVuJGM?^p^w9O9bjV|SCMOIoppEEOf zdwBux2ZzI^5v0dx7kNaQHb-DV*?%?{DYe1viC?eb8dkxupl$cuwJXu;Rj|A=!}RBS z%G)hBHSFzA;un7V3_{m|l=iM%5E6r5>ghu~n%}S13~MB@=m6+3+e&^6#VnnE{&Pab8HVYGa3z3YVI(4zX-U)cTXJ%b>I2nv;Xf1c`w^{84SWuRG+ z|I##u>H0W)AI@or`%HxtTp0)jxz`!d@zSC4Ah&uy@k`Q^#l2lzNOEhe@RG#AZf@5i zo@SRk4Eo4y`>8tQ6cmDpcYe!ML4C$-aIDdyTS~;&TV_+BwMtnPD`OK75NHyBMhD_D zUw~Fn9^{ZejvOt`)K^MVh*N_NrD|KullUy5Zz>1GJ@H;-gXT^ANIA+9{CV0Nc!~h; zHBWw<;eMofYE->#*%}j+?;$26q;>1b*}f0wmV{0>rh#1FqJ8Nd?=hw5mTxZ~AiYr< z#!a~E%qw0miCfZDNf_^at*N@eCiU@6(|>haRYHFZhhTIa3fBNP=J%{~YKbL%s@Vp0 z;3bQIxYX+yo&TVfgqpk$LJ&94f9d0NALGF3zyrD+g-ZSTg?|E}ja&*8Sq(0HMu)Vl zOMM4sUP7FDMQTuVwfE;u4L>h@b&8S_0eoybCZ(qbrW0bqg)P;w5I zm?yanL;os97|tio*957TI?OyG(~Wz<`zUzv>xeH;AT7EwK9B+9JkO47p#Z+stoOgri`ZnZRuT*-PM@}SYl)lAXT}JfLjg79pC>2!

W3#kTe@!!1*!j%L-1;#92K(as^c{CNahv5klTAFJ zO1(v2G=oPi1W;_2LI-({jczozYNUB(XcJIBRlBIz+A7>!|0R z@?bRq3T&`{G&1v}*+G7xIGgO53j1D^fO8o%u}lE?;k^=y<2*JcW{a5R#JfNurl)PI z?6^5CQ)oR@cZ0qVT@fa}H=d-+cndZ&j91xD-8IN!Mmpg5`0JG$buWCY&jBIGZbyzo z66_X-OG72^U%CGx{zQfFbxl%RoLJKY)0jn}1*n6|4)$jiGWt|$wj77b@F-vIjQm$7 zkf^%V%*W7EB(LM>2~pL?`2!TX5?CIXIyk(bfk<}sUMGiE5gv!@3jQV8ohWst5FqGF zXGUstXJG${5_a}09ZhmqB<|9_1u<516X@D|;9L(j8G@O^Q6XDWy5DEuEmSG42s+^@ zyWjnwqW1al!E~^*m&zlXi1z`JbQoUJ2upq9Rf1{j#EV2pO z*)~z~v(%L%N00VEHnt-fwl}TQr%y)~;h!vEcqno&V5QE`Wc z#KW~~m0?pEHh0}orM=A>vP5Pb9zTAcsnK#d5L-4E=T6@n#3&Irhqm`j@{bK403zD4 z6le#eVJ=#MsI#QvClxDi{|1l32HG4NiwyV9_`6l^##=Oss2>s0hJNq5^k`UH`R*Kd zn2w^JKY_n$JTy8sW*{Fe&;tS(W|``}P5&~Ng~jeGVh_p0b6eXwwo7$aZJTjb)#Pt3 zgbX}&PCi4#-0E!%;0+S5T z9o|JNSkKu8o8r0MN;^*g*uVJG@+^iMc-`$=v+4XZ0pSw=BHSn_DZlktgxPZEx+K`e zil7woP1BsU76#sxOZ%jt@dY+WL|$)3R~pTgkX^mcAl~zRg|{ioD3i0;LUzkL;WfX1 zt5vve&=Z857Yj-&0H)Kca8*8h_^?7&9xI1_MG2a)niSaP)C`Ji&_B^V30r-9m&41; zEAygy({d3JN?C}DzFPAeK>ZSW#UJ@9)%s%2W@B$&dEwIIF*>AehVCj)U)@47hn}XW zf9>yK>uVohPfLN2YWll=ifjqDK~*+z+ZOmmb-E`W=CCV>fhqv$)oa7h#c)zYqJoeO zf9zm3IqCa7VDQhMKR?Ur4ChrBX^O>o-h!4Er9>vBId5d@6muUi(kKyM1ppx6yuCa? z&yq9>n^_I|(W4_tQYJT;m>$Q*Eqz@h2kG%GJJtGWp(HBVJ*nP%k_aY4z0uvdn$-_T zd`71GC7X_1N!1DSZ7Po0GGigyPN2xZ;Ckg8qxd5P z#y-P=*H2T#^wuxT`Y!ZVilBlhz5p3hS52rw4tr2rAPU(Pk%BHg6u&Y)+9rf)<K`?!;2tsmtQqP5pUjf9zyK*WD`s5SQGD(x#F#}-jbXlNVkFFN2kqn;&@+IQyudbXOY|e-K zLOC2c&++y#ns1z{JR?eTxY0jG0p+v4*yAX(*k1Kc1FM^oTcw)-k-iJz2PAa>51cQP z&DMi|A%~yN;`XI9g;=!ElHho62uq7q$C|5vclB1j?ZP6Ig#!G)IZ0ubZ6GkzbrOqfX9T+>IRn5oEbgHW3El*wDFvl=`+XImm~5? zoDg>5?1+g@HvSZ1lQq(H{A5Z%!8hnwVS3Q>w8x0Lb?UY@*-buBcKlBekqSu5Mzm>T zd|}ba>N+}{zjW!FA@<9bMek=7$UV4cx_r6uvZ-LXa&@pb(9KIpL7lgqmVY%kE980X zI{4akXKV2Rax7Z&;xLRvXMEqu$ePJEKr$vK)ag>P{@GgS(gbx)FKCQftl`ivK;zkc zU0{tOg;ku{$lu{h7cNY~!79@j66nnc6omRl)Lb1F-zISo(KPdSc9@xp)ek%p$sl`x z2%o`W0sG&NM?7#XHaTZnrC<;oR%BiPgXl1vW>}*`-%A~Mhc2G48G~{8(J*pr4zvVl z@87PDmB3Acx?VT+Rt6kG7Msb9==lWS7Hics`Nnq2-L6~%Z}=IUT@$Xb%}FucEzFph(3L#%3KdI#Xg*XvYjgXprCfVq4HA;wo=_Se_he?0G;l@^Xz-Gw`A zWFI&r@|w^e=rQijJq@f;Ps5x^)mA^j`{q^N(8?7k(W=O+I*voZH*7mYf6Ccinw9$f z`!_xD)7&i6-kOu)L$P7Lb>WFbo1sT!i=NCL*unUx2jCdHn+(>%W|dVfy%V@ zrA-$QM`eOo2WguB2{d+Es?1yvlOIuvC9D$&nxW;Lx2b|Y1vC~_LI=lKmyZtNWjK71 z^{;7b)WV(T%|8zx?OEK=7+5{p_7Jv9XuzQ|)ORE=;w}yhlsRYBpm#Tg+5+AT%RPPC zp0*J82@K&UQ9&`uuBYom*Fa!9UDAdrKWN^*+)xQnTjjyNYXIXxTwRd2;-^moRA(w* zwb}T7IdOH&CHhR}mQ8?-{23vPXVLR;ZoR)hRsaDC`}$RYpiDmT?Az(KJiCcB0L{LY zj>5nL>Gdmz|5xFCY8)?csr=zDU%o6fs@2pg5{q-vb?lVa)+Wsk{z$#rd+|7-qu%@| z-{Z%R=c`N!rHD8MA65DpqC?orN8(J}q1b~mRm3VwMQCnrJGk`c&j;4S1YB93x74p4I*qq*|$#uY}ES$47oGT z+Z6$GyEsA^wX%KG7_k3j;YU%b*YODV54<$$Gt+1$-LHRb67of4f#4acQ zIumdijz-7=)n(iwT1Jt7aq?HDW&Hihmef*h-3xUT!}daNjtJH5OFx3diS4?9pFek3 z@8fxjd#BFnd(Nmch&wf$-M8$`?f`iUh+}g6vi0>gH#*|P%K4nQ3bk_}Nr2ad9Z8@pfsC3l-Vqk4s$srM)UXCOh`@;kDqx-5b63nhw1dM6vTjTV zt3brVZ$8O%{MfOQ5|uUegh$6);`ntG#bRB45a1d2Jq+}E;5}Wr>+#;7<5C)aDJz;vePP`I&zH@*s`qO%+W}Y?FHA7x)B;2>6n%1gSl=qx-afkqFzq@>0 zZkfUEeG2zirlvxt#Fdo`3!EBx`K_by4R6HUw>v>DdGYe)=d8}?qt#Bdzd+pKOO*o-9Fzv-IVso3hw(vnD7expx!r(uLy7lojp-UnW_f9mFeRR;1(J>|PB z;DFaq{No#e^g@Y1T?#uA3zl8jnC?9mRI7N1uw*FnYi*|a3+oe-gZV5^yvfebfs9p! z;Zn~x4LB?5RU<}8e?K0uMdUh=Dsgd&Ty1q_{v?_!R~A=050$i+zmzU5KNglxU2IQ(qSBn;CG{uW}zfr0X{Xu~+|liKEv+ zJA7!I5?<6Mjt~Zzo`eR{ zv}V@{LAE>b5%Aay<4-SQBqqoYhNe#8|iQ^;8St#rkagItJzMqKOGQJ!xbycrJT< zM72VkLNAbYvBXgr4oEyYBbV{Xp;Cy4#uu)4>|UzW+q$!25wo|`zlIshI=j+C@dBEm{{!fKC%^nOQ1 zXqsB86`hnukQ%7TM)a=ezJ5JDfv7A9qwbnly=1R0SmHBn!P*v{k?X&Qw_EEixA2PPKZfTz$}N`y z6sV>%swAZq(rd0_jCYC1)cr4W9wwTT_>56ppn=-OX%Y+22Tk92x<1o&g~i0gOyJ=w zE)EuEUGplDO6XgW+1wOyU0gU=ge=QlG$z58vInGas`1)l@(-&HrY0#DyL`?RI=(6Z z25Dto8;p=3t;kJ_^U^jH{2&CsPbqi}16DAkY^Pyl#7yu5LZzwirXD1*?V8Z{dFkZg z#&>XokLo*p#;+6Jc7SwG(LM%`GZF8t3?a2`Tlf-5dl2d%3Kj%V%DKdKU#7HIkfXXI z+LqG3-(}Br9}`{by7BhIS-M1U5csaVYKvF<>#_F#_Bc&=ULMju}8sg4V(rw^%%}GE_j3;YVeso3 zzwr(bMaTo~Y@#?jok=X*B#PWeZ<&2q3m=wM5XYCxmi>jNL!H5BLFO$j11XNDoQ@s zyz}4U;Db`k9A_?Z-Wk&2ycBRnh+VSp6F8@#l!*hE$#t2i`{s$)yeq;UNlKnsdBx^e zwAPP(m|JP;#r?N_4iovG)L0W@q60k>d40A+Y578;)v7;SgHx%@vKs!*G+VorZ_<4I z8^&(K-L|)Pu+`xgu*)X$F29ksz-vgH-`Xw|IhJ=T=EVHfo{vc^l+#qUFCG7v4}#pa zuNy1wsSOzgoyB9G95Tdx-?fWZuVytgG&~|{`DMP{qpTu2NMXlVK%suHyB0c&&e1O1 zRL*(DHc#;W&ujv{#1QylGfT3Bl5- zAZUslLOKg;;yoOtDl9a(^e(yYM3qLm#F^nB-^#!!}?BI~QZJiZ!1#ht#49$#0y z77*YQy7xL=%w5kXspP)^aAmTorox~Gj(%Ai6#JDbAKhK*$l@yzc=Y7i?$??pY3551 z%;Nh*O*0-mG?`lYrpJX%l`s5bH}l7Jsx@XU&QoaH4U~QRznPsG$&pqr?%1)mD$yoRI9*#8?EYCz@7szhm&YU!*6DL3Uo##alj%4&#{lsiz(oI7T0wOjlaS8wdF-K0 z%7GXH{IhFfb?4WVztYqZ4nR7YUqk%A=NELhPUNyZJ5DaP%^%omj45cl2bFtQssakA zi0QexG>~ZYLT+mPaFKE2t6eV_sy?10At!~r_bX~*G5?0zot98!B4Z2UU+KZ>u{0F{ zHUn(WED^GxHucI`o{m+ELfdhPOE()dA`SJ$FAk!)cAVY}jDQm&QxQ{j99zhGn$=}y z+EvG@&etUOB29$L%}Ox={ex8z$sxx)12i+2c^hXf+3}_#X?A+C%?|%+lgVd;{6C-> zD5g-sSIhVNQy9JXFs1v$jDy$VS>+`2=Mgu zfES&0hL&dzZS*`E_(6ZK?n!eLmnu{OGTx z`=G#aAGuTJwXcb-s$*yziH#W}?4@*IJPhKo_J?b+d(|)Vhr%2-9LiSqGj+w)Ha?}% z7tN^;f1ySy)V)1Vlk%^)aY`wvht=L70p!=2&HJ|&ARW@nYIUp2WvG4oR8>{+gfVZ9 z`}d#2#qU+eEBKZ^QB2uBP=M`iwf$l?teV(OaVdEid)NqjO`LGEEyB*kua6{otNp(O z2am63kIDSDfqD2tfzw zXv7n6_>H?`xXBc?uv3)X9E$v^xFL)52=770k){j*>!5(X9iM|Hd~8luAiabq^YvZg zVu&cq^^04iIY=X+T!KP-X>+DYyTmL3W^kAxcLW#*+d~=R)Bx7Ok>ld(E_xqLRm7Nb z7lSv-(6G5C=@D_?wlnz?XeFAJ7a`~@l~AwP(B6Yck%$DvrAhT-8@=3vgZTJ^?JfyR zs9AD>J)iB@nHt=^L4DyuN%Z2wQ%@T@kb7W2HaoE5!XQkOHV^m3dF=A$SKIz~RF$^& zcOE z`Dh6FoLP|E(^DlWGUBXBtzNxwTc$HELJSzP*iyuS4;RABgkprwy(pWAl0Jr{l$WoT zaWrjbWYF>0#EQ)AI$l6iAGzQF-nHmw#lAj18S>FZ7JWr}Sfbo5LaKbM#ff%^m|W_R z)&WOXaS2hd`nulWW}<^nLDolRf06xYaqo$cJ+cSGD$_1;$87L*(ITn8{|ijL85Ulrbx--SV+@QutK9OxVqLyWV( zZKhY14Z)H2!?YzPX9mO5m#%yZidQTBvn3iBaZkLlFUu}T=hV$xmiM1wHdf20?vdV6 zfk)`Jqz+(<_G_jwsFtg@tqHeve?P8@y>I^rxu9>_Cp27G6=(yvPs##Ig|_}j^Q2y~ zid+2+y%m>*CcKFAtMJoA%{I2O@*yTMKC3FPPvT}p8#sJVcArSP+UY0C93Q*V+9H}@ zb;+stZZi}(%&>dnXDC{tgygFC#&V;CO)_id)!zA@?X8pd;WN0%TWsI;#Cjx3V7)QF zO?<8wEvgRmAI#$%Xep=rx4jQ^(jXm2N`NZXX)p|cfSwH1L52<}=@vVZ$|ol&g$qRB zTC67l*C5PCyvgpYV(i|Kj{V8gLW>=h^Z)GcJFTF3((Mcd>ud-i>v?K&sa5_^&rGb;VBy$Cs}i(cc=rRx z0IvyUeoLpB1PgBo17~aJw7Qmq8})<|V!rG6A(!JJ1N^W5&F9#?x&GhqIiu#Wg0@(j z>J=BwZ_E6kJewLxDq4vVcobJ0ec%4=C6BV7hB`p?s%2?#4Ok1T4410H@x@F>+x0)k z<|uD$XqG(>=TzoxdYLqT>|#CYEn`?e~a3<9g09ZjQi{M zFAJha?+9~gPY*{@0<&J1&$PGBHhXsob-`|sBpn3V=gO0{$K)3?+AnBYSoWr)u}3<4 zFg{sTf&U}==YY_305?1g>Usy3OaDL9Kks%BAuG^7`0;mCWfi8105BWqRe5OPXqyF# znd#-*VJPVbr5-;v>u)ziCpvRDMi*15&6v*{`Fdmk-iV~w4cS)L3~%qh->}KaNC&Pn zqV5WdJMRh8Q)ip+KX@%zI%txR*u(Gb>cVh8@rz@x*>nuQcD}h{;0kK>pk9}fEw20@ z6#i9$$cH3L#Uoj2jb7ITWGwpEFZtr%-#U%ZF_6`OQ1!aDeOS*T>WG`(FrGDI?NIGm zEJxT!1}0y+awW4d^!5_S`uJ*osSOd3==|l>#sYSLBO$&wuK)V= z9|Ghs!;cX4-b%mrLRVz=K;mF83BXK5lj|%#!YD2CU-idAjFMmpa8-K%m)pw97qZ>? zY0$$F-hzvfbi=R57Sd?!%hKko3Dz(E9IMZ_W<3ARg*PNx<$7`D<<@+3AY3Y2p`!6!*}$7D18h zwSh`sn^hmqd-v255+YGU@)CK@UG7n3-`Pu!96n8Xg33!$Mn)zi88u3m;d{-4b!gk2 zAl#~a;lkh36cl~6a^V-3Y2#0PF;t*>>M3$BjDPy?ycw!HBa?fL6AHAND`Cwuj&nS- zu6rlGXnv5Ya&6?V`9{ku{vSjZ&Tpqjq-#J%A~T99jK|=YIo>Uav6no4r(5_|pOE6l zg<6JQD*F4u6O;>u`@`f{MfDF>;f8x~BANfVN{a=23 z#mf0n?*Dxg)fCX#p@m}!VM^Xh!D@|Kii}%mA20KiIFlCs(i&pzRIsA}gA5%#R z3`|7#-`{6?!^HXK#r0#S{?Wd(HTw1Dskf|`)I7+iDOf8%J}rD<8*g-2>gS2X>*@VU z$JlPNcDko`9yxuQ`OUrGHlq@kKUn>c5eNu|fEfDL0TFl9_UbZkA*AWTbIvht8CdImIo;N>abh^8L~ey)ECJuKY584>GCd zp%89US_2v*d`&rI_L>!y#4t&lzl)1gr>fb81Tqr$wsfs_B=?}mS|nf8`1?uYZruLqR}56uHp~7I!%sXwgo@pKQ1i za?kGPgD}=#p6j~j#GZcd%GPoe>tA}vwPX6m_^jcASN#fvKa~EqsZjnF!dwWE;?|=T zjjL}vqixHysK_Ac?sq6bRBqG$Y?~Y0Seu+0iUL~m`d^_$_jit$}jjSG<*)s5{8y>&wk@pksMbxMdyvF}- z0sgwcYJivDvR5~u&_d?gwZU>h(VF(o*RKzqW8lpW)?N`79_>`Vp)T3HasaoeTR=Tr zwwoFa=2Rgd(iVd*de1|ZK5GdGPBy~r`xhpr-@Ja~9iTxRu)pM1pbOpIx>5rM3(S*h zhzA63D)X(-Bt8belhf)(lW-puZQsbsRhpeU<;tqnefeY?)gTuQWws8S3Xdb;Oh|mM+h|3BM)V?B|n_)nFB#?Rj?ALf_t59i#NG>kg(nCmTYLm-G`ni<+)+hE#TJMT zqrM#|C^-)t=L)t)E=zv|jXtO(%&e?<2I%-%zPaIIbKTR<-t_WtC1JaJ4&#{sdJALp z>>c=_JXJd(K=jGo|NB-mYM{jLViU&kf_d6k7hh{kgcAfb%NJ&y$aHKP6!P-4h@zX~ z7P?!x&4B@{^;~W*v*oNab*SnVk+vz8LONEbS7Rmf%@1vB*=G*9ufIl<70t}jt71eH zsSegAo-G{$%&wj zr24WR-Taqz@|?@JX=DJpMp7>!&V71LQx>FCFUmA^RhhZO+4cS=dP*nt%=z%dx=M|@ zp+)8~)YT*NOMG!V-$HW6=6Qs4}jUXAAjhej=Foo%hp6tn;&E;8GXh zbqj0{#Npz3pcf3n-f?6>_Kbr3aY`B!5QF*&UrfQ-+sn#VimF;cfYj-nzSn@B=Dn0I zM6HQ4NO(Pa^X5&aZmFM5MPAVeIe55MC)9vv`L?d=*}QkEvfYC85&^@J|i(!J{oLk=PS9} zo;jz$9nRxK2wzb$W@1iby|LlI)4U^Ie96V}2Y;*C%ST4Ms_sV*DLn=+4h@_A95p0# zsGqY`?}RnLu{>+MIN(*BVVEOZ=G2&3f(x`2fGaSu5(s4?Ymw>1&HXSVLj{(~y}dUs z3KffkSN0!>;LSAKu#hwq4|lo(8Kk1Zy`Z~%Tt)>m6m6h+!+huHO84CMY)Gd@<(AI+-{(`bE?p+#bV zc=x>)JFr!c*iX#2lzPwq%!{xb4AfTtYwm&3>G`ga(p0l(#PbiN*|H|iKRlw8%*>oN zzcp~*aq@#3&$jZKO-t?oiLlzMi_>3zsnB3Yw4#Tg%-1(RLrS=mzO0}6-B*)pWnZPf zT@b(0kri83MA=0vx8JkrO}N0VBx#l6|cN_pAY!GG%G7dtOjcP3bQ= z#>|qI{?*`g5B5sB=E<=H>GI-kD(}^H+k^K@j~+S1jn=eHCc(z*gd&|Vy@R}39PNb# zelWdA!7!A3O`fKK@0*@nE6vPRh0KGO?x@cG(b%oI-gny7xQ|7vL3Tvp6!^Ir_GaI; zd#IF2b^I+eu(~%+%lNguoCkMIA1%<6;WU`w?49?ATqiL;RMIlu@z` zjO8$RMN}Ud{kU*}hw2ka}a(ODU=PSj)lz<4WVu04;c$9S1zk z1{Mapj-NW)k4rA9znzL}F@cO!#-)Fy0NY9uyC@Wys$V?VC zbe>*_EZ2<*{wMcfpQI=qbSCeT}jCe^fm@nRaHJC_rf**srH6~ zVyZr9X=h^!BB0fFceFjiNRSY3zwt4pvB#AVxdnoN*KX+AJ7v)r4 zv*3K2nR#Jp*T8bHfQnxHpy5_frtMhOu@omz&uxvmuUT{y`_qdvUb>X}D$Yv-Nn`Io z2Qi4G!p%(&|CQ6kZ$0?Twraz)s$k$Ey@<;*)zoLbgU*%=;$yHki{ZBN%O*ZtjnOuE z=oy4fho1=*>zG&hQ|}zTtd?j&wi`Eod2j=CaPG=IweZ$hXVS_DZpD#C{dgXk0TN@= zo`?zkwHX-yCFx|)M%paU*Zl4>q76C0B!~#Eu9+i=4{A#KQig_yYkphG8?ag&Tq&Zj zM6p$?APO(Xtt*-;N+b+;trwE$7#E>Np^KwBb*deh%_PXl%zA#8GDGi^X0(7{wNigT z$v2y_QawY#gr9H$r#$d?7%$={&{#6`!p#hQ&~K<`KflXw-Fa`w`8Mbi+vDGWy%xil zlMj?3d3(tg*wko8 z{y^%<6U2S>>C>l80-+BC%CSK?JY^8-k|s?OpIoOJxQIaLj2!z)BuWOgGPMQHK24>u zBo6j;a+_mlTvtq~qZ8b50*#Te>wd$R8tps>pUKI|?lN`D;*}e0A6M2IYW*9p2v|jo zh_k|jAR|eA>PY}@2dFSeg*F>5$)lud&FwF<&f12{FF{6b?DoKOds(X`W;bs{pmo&% zVzqz_FnEAjC2IV~G=qUx28^O>ApaFZuMC7NfvL3sJfwv-UD{sEyy#fPQij|9}Tvrf_`gsTdn48!x*;+JH178wRjiKOTce(*i~K=G_V8CdAnv z;6cp-?%ragtgxcOkL~7U$?h;%+nrvn41xg|ow_6nY>qy9ml+9!tZ@WiB-3@7!6MJ- z6I9oX`)UBs5CJmPcd<7k3oI$%n0i{bty|`lffT>{`+@G_XC~d9U}_$e+D*K@=3Y@P zU^D!J`^>3R;EB%ZPIa~n+rl9)rrCKFfwNF+8pSrnO*J)143V*wXz}!T29jqhTyB0DIOb@*Vhk} z?EDnUQeZdno4zmstQY9ywp(s{Oj3#u$@^ojQ_fubhIv{=pdRybM>#J#Wo7!jM?M9D zvO|*7)1Q(-jEB6>@D&+{;@8trV~JNGAE4BWw4#|mT#jN4KpTA>dGiRGM&y?PYR)7B z{B=ScVOuSXJ+KW8_c3XElM81Tg&9|K&fj_xb+C(Ap=~dDB{hcfP^}>g@)DPJy0}fr zI|UFKM!JYyR=r(~80iLj2Ee&=;;hFor(&F_7N`JF_m_=^wylv1c@h<*BAXy(SMMJ? z6byfx7$Lh<9)fP}x%V!g?K{~LDir~^ zKd_x+5Pn7`Zs#xEKw`{xPETz?V<=a*o(f?l%1gc06w{9SxfDhukqg(Vq+wZSXi>5= z8W$TlXo5JWJ32I%($NOEf>AK*X>l^Qm;e+NwX=0BN4HcZq`-PegIr}52?=ouzL1iY zTA#R<(N!qN?4b+gT}FZI3bW}E5ATU|lu z@i+R%H-+r2OXr>+qb;!O5S*P7DH}^*krs=b;IsgDy?C7A*wH+T!l?vFcngeeF^1oC z3N3P2;x3z@O#jr>NK$R1m5}hRQmfhQNE^@bxZvOy#j2jGr80Dsnamjwp^IMW>1%Q5 z&S3TNU29ZY8}mL;2mRzS z;;(7GNjlK^Nm)(c==i)g>!+L)*Zq>r*mTDOYup%uMlHHioH}(UoD5r;8$cI{fUYw#=-s<__+O;1Wgu`b^62a4R&RfR=tVbw2$+|HR9*$^X4G__L`=4#u>r=D@vQSHQ8i`K;s{ z<$G|VJsvcC5NlJ(--U)1tOmfMmV&!dqy`WRBLhRwG?)k%d5_0GoSlLLIY%qM&QJ^$ z?B(uaHg=<{46ZD|%!s>f8h;Q@Q>>2&lFa;Iw%>QAJFD;Ard`((C$HoB_(PmMm=)n< zpXF;Rw0tGeybUr~y2WXja*LaQi%xZqb{7DQMLRE|0|L@(g4B#UR;nl< z9i+D)HM9^FDbi~qgn&pBLg)cP3IBfQJGbZRT%B`1uV$2lw>-~&_Fj9fwg1@S$6~Pr z=G234Rk$yMr`azGRT*UQl-K_fc}Y(lDY0u_g=8gua#~PH9EBniPATPR0XE6{3A2P+ zOWie23VQUyRIBU!aaXiC3>*&qTqk<-CS9VoCR%G1vcXBz`ZF2GGSqhp|V9|9%1G~F|?{o8#4nK3fw@#eso$n58 z2WotFxvDrW_XJPjx3@VD0=pOH0~i5!2?0vKjHu&IooCENPXh9&teU*d;} z&+EivFwViyR)rbWQf)ekQ|_3`5BU(e&5;($PIy6sMlHJ5%8gPp^+3Iv@$;~_ywy4| zrl!W_(btI5pW>xd=l1T!rJU6Ok2^RCjKTUz_dRF#0v7C@r^b)*x|eX{;vms8?8$*l z+ZZ-JMNez8^>+<-hlLSO`VgT_^aTk=|7bmQP8-qfV+c+(;Ll&ac<~vp`t@GDWNSDN zkD$*;yZRornBc45Whtz9W!&~3t?vwqtZEC9np8pG7r0N5cnS51?rCv z#o|bn@ZMmG4Q3c>pXu^Gm?Q-Y}*_h3-qfVgR`*)h&pRl_JZ{W!r=;?`5%z31#HH@a$rWx`{ioB9bUh_s5BK;ww2mmx815eM5BwPgJ740#%5+&&t!c;)YPE3Mw+87bW__(L-9U9p%9q*y(M8wDpaN(|Ag z>XR6cndnLBhuwD-Fs^t7UGwb*TTT&c%i~RYp0!&atKoenZp;?=C!ls+$`l_hM%^in z(ZF&!J9eJRE`A&JV?&oT-w71)!ReF|iau(64l%wx;aqD;g`FU6JMnw%mOwZU^A?WW zy#8KNZg6+P2%h}f0wsr6p;F{>u6BK4-@t^!)&fL9_xvp|^7Q@@r%kE!=-XH_@#yFV zRV}0CysLQ;>4;=Hj4slHrEv!^tLS%7mcc-EuNNk{+*%=8r*(IC?`86+8S94Z z&g|>;8@1w#rUGoNw9+n_Z(URaAdY?6pf5yw!uGQE)aYZdTgkT^EIo7e{{6=xe3sm+ z`)eLgVnxZ6g*vy`THl!u&6}uLr(h5g)GkA5SQ;F;imrOpUxFX?r?|f)gVjCAenW4% zy)Dp&toUt)`k>Ct=1A?r^vuZlpvca)8J3_V)q|)CGd=qM*|U#<-Cd7@9)u&GD9#Fh zZ(J~p{43`+?PudekQ-J%E|Km5O6*ye;`ft?p?_WRsZu}420h?Smm!E?*b_oLaDo7LARb{a%ux3|1nwHHYiXqKfI_v)-Fqe!hRDX}-Obu=bff z-z+1WN$?K{5C*^!#L)Oe`Jm2WIhd=_y59BJC6Z5o3(p#5U>tFv62WoVJQ8Y4_9?M# zd{AQAUPgbT|7NgW56};-m{!ADtDeHavbj!MfDcm@+_^r2(kPrC)j6MXa)GdwGE3?R zTFaeVRFW5$fpzV>Frw5@3Z6^|R<6A9BUrN+dYe){BvH_6Dn?%MJ6cU;h1Od=7@-IP zw`Qc;y0H<2=>)xnys&fH2)TZa{SXG)po^M`nz*1uz`1%S#l?}3wOMNv+@Nape z_r4FtuEI8+nsNL^Z<|~g?b(U24rWvwkzo-&_|EhOj4`#O_}4|PTu2vTW}j_{cxv!k z9ysKj!xIl&!&6W|`Fq=UM-d{2?DL4JFR-el9xUF68;%ZhF@fJ=`uX$c!TQQaOFzv< zc;pD#I&(RdDDcIj=w-j5j1p6An$DguQl`0ZL%QwcXTY4$T(32&5IKLgIv0v!C+PP$ z$iYkIzmpg{Nloj^KWWgx6MbUl)(tK04^LXzzflrVxhA4kHU0C-=amA6vzEr2e4ZDx zAvOfd@4lIDZW;It`amw$cQlO2mmEjl(bHwjP@Bc7_~ak86zwyxuG|HsBE1#7n|eE~ zYI#W1a^~z&X_I1WH*2E4LOG^VhU{y9@Zi!JIq!cXc$Chc9(zb?H-i=E@VIU?TxR5< ziE>~k0lp|?cVt8vBg4WxhY><&q#i!heUbW>{Bu+7hPMimp{IS`*HLyF8W5{vtC6L2 zl3s!T%G6Z+I>=xiSYSt?_#40Oc8&ko4|ga>9c6sX$&{K|Fz2xo?%un$)E-&kIVR)a zzg@LdGd&;ZO;ASOPBY69j@)FafGk>n-dp-qsLk@!&|?QX>f+h}WgcO)g_ z1fX2(%OivPhcTtnEk*#UyGFNty+~;-i7BN1CdRM3tMT{W@uMry20Q-eX4m`rdZ2{l z-@8Ji5kZAM*P5jr)9F=fr4}-WHLrBd8V%VpzV;|Q#9rokL{ow7TcAiun=jiOV5c0eglvmhnh1cJ!Om|8rxXOkL%~mY&qcN_OdYucm ztqCVT03j`4CMyo+HyP*Bz0t_DC8`QUJUyVpvitncEt6S}pP%gC{9Kjp0GW{~fj-A~Wh9ekLzfK_G(eIa-!YnI_CH8J3K6&5wo2&D0X$X=gj#@{7FY8q#;s@uX zt3IE3&h6e(&I0Zjy>&3z%TJA2u>&UddvetG1j#LEyMfj&y1$4*uOV2-XtoacS@*;8 zpJm8c<7*u|)%iPH1_&fzx;rY5R0PI>OJN@33|JUA_GN!CV?eQN7{-F(75(3)Zl#J@ zx%agd*SW?*XdvA2*o#7RAtTrZ ziPFc!sa{BwGNvzC(Vb4g(S^%?TmNLC$Dc{qt*%%p!Wf=GL0hPO%wncv%Q=Pmt{*gu zE%-oDPEKa^4mjH3;1SC)#OeK`P#s*48+~MF8oMy|I$LM!2HZxn87LY!`k_B)SyTWN z&_j7=Y-8CuP8R|j#345hs1R@T!pgTs;DJ82Bsl8Fj&7v_;4(yQd$`0A${<-7UgOyX zF+O4A=V9~8Ip@puyY*W>Qc1wAMS4rw^7>u&VW(w!UYV; z{5*WC(0e_4O7_$Ir%dAl(rkZ8#>7}U_zHffpMs7qV{+Kvoi_?uSG!8vQB)12)=&#|N&{nm)WMZL?EayGX+sw7^P}A0Wu%8#NPzmY z3jmI7X6iJ8kX{tzcZ$ zD(M|wa#*|9xK5gh7IdyY*7U0CWrvf)@tQA#@kTH}xna`U+8QeL+JLzwmiz-tAfxV} zFyjTqRo+;3zhmmrzMH;xoP%5|(G%rgT2Tm?k^2_WOG^?p)Up|X z2vfsLdgpkmPd)$s*{P?eX9$l?jo?R#acHV=3g_PIF#qiW8~GNf9gguS=1My1f~Bbm zqLS3m_`R>FVNxyWWj74-aoyt{;ET1>NE!_L{a24V zpYhVq#$@Yh9JN*oJ*#kx__VwGE`lWk^Z2beot^Ddflgwg&E>hY;XSLGR-j?Q3(h3v30D0l!bU?lk+c(}T#$2dN7vi6xqMflrf#akC0kP>8mn zC>LE&KL&$gRX`dX1LiSr#+XpPxw5)SUiG8%uA5S-8ZEJuBrsBcbDJL z13=)C4alOyCT|ibue4)D^$*(EfDgQ^jq+i(Pa`!ISWe7woY3Cr!vJ_BtZobJY>abE zl|o`uL$Acm_EwjH*JKgcra`YpBN{P^0|8rw`+$5y4U0L=Pai2$FrmXJhZM^G%VglA_}#^G=-%5Bj=|MJv+9NbAW1~3-j zqKED8bLL_Pa`jb_bbhG9qkrc8DaqfG!0;4ly5TW3#^6S@e@(8~QJlCPjbPNV;3$fi zx~=7*ic zY=|KQPzC^tw`eaji0wz`@RhUvVSGy0D?W02T?s!eQvxsieyo^PeokujCKEA0($@=% ze(~Z(wWz$jd8E>a4Prr5+BmwK2HR$0Gx%+EbpJR`q2nB4wdWo2{A%@+?U6OW#CHQX z&H+ty)N*ZEyEqs6JQxOI(kEe;(hG~P-JZW|!?Yt2-$L&4BeX3bdOAMF{K`k9C6d5g z5(Q3mGj1e*pkTG%Fr&j85MHTW+WzI`bR14hVwqJHzkYKU(yjIe39N04}| zqG}l`-c5`)O2lS2c=he_{|;vgWQHFP-vDCD8OFqM2VT%RW)XM9LNBg#?`LbN#$O^@ zBMq>s(t&59maE~+4M2H*ebd();ytTn@E_VQ(dRx07qh%tABr_~eVzfk5=|SrqDK>X zy&Op4SHSd(@enp=T}}gj4mhPX43Q|MRe;fsT8gr^{<~2McI-<54eS%4m1PG;XBLn! z$c#Sf6Xlopz1+2%`uYPb5o!>UpAsNh!2s9~kcaexmb+*|Q9(5e!E}xf;E2-hJtR3R z7XucOGkrPnfKfaL@ZE>=Qza_J-T$8I$zUL{XZdiI1+9M9&!NzTmjkzUqa1Rri>*0? zMMdpoYd%1?#5r+l%k1NQm58DL$-1GlAk(I_KoD1z)e)gTIHVrwz_QpGd(k*G4rywh zS)T(4L5j?c3HB4J1KWdivbg~?)2r)p<)BR`>1f4U zD)r!F;2Iz2*UDn>E4K0<&kjducD$sDL05BD^Kt?0$We6772H`Oez)4kk={Ds(%*tf zo@|XR6M+jW@UmNv*DnICk}lb**1d>XW!!e}7;O$5&2g4cjmOdo^{FfcL@HvDQn-h0 zg5&x3>U2V216^#EqbQm2S>55J=zEVfKC5_%BW6ArN}Q14ZD>1ni|W0%Q!9wbK!HC~c3I!X+)VuC(&5VwA-M(BHF_1IN)#~@bYG-5t(HlO71@S*w4 zi;?{Y4mea>1HtZ@(NWEn%?tkSB5a?6d!9}>K&=S?jd9s9O1-!C`QUP1HRF%|0& z%YN+4L@YsF?oFQ?Drf^nQ{B6=iFpGrQ_8dZMuk(~ZN@C=Ej}_b0_;J7nlMED1Km1- zAI2oi%*od7z#n=mbeq1Fug|}R(_}D4+_EZadhMh=;KTBqXsv6NjbMF(cqe^l@DUUk zN|05N$7o%tk&^uEF&wR!mwCwi4Kwwy`@Uk-tD3-*dh*YN!$|yQ1oiYPG}NY@m1Y7X zYaV!CI%P8H3nJ8u(q2FNAmUSy3;b3^?m=p(boWR;KPMo<52iTzId-~ZIXF0|!DQ;x zt)mDY1h!{!PVaXZqP~H;y}y*{VD8Bn?_- zQ{z>#z%Dww4(*`Z^_$KqQ(e15q8xmRx1^J*>dzCv-UK2%UQ8?`i2;TU##BxgUat0=(4|Xkjf6 z-4H{V_1s;~k*y#t@XZR~;1|yZdK4D!4E*ZROK1bR!+8j{&_D;5*~3F%*2YW%KUk2x zbfjd0=9RO>QAij$&-UySTMw|lqk9iVHX$?bp`ndH_t0Te1w#JY?D`d)RaY11#opmx zjMbS_IU_5>g9UG0OnW=_9%Q?r>v>}G0BN_@8dx=wZGiufL&fU^^tzJ#&Za2)nE%&7 z=9|lwB+H55DIowO)q0*VkiG@}IK~<;cjhqAK|)9pTt4+t0tm!lIFj%I@a(BANKBTO zDGOlMmVGBg8@LV531mh!X?zexK=(_;Wm((Q$Ecw?nG0zuo^x z*t|a^6(bs7J0=LfHo4LxXWtt#?L~91#svJd^IH6Hjzg<%(3EvL~<)A~40qc)WiNN*9FSNO1VS z!oabK=1P2K^$n|N0I`zC{*fRcuB%8Jd%802V{TjVC~H`JT6nX6a%;nP?)x{fplrk- zTF67^dYcTsV>3k)=K$wXg4B%Mx9=mN#NBh0;D&js$-W>clCLS+)IIWoh}@La%WCGw zwl7;7p?0=2n6kM+QBOpw^=|BEn?OZ)TD3zp0v@%$tDWK!g%H~dgN#;!ut|ETT6qao zNXh#1GPaJ5w%!SG3k3FZ?Y_;%P+}KmbKVBKHGs2(c*R4=5ozff4Oz`IOjsXKL_n<+ z?BCJI$fu$$@RA>7Y%uE9>n%f`e8|%^Ch=km^}BidhId+GSf)qA#zZi%et7-^25|44KE+GAGvPVq#nSWjBB@t4s59+Wgb$x zE!1!=o+%5KaR|f3mUlSF7s3i6Ferln$pzV|psJF|J?PW{4S!e^W*yu_4v&T!WXwlK zFuw$1?~12l5*unjqF>3KFB|ej76@cR1ITd+L9S!$9sC3w*H^nz53>CjQM?_=muQZ# z+M2?As56nxdZ&uP$7%42kHbU#CELb|M@t;bYipQ|?KW{@W+O^8HY}7%OjwuRBa{rc z1wbYACvz%5MOZrt-0^&y;(CZ6z7xOn5k-T$H=8ElVNNwO*@?@m{;w0BY|x1?72UjQ zk-L_Go*7o-6fy42dUpab_T|}oY5YeJ&@6^$H#0<#O6!~|JB5+CANrvxPoE>h8Eijf zRJd(jNAml5wBe{fdF;qzlR{{W!62EB7T13)?a4RIi|$NPE-J2PwazOE!my031?*WH z6gRvomu1>B>c1|pU7{>ofa!2eR6B4niW7#9dNKi2>F^ZLe0so}{^kOcK!?PX0g%q> zjAQC-Mx?S-93)N@Msy}vQLiqy6<1lw8tmCwQ%A{}Tc@NX5bxJl*Y6({^)w~hmiz+o zre7O;8MNSgO;+kj09ken&L9Hgv4|Nk5qmhq-d9SfoS#Z`CzW+67MNHEH(FkIfnh#q zAS%Xr{>IkA#WQm=dSYf~%+r4;9gR@jVD5&rYfNB^D0EPwekyoAi2B4LA$A%xe z+UorNMkvT-KIh#%DjBhVj$nFfX%{x=kSpu z{b7*~4;Rxu1K-9goADod*0Ci|@$_v(Bn#{ddTqY5s|eJ^7$8dpZT$YC3({yH%I|yq z68Z}W(bO?K<;`CwLtA666yzJLPK=FhoaJW4`yR~ z@cbMG0k{7DIV^eP)A2)WY^NV<-cm98UwjW)2mcFl{=e=a{0FRptrt!6mT%$qj#f+Y zzj#B&TSKv#+5rXMZwdLcoE~>6&j$|Ig|`ehE!qu*Ts4rncho4uJSK_7m&~A{#)g;<8?Lamaa%zh(Ik*UeWotlaoeH-!7^P1)rlWBYeLbaB| zUta*YEhHI%Tu&0~Sj9rdb$vq9o`cogxA`>uzj|IPUYmvJgj&=Jju!af5XJ4rN*LmdzK6+q=BZF02pj&6y3B zFNxc$W#L`!e&QI^$81&Z!5c<;n!OptohEb71fJa!%Mogrmr9LP<|^LfA@)vyN=|>g z>FbgmcqwFlbr-5+%AnF08LM(ukYSHI*5gx3)Ph6%GdsCKJ) zHb$KFG0<)OI=a1CM27$BQ@xmF`w6RIuZ1W-uVPDvseDEnDRdijOfZ&dGH;ibhMz9J z-nKLU@bC3)N22RhelEYOVXd%$4-yodH}SM`+m$_<1_No=mKUQAP^sqJ4P2Vh4y=tx z^MRt-k4I6(lp^Yj3<1rK2H^rUH7aP+cmCqXuvB=QSx^7W>9FS*8EV1Fh;0t<507V+ zoxYLq!kAPRiMgH{Deq$~5)Oh|WO0ah%{4f89Xq-_%o0(0dD$FU#hRyn-z=|?Z-yk=MPFN7?8X;V zPWvlWRU%iM*76;zczZ!~qfa!o{lW9HG;T2)lN0EvIs-%d1Mfi4PnYx7iuSdO`E;Z> zafNaTd0(&Chu~=^kDc}3EjeYT^{7CTe&65U-?{6Jq5Pl6U6gm%aTjKLODnZvtG0)D z+4Y3a%j;gZ5GJQid>*munHn$ebM_eIO3#&?rq5>czK$D-Ca%AaBw7#}=Fdp|ID{2WA@HjOJbS#d^Gys&!A}W>h8v#HSx37 z@nyp}{CUFHi4!_WC12eKJ!tFBi%M?&MFZi3mE5pP7M495)BZ8Q7-sponrkcUhW5YK zVK04zo)@(un(WRgC(y|3)E_y~7lW27bgHiZ_joiw!)>puI~wSz7OrKoU~?j5_1)>H zh+8SraW9OjKF>>^<1CKV`})fQj{!Xzy^#I#JCSTsso zB+L`D(*JsEXjJ0r#4F1;?s-ztasU2zz3*Sh=NeSE5ee5qB3}6X+#JX3`Q}gtnzg0& zi-@u^c0d&4G@vVa#AO|;Uz4S&quJX;XzrLae%ksS2vdYV2ugJ?tT%(=ZN&xR2`cf= zGWRH%+Ygc|6pkexmp;2*zw47@#nk$v=4XWpp7Lx;cyh-WEl? zaVrJ8;KZn^#xXZMC(LSvCi}dl2tffC>q3Rfz2zXV#En&d`07x05}H7A_`@1PLU9x0 z$b`R7F-)@{qpYCQyb5UD#4u~Nf{yHgreJTajT0sVX<_L(4xVPn6{f^96Fk zyK-6xmG1auWqoCUS7zYb;%2#U?3JQ8Wt-UI)HW&)d=t$_w%u(yUBkHek^jcT45HaA zPzrPDs^nLk=?~cSb<0<_0$ShvI6teWc=uoJnfa{A*PsMNnpDX3fq3R!B*(bQ9yU*O zxT?s^_dFluW(n#U=x@%FUE6*SEid$){?*Ei>lfKqI=)fTuSv6d0w^<9c76^=l0FcWln}0UdR!5 zbI?Fn=ji-v02582Ju)>cb{3{UB4b2!`$2_DPTXm>Tm2$POq{gqDTyZ$@>HaE$}#u{ z!#=l)Bdi&_8rnx+yPvYa6Z$Hgyd1s-jGIC; zecnWp(f#;nT^-UJuC2(%Ulr9N0-AWKygYrv5a+KgtK#VBzYfA@u=ECAjF!M(ljQuwAbGJ(kW23tiyI3Y0s^SuhbA zQQRkFJ#45VbAo<+1?FHO#cL;hZ|}2}2*+9gb<|Xoqn4+}kLQeEOU6(dRA+}uE2p+A zzqvE7=s3$uTh6Xy*JldsEu?m8(E;hZZq7XB-{zT4dLxOGG?{<-`?!ei?Q6k5OlxmT zOHJc-YSN7kHVRs`dk3H`61L4ms*>dLnqoB1%$gK~e5uH?Gw+{rc^wX8f^$53<{VDa zVzu=`{IO}#L&Hno2kCpk$zsZC3E6EY&8w5F`G={Zj@Lt}oOEt-%O6qlG8`Ri!3JVx z4DE!(*V#nJXku!yaNN4zVk^uiCPdM<&iaI0tYduL=Qe5bMV&vW_FhS=;@K>EdfAX` z>5yx5rZ%ZfNr6k;3}iQPrKvG=*kV3U*fmejzlb@mMp*CWjZCJBodwi`^vWC}`DOV) zWqmI5!Jl3VPTfz9+ z+gDs)A^~t4kK;Hb5+hRd@sDP5>N|9|Ld?CPbLMKn?*|xSv+V=42P|^dtJYCZxsP>Q z9cO%)F=oH&t?LJHApxjjYKC1-=irq@){Z=(o2VTt6OT6jpDTheDx-+At$Op2>A05=BYfDO_A#Jaa!~RO^X+W|+t+-WV*f?-T=HR&s zNs^Jnv!12T_ls16M`qA;a#>9;wGu zEQ>P<4MS66)WW`!rJ8CLX=-m~wt$fG(g%l~cM`JNy*;@;o37suz)7f&E*e}*)opHa z`P$!jhO}}C?KD=U-ovS;cDvQVvUXAzf{Z3i))PdIIm_w2Z5uFq%4wj`=w6nn@>Kb# zUqw+4%dh;dck4xhvg0#|piZRVQWFK^f>UXNBU>tvh7Z-r+_ZsfQTOn0$9FDX$SJqC z`S-!U`4KuU;7nq2>GvC~%}YA}#CYxW#GLa&R>4{Vzr`hje_Y{p0?5Vk9I{FSN=*Wd z3;sx}))v=Wm4I!#>DN_asi$JypQJc5{B9H1bY-8xt*>6v?!Dze>|tXo{b4n%t>es2 za~51@HIVXHM&hXe%DyT126IWcoQ=EI55v)8mfpKff<9Y2ij_X@h=ndCM0 zOSa~I4!4Y0(Bp{lo4gfQ;tg(E6c)vnN0}%H-1|#ny|N=Jx<8gOV#vj(I+`uDIy@1g z+!m-8?crUvft|*7F6=H;;TQ>~*NAeshgAu{n)S?Awc`piDrH0piBtPzdDw#JSF>7m{`#${+=Ao{gS>YF5}3kM zlFs?#-`fOj)yXU43mtXNP4+D5s;ItHu38Uz>0xUVt>_lxoptOKXYsqdQFl7es8`s_ zjW1iI+hQi1{6WpL@3A_n+rRJ%KjBL{h15Avy?ip_ZsD;_@jbn=BD0R9+d62me4lDr zGhwx*TSBh|we^iTy(q66kDrmS5wm_YmU_I(c{8u&iInWsDD-b^_xIMX#QRzGGEl6B z&L!TomoLQ$yX_VqMygiPOFuTHmsWgN*Ez6dN+a9PpY<5o_;t?r5b#uAP1KeL;px(1 z27NksA-hpGFZD=UTzlw3a7eH~j;>m?fWC0@>#pSMQ#&crTq8?cvm7DV&WMTK6jg<5 z7{!nZ|E>+%`e^-jSl3)cVsT^)xhpc&b;H6HJN4=GXvVHhgX4*UQ062ah576_+Nz(0 zi_g)0mVC?3E-f2?2vUL9n$%xM8DC0hQ=m#0oE zT(9q2`9v{kEUeMCXc1GOvr+n}RS$K`JL@<46~)}$VRoE*eq{BPqxy!)p+gC?@S^@S z86bt}#ch4EDiGg{FOsEwBqw@23^$vZruN_ES|Hfj-?~*8u=8YNE^x%VlZ=<@d|ixP z#ceSBP<*2Siz%i=jG3s-HSvTqvz^szK@KtldU@VPEz@?ch0DiYg=i8oy9Q###P_ zulrkn?$@SzVYO3_KXENZTBeB@$u*u(;F!E(?bFb{F+p(o`D@Ac)~$Y{XN;6e*D zRe;qLGqaUygB>Be)|&W>M;+Hm0(aaIZ}b0N*2SsBgaSsCKZ-wgf|)YcTx{7XkMzo<}K95?>J)Ds-htg{J>m-Hhv~#xEJTaB z^XK7zIQ^{nDI zA~k2j!|F~22Y1>3_pLW=Mtvl%E!&bLt{L~hr$4O36U@}%)1~*_3eT!xv8j@8*w}I( zJquFa+}bLQcvJmaT;tC_d3kHgz@~#OzVs4H96nZ4D?i?y{?x1)K6!^n-uEi}MOlRY z$rG9VHh%PWd8kbvd@mLw#p*9guHwL`Uo4_Bc1Swh$i?8 zv*ybRhkDIkH`*!=)?cKZVq;rO2q+EKJ`vhjX!nkH|BT|=0u5aNj{1G15hdOdtc?dr zqBZ)7@195x_t2ZlsmQmzH-bCm8y${Chm~fXwYp_NY0`)`AV~mfYyj`d&5avwi*zD0 zRMzzJ*X(fa(g+B;Z*BXUP>r8&o`b8?QQ1TV1lHJXRTlmCG}=moDfigF=>GFWYELa@ z{$)gt-+%AZItKo$8JDb38XUtVgNoOko$bcpV7*&`V}zKh4rxzzsg;7?S8Z|LS6lN~y$SCD zv*tZ)X-_U{3SD;_zW;9B-XKcM&_mDf2nT;e-~s_PC2ksp=7HsO_grdZWo7uW$H~9B zmn8nie)!p=2`*GPyr|NMy=*t04c6vd;A1xG#bjvOvOE2yR9}4slx#nF{YH<&d}ZXf z+&{z3%Ns68!GC21!>WtFcOp}>5?%|>Mj&?BpG;lKmKlvkepfcoP2_DUq_xW@L*eHz zCo;)5N=(C=W$=ygNw2LM1>2>c7tPf7!dIR;^+=huJe~93YPsAO`(*XZ86KXOpKW%9 zV~b5sdl1~*+LIN;X>hLJQF(EUH;gzx-uY#=B9tw0T@7{9hQiyXl#N(T!}=n$@Pl3wzk^KHuC-3GH-TyN$)# zko1Kkp1ga^V%7gzSO}e(iZ!jbwFh@GMNm+@MT@erU6WD_l3io0-G)aS7FPEjo%Gt6 z(XBcv8-xD+{K@jlrrs=>CI)%*$4N!De-7!lOpkwh%=$$O6oXxF*8KUH0kehUs9MOw z%PB+kSI(R{Gd|Q9YB%;pt!HRc{@Fpc`w5pPRwSfWiyw_W!#4f+bR#T0+=2I1*<}s; zhpw)^K_69b-4crnmGhZynj7@_P+fg7AtB*IdWpm=jh2CPcjzaPn!-*?ziBgeVkcm>!$0qyK+zUu04N}v!65)MZfL2%uGbKP7s4jAACTCyS%_Io g=Km*uIB%D2p6BJ|A7jQ@H}60BKYz%~+yDRo diff --git a/sdk/python/packages/flet/integration_tests/examples/controls/material/golden/macos/date_range_picker/custom_locale.png b/sdk/python/packages/flet/integration_tests/examples/controls/material/golden/macos/date_range_picker/custom_locale.png index 98f4143da1676eaf010144c3bf325098f88e7d44..d70cca712478d5a40ef2371dafbcaf42ff696e95 100644 GIT binary patch literal 54851 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 literal 55878 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≈lEv(dQ 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 z�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 z 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 Date: Tue, 16 Jun 2026 11:49:06 -0700 Subject: [PATCH 30/47] feat(app): dart_bridge session-restart loop (Android process reuse) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `run_async`'s dart_bridge branch wraps the `FletDartBridgeServer` in a new `_DartBridgeServerHandle` facade that swaps the underlying connection in place when a Dart VM restart signal arrives from libdart_bridge. Trigger: on Android, the OS may keep the OS process alive across a back-button quit and restart only the Dart VM on re-launch. The new VM's `PythonBridge` allocates fresh native ports; the running Python still has handlers on the (now-dead) ports from the previous VM. libdart_bridge 1.3.0's `dart_bridge.add_session_restart_handler` fires the callback registered here with `{label: new_port}`, this rebuilds `FletDartBridgeServer` on the new "protocol" port, then closes the stale one. User-level Python state (singletons, in-memory data) is preserved; the Flet session is rebuilt from REGISTER_CLIENT. Soft-binding: `add_session_restart_handler` is looked up with `getattr` so older libdart_bridge that doesn't expose the API still loads — process-reuse is just unsupported on those, matching today's behavior. --- sdk/python/packages/flet/src/flet/app.py | 133 +++++++++++++++++++++-- 1 file changed, 123 insertions(+), 10 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/app.py b/sdk/python/packages/flet/src/flet/app.py index 1e07fb2eca..b7b27e8219 100644 --- a/sdk/python/packages/flet/src/flet/app.py +++ b/sdk/python/packages/flet/src/flet/app.py @@ -417,6 +417,66 @@ async def __run_socket_server( return conn +class _DartBridgeServerHandle: + """ + Forwarding facade over the currently-active `FletDartBridgeServer`. + + On Android process reuse (libdart_bridge stays loaded, the Dart VM + restarts with fresh native port numbers), this handle replaces the + underlying connection in place via `_swap_to_port`. The outer caller + in `run_async` keeps holding the same handle through restarts, so + its `finally: await conn.close()` always closes whichever connection + is current at process-teardown time. + """ + + def __init__(self, build_conn): + # `build_conn` is a callable that creates a fresh + # FletDartBridgeServer for the given port — used both initially + # and on every restart so we don't capture stale args. + self._build_conn = build_conn + self._conn = None # type: ignore[assignment] + # Pending swap-in tasks during a restart — tracked so close() + # can wait for an in-flight swap rather than racing. + self._swap_lock = asyncio.Lock() + + @property + def page_url(self) -> str: + return self._conn.page_url if self._conn is not None else "" + + @property + def pubsubhub(self): + # `flet.app.run_async` doesn't touch this, but keep parity in case + # downstream callers (tests, alternative entry points) do. + return self._conn.pubsubhub if self._conn is not None else None + + async def start(self, port: int): + self._conn = self._build_conn(port) + await self._conn.start() + + async def _swap_to_port(self, port: int): + """Close the current connection and bring up a new one on `port`.""" + async with self._swap_lock: + old = self._conn + new = self._build_conn(port) + await new.start() + self._conn = new + if old is not None: + try: + await old.close() + except Exception: + logger.warning( + "Error closing previous dart_bridge connection during " + "session restart", + exc_info=True, + ) + + async def close(self): + async with self._swap_lock: + if self._conn is not None: + await self._conn.close() + self._conn = None + + async def __run_dart_bridge_server( port: int, main: Optional[AppCallable] = None, @@ -430,6 +490,12 @@ async def __run_dart_bridge_server( instead of a Unix socket — eliminating the socket file, kernel context switches, and the connect/handshake retry loop for embedded apps. + The returned object is a `_DartBridgeServerHandle` that forwards to the + current `FletDartBridgeServer` and swaps it transparently on Android + process reuse — a `dart_bridge.add_session_restart_handler` callback + rebuilds the connection on the new Dart native port when the new Dart + VM signals fresh ports. + Args: port: Dart native port (passed in via env var by the embedding side; doubles as the keyed channel identifier). @@ -437,23 +503,70 @@ async def __run_dart_bridge_server( before_main: Optional hook called before `main`. Returns: - Started dart_bridge-server connection instance. + Started dart_bridge-server handle. """ # Imported lazily so non-embedded runs (web server, native desktop) never # try to load libdart_bridge. from flet.messaging.flet_dart_bridge_server import FletDartBridgeServer + loop = asyncio.get_running_loop() executor = concurrent.futures.ThreadPoolExecutor() - conn = FletDartBridgeServer( - loop=asyncio.get_running_loop(), - port=port, - on_session_created=__get_on_session_created(main), - before_main=before_main, - executor=executor, - ) - await conn.start() - return conn + def _build_conn(p: int) -> FletDartBridgeServer: + return FletDartBridgeServer( + loop=loop, + port=p, + on_session_created=__get_on_session_created(main), + before_main=before_main, + executor=executor, + ) + + handle = _DartBridgeServerHandle(_build_conn) + await handle.start(port) + + # Subscribe to Dart VM restart events. The C-side + # `dart_bridge_signal_dart_session` (libdart_bridge >= 1.3.0) fires + # every registered Python callback with `{label: new_port}`. On + # pre-1.3.0 binaries the symbol is absent and Dart-side + # `signalDartSession` is a no-op, so this handler never fires and the + # flow degrades to the existing "one-Dart-VM lifetime" behavior — no + # crash, no regression. + try: + import dart_bridge # type: ignore[import-not-found] + except ImportError: + # Not running inside libdart_bridge (e.g. unit tests). Skip + # restart subscription; nothing to swap to. + return handle + + add_handler = getattr(dart_bridge, "add_session_restart_handler", None) + if add_handler is None: + # Older libdart_bridge without the restart API. Same fallback as + # above — first session only. + logger.debug( + "dart_bridge.add_session_restart_handler not available; " + "process-reuse restarts will be unsupported" + ) + return handle + + def _on_session_restart(port_map): + # Called from libdart_bridge (under the GIL, possibly off the + # asyncio loop's thread). Marshal back onto the loop and run the + # swap as a regular asyncio task. + new_port = int(port_map.get("protocol", 0)) + if new_port <= 0 or new_port == port: + return + logger.info( + "Dart VM restart detected; rebinding FletDartBridgeServer " + "from port %s → %s", + port, + new_port, + ) + loop.call_soon_threadsafe( + lambda: asyncio.create_task(handle._swap_to_port(new_port)) + ) + + add_handler(_on_session_restart) + return handle async def __run_web_server( From 70a96e72c97d482743a225e34b1e9e0086451089 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 11:49:23 -0700 Subject: [PATCH 31/47] feat(build): wire process-reuse signal + skip-on-reuse in build template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit native_runtime.dart `initBridges`: * Unconditionally calls `DartBridge.instance.signalDartSession(...)` with the new `{protocol, exit}` port map after allocating the bridges. * On fresh start: libdart_bridge has no embedded Python yet → no-op. * On Android process reuse: fires every Python session-restart handler registered by flet.app + python.dart bootstrap, so the running Python rebinds to the new ports. * Exposes `pythonAlreadyRunning` (forwards `DartBridge.isPythonInitialized`) so `main.dart` can skip `SeriousPython.runProgram` on reuse — the embedded Python is already running with the user's `main()`, and trying to run it again would either crash (pre-1.3.0) or no-op-return immediately (1.3.0+). main.dart `runPythonApp`: * Checks `nrt.pythonAlreadyRunning` and parks on an unresolved future when true. Keeps the existing `FutureBuilder` plumbing intact while the just-restarted Dart VM picks up its UI session through the already-rewired FletDartBridgeServer. Requires libdart_bridge >= 1.3.0 for the actual process-reuse path to fire; against older binaries the soft-lookup in serious-python's DartBridge bindings drops `signalDartSession` to a no-op and `pythonAlreadyRunning` returns false — degrades to the pre-1.3.0 behavior (no process-reuse support, no crash either). --- .../{{cookiecutter.out_dir}}/lib/main.dart | 14 ++++++++++ .../lib/native_runtime.dart | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart index 2a124d81bd..73b8557f6d 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/main.dart @@ -236,6 +236,20 @@ Future prepareApp() async { } Future runPythonApp(List args) async { + // Process-reuse path: Android may keep the OS process alive across a + // back-button quit and restart only the Dart VM. libdart_bridge stays + // loaded, Python is still up. `initBridges()` already fired + // `dart_bridge_signal_dart_session` with the new ports — Python's + // session-restart handlers have rewired by now. Don't call into + // `SeriousPython.runProgram` again (it would no-op-return immediately + // anyway, but the never-completing-future park here keeps the + // FletApp's existing FutureBuilder rendering until the OS tears us + // down for real). + if (nrt.pythonAlreadyRunning) { + debugPrint( + "Python already initialized (process reuse) — skipping SeriousPython.runProgram"); + return Completer().future; + } return nrt.runPython( moduleName: pythonModuleName, appDir: appDir, diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart index 6e3ef30475..ab281e4901 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime.dart @@ -31,9 +31,26 @@ PythonBridge? _exitBridge; /// Allocate the protocol + exit bridges, stamp the required env vars onto /// [envVars], and return the `dartbridge://` page URL. +/// +/// Also fires `dart_bridge_signal_dart_session` unconditionally. On a fresh +/// start where libdart_bridge hasn't initialized Python yet, the call is a +/// cheap no-op inside the C library. On Android process reuse — where the +/// OS kept this process alive across a Dart VM restart and Python is still +/// loaded with handlers on the previous (now-dead) ports — it dispatches +/// the new port numbers to the running Python session-restart subscribers +/// so they can rewire transparently. See libdart_bridge >= 1.3.0. String initBridges(Map envVars) { _bridge = PythonBridge(); _exitBridge = PythonBridge(); + + // Signal the running Python (if any) about the new Dart native ports. + // The labels here match what `serious_python_run` reconstructs from + // env vars on its own reuse path — keep in sync. + DartBridge.instance.signalDartSession({ + "protocol": _bridge!.port, + "exit": _exitBridge!.port, + }); + envVars.putIfAbsent("FLET_DART_BRIDGE_PORT", () => _bridge!.port.toString()); envVars.putIfAbsent( "FLET_DART_BRIDGE_EXIT_PORT", @@ -44,6 +61,15 @@ String initBridges(Map envVars) { bool get bridgesActive => _bridge != null; +/// True when libdart_bridge already has an embedded CPython up from a +/// previous Dart VM in the same OS process. On Android the OS may keep +/// the process alive across a back-button exit and restart only the Dart +/// VM on re-launch — this flag lets `runPythonApp` skip +/// `SeriousPython.runProgram` (which would otherwise wedge waiting on a +/// Python interpreter that's already running). Always false on a fresh +/// process, and false when running against pre-1.3.0 libdart_bridge. +bool get pythonAlreadyRunning => DartBridge.instance.isPythonInitialized; + /// Extract the bundled app.zip into a fresh app directory and return its /// path. Wraps `serious_python.extractAssetZip` so main.dart doesn't have /// to import the FFI-touching package directly. From fc0771bbe5b03125e2d358555a28505e82261dcb Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 11:49:39 -0700 Subject: [PATCH 32/47] feat(build): mutable _exit_port + native-log tee in python.dart bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related changes to the bundled bootstrap script: 1. `_exit_port` becomes a one-element list so the session-restart handler can update it in place on Android process reuse. Without this, after a Dart VM restart, `flet_exit` would still post the exit code to the OLD (now-dead) PythonBridge port and the host process would never receive the signal. Subscribe to `dart_bridge.add_session_restart_handler` (>= 1.3.0) to mutate `_exit_port[0]` whenever the new VM signals fresh ports. `getattr` lookup keeps older libdart_bridge working — process-reuse is just unsupported on those, exit-code routing unchanged. 2. `sys.stdout` / `sys.stderr` become `_TeeWriter` instances that duplicate writes to BOTH the libdart_bridge native log writer (logcat / os_log / stderr, installed at Py_Initialize by 1.3.0+) AND the existing `out_file` (the error-screen capture file). Previously the script overwrote sys.stdout/stderr with just `out_file`, which silenced the new native log path entirely on Android — the whole point of the stdio install in libdart_bridge. On older libdart_bridge without the native install, the tee falls back to writing through the default text streams + file, so error capture works the same as today and there's no console output regression. --- .../{{cookiecutter.out_dir}}/lib/python.dart | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart index 679577c337..b0dd7a46fe 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart @@ -56,24 +56,74 @@ def initialize_ctypes(): initialize_ctypes() out_file = open("{outLogFilename}", "w+", buffering=1) -sys.stdout = sys.stderr = out_file + +# libdart_bridge >= 1.3.0 installs native-log file-like wrappers as +# sys.stdout / sys.stderr right after Py_Initialize so prints land in +# logcat (Android) / os_log (iOS) / stderr (desktop). Tee here so the +# error-screen capture file ALSO gets the output — both paths matter. +# On older libdart_bridge (no native install) `_native_*` will be the +# default text streams; the tee still works as a plain duplicate write. +_native_stdout = sys.stdout +_native_stderr = sys.stderr + +class _TeeWriter: + def __init__(self, native, file_): + self._native = native + self._file = file_ + def write(self, text): + try: + self._native.write(text) + except Exception: + pass + return self._file.write(text) + def flush(self): + try: + self._native.flush() + except Exception: + pass + self._file.flush() + def isatty(self): + return False + def fileno(self): + return self._file.fileno() + +sys.stdout = _TeeWriter(_native_stdout, out_file) +sys.stderr = _TeeWriter(_native_stderr, out_file) # Exit-code transport. The Dart side allocated a dedicated PythonBridge port # (FLET_DART_BRIDGE_EXIT_PORT) and is listening on it. `flet_exit` posts the # exit code as raw UTF-8 bytes through that bridge — the Dart side parses, # then either renders the error screen (code == 100) or terminates the host # process (any other code) using the file we wrote to above. +# +# On Android process reuse (Dart VM restarts while libdart_bridge stays +# loaded), the exit-bridge port number changes. We keep `_exit_port` in a +# one-element list so the session-restart handler below can mutate it in +# place — `flet_exit` always reads the current value. import dart_bridge # built-in module provided by libdart_bridge -_exit_port = int(os.environ["FLET_DART_BRIDGE_EXIT_PORT"]) +_exit_port = [int(os.environ["FLET_DART_BRIDGE_EXIT_PORT"])] def flet_exit(code=0): try: - dart_bridge.send_bytes(_exit_port, str(code).encode()) + dart_bridge.send_bytes(_exit_port[0], str(code).encode()) finally: out_file.close() sys.exit = flet_exit +# Subscribe to new-Dart-VM signals if the running libdart_bridge supports +# them (>= 1.3.0). On process reuse, the new VM's port-map carries the +# fresh exit-bridge port number; rewire so flet_exit talks to the right +# Dart side. Older libdart_bridge doesn't expose the handler — fall +# through silently and the existing single-VM behaviour holds. +_add_restart = getattr(dart_bridge, "add_session_restart_handler", None) +if _add_restart is not None: + def _on_dart_session_restart(port_map): + new_exit = port_map.get("exit") + if new_exit is not None: + _exit_port[0] = int(new_exit) + _add_restart(_on_dart_session_restart) + ex = None try: import certifi From 04ef5bcac4f0c1e1082db75f94642d794428b5c8 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 12:29:08 -0700 Subject: [PATCH 33/47] fix(build): line-buffer the native-log side of python.dart's TeeWriter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python's `print(x)` is internally two writes: `write(x)` then `write("\n")`. libdart_bridge's native-log writer strips the trailing newline before calling `__android_log_write`, so the standalone "\n" write becomes an empty string and produces a blank logcat row after every print: I Hello, Flet! I I Another line of output. I I And another one. I The file half of the tee still receives writes raw — `out_file` keeps byte-for-byte parity with what Python wrote so the error-screen capture matches a plain `python` console run. The native half now accumulates until "\n", emits the line without the trailing newline, and skips purely empty lines (so `print()` with no args also stays out of the log instead of producing a blank entry). `flush()` drains any pending partial line in case the user app calls it mid-line. Result is one logcat row per logical print line — what the user expects. --- .../{{cookiecutter.out_dir}}/lib/python.dart | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart index b0dd7a46fe..1065d7b89f 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/python.dart @@ -67,17 +67,45 @@ _native_stdout = sys.stdout _native_stderr = sys.stderr class _TeeWriter: + # The file half receives writes raw — preserves byte-for-byte parity + # with what Python wrote, so the error-screen capture file matches a + # plain `python` console run. + # + # The native (logcat / os_log) half is line-buffered so Python's + # `print(x)` doesn't produce two log entries — CPython implements + # `print` as `write(text)` + `write("\\n")`, and the standalone + # newline write would otherwise show as a blank logcat row after + # every print. We accumulate until we see "\\n", emit the line + # without the trailing newline, and skip purely empty lines (so + # `print()` with no args also stays out of the log). def __init__(self, native, file_): self._native = native self._file = file_ + self._native_buf = "" def write(self, text): + if not text: + return 0 try: - self._native.write(text) + self._native_buf += text + while True: + nl = self._native_buf.find("\\n") + if nl < 0: + break + line = self._native_buf[:nl] + self._native_buf = self._native_buf[nl + 1:] + if line: + self._native.write(line) except Exception: pass return self._file.write(text) def flush(self): try: + # Drain any pending partial line (no trailing newline in the + # source stream — could happen if the user app calls + # `sys.stdout.flush()` mid-line). + if self._native_buf: + self._native.write(self._native_buf) + self._native_buf = "" self._native.flush() except Exception: pass From ecb53e2f02a4864dc7cacba081e0ae88c857bbba Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 14:01:08 -0700 Subject: [PATCH 34/47] Bump screen_brightness to 2.1.11 Add a local Swift package product (FlutterGeneratedPluginSwiftPackage) to the macOS Xcode project and Runner scheme, including a pre-build action to run Flutter's macos_assemble.sh prepare step to ensure the framework is prepared. Update Windows plugin registration to use the screen_brightness_windows C API header and registration function. Bump screen_brightness in packages/flet and remove prior direct overrides from client and template pubspecs so the project uses the newer package resolution and avoids the previous workaround for macOS/SVM target issues. --- client/pubspec.lock | 28 +++++++++---------- client/pubspec.yaml | 7 ----- .../flutter/generated_plugin_registrant.cc | 6 ++-- packages/flet/pubspec.yaml | 2 +- .../{{cookiecutter.out_dir}}/pubspec.yaml | 8 ------ 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 2d14a27cfc..79e83f043d 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1364,45 +1364,45 @@ packages: source: hosted version: "2.0.3" screen_brightness: - dependency: "direct overridden" + dependency: transitive description: name: screen_brightness - sha256: "5f70754028f169f059fdc61112a19dcbee152f8b293c42c848317854d650cba3" + sha256: e0edf92c08889e8f493cde291e7c687db2b4a1471f2371c074070b75d7c7d79b url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.11" screen_brightness_android: dependency: transitive description: name: screen_brightness_android - sha256: d34f5321abd03bc3474f4c381f53d189117eba0b039eac1916aa92cca5fd0a96 + sha256: "2008ad8e9527cc968f7a4de1ec58b476d495b3c612a149dbd6550c4f046da147" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.6" screen_brightness_ios: dependency: transitive description: name: screen_brightness_ios - sha256: "2493953340ecfe8f4f13f61db50ce72533a55b0bbd58ba1402893feecf3727f5" + sha256: "352d355e8523a186ba1e494b74218e5ca96e51975a0630b8ed89fbb7ff609762" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" screen_brightness_macos: - dependency: "direct overridden" + dependency: transitive description: name: screen_brightness_macos - sha256: "278712cf5288db57bd335968cbfb2ec5441028f1ee2fcbdc8d1582d8210a3442" + sha256: b0957237b842d846a84363b69f229b339a8f91faced55041e245b2ebff7b0142 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" screen_brightness_ohos: dependency: transitive description: name: screen_brightness_ohos - sha256: a93a263dcd39b5c56e589eb495bcd001ce65cdd96ff12ab1350683559d5c5bb7 + sha256: "569a2c97909a50894b81487619eb7654f5358443a5af05f840c19261f49c0940" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" screen_brightness_platform_interface: dependency: transitive description: @@ -1415,10 +1415,10 @@ packages: dependency: transitive description: name: screen_brightness_windows - sha256: d3518bf0f5d7a884cee2c14449ae0b36803802866de09f7ef74077874b6b2448 + sha256: "368b9c822e1671de81616e48150006e39eff2a434957e47ee638b09a32b2297c" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" screen_retriever: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 09a7815bfa..79ebf5030c 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -95,13 +95,6 @@ dependency_overrides: flet: path: ../packages/flet - screen_brightness: 2.1.7 - # screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a - # Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 floor, - # which breaks Swift Package Manager resolution. Pin the last good impl until - # upstream fixes it: https://github.com/aaassseee/screen_brightness/issues/99 - screen_brightness_macos: 2.1.2 - dev_dependencies: flutter_test: sdk: flutter diff --git a/client/windows/flutter/generated_plugin_registrant.cc b/client/windows/flutter/generated_plugin_registrant.cc index 00afa0a858..c4d28c3ed8 100644 --- a/client/windows/flutter/generated_plugin_registrant.cc +++ b/client/windows/flutter/generated_plugin_registrant.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -47,8 +47,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); RiveNativePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("RiveNativePlugin")); - ScreenBrightnessWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); + ScreenBrightnessWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/packages/flet/pubspec.yaml b/packages/flet/pubspec.yaml index 3a5a277f1f..a9976ad041 100644 --- a/packages/flet/pubspec.yaml +++ b/packages/flet/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: pasteboard: ^0.4.0 provider: ^6.1.2 screenshot: ^3.0.0 - screen_brightness: ^2.1.7 + screen_brightness: ^2.1.11 sensors_plus: ^7.0.0 shared_preferences: ^2.5.3 share_plus: ^12.0.1 diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index 4019c41dfe..ec2dba4660 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -44,14 +44,6 @@ dependencies: dependency_overrides: flet: path: ../../../../../packages/flet - device_info_plus: 12.3.0 # remove that in May, 2026 once CI moved to macOS 26 - connectivity_plus: 7.0.0 # remove that in May, 2026 once CI moved to macOS 26 - screen_brightness: 2.1.7 - # screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a - # Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 floor, - # which breaks Swift Package Manager resolution. Pin the last good impl until - # upstream fixes it: https://github.com/aaassseee/screen_brightness/issues/99 - screen_brightness_macos: 2.1.2 dev_dependencies: flutter_launcher_icons: ^0.14.1 From d33506ba629c95654b4197e57e2f5d0820fcf561 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Tue, 16 Jun 2026 15:07:39 -0700 Subject: [PATCH 35/47] fix(build/web): add pythonAlreadyRunning getter to native_runtime_stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The web stub of native_runtime didn't expose the new pythonAlreadyRunning getter introduced for the Android process-reuse path, so main.dart's unconditional check `if (nrt.pythonAlreadyRunning)` failed to compile on web targets: lib/main.dart:297:11: Error: Undefined name 'pythonAlreadyRunning'. if (nrt.pythonAlreadyRunning) { ^^^^^^^^^^^^^^^^^^^^ Add the stub getter returning false — there's no embedded CPython on web (Pyodide runs via the JavaScript channel, not dart_bridge), so "already running" is by definition never true. Symmetric with how the stub treats other native-only entry points. --- .../{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart index bbb462169d..95cacb5acc 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/lib/native_runtime_stub.dart @@ -12,6 +12,11 @@ String initBridges(Map envVars) => bool get bridgesActive => false; +/// Always false on web — there's no embedded CPython to be "already +/// running". Mirrors `native_runtime.dart`'s getter so `main.dart`'s +/// process-reuse check compiles for the web target. +bool get pythonAlreadyRunning => false; + Future extractAppAssets(String assetPath, {bool checkHash = false}) => throw UnsupportedError("Asset extraction not available on web"); From 17ad99592e26dddebfb2a7cb55e1b75e98b0f5ad Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 11:54:29 -0700 Subject: [PATCH 36/47] build(android): consume serious_python native-mmap packaging + --android-extract-packages (#6595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(android): consume serious_python native-mmap packaging + extract-packages option - Build template: point serious_python git ref at android-native-mmap; drop the stale packaging{jniLibs{useLegacyPackaging=true; keepDebugSymbols}} block from the Android app build.gradle.kts (native modules now load mmap'd from the APK; modern packaging at minSdk 23+ is all that's needed). - flet-cli: add --android-extract-packages CLI flag + [tool.flet.android].extract_packages (cross-platform [tool.flet].extract_packages fallback), a built-in ANDROID_DEFAULT_EXTRACT_PACKAGES set (certifi), merged and exported as SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES for the serious_python package step. Mirrors the existing --source-packages -> SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS flow. * build(android): route extract-packages env to the flutter build + docs The serious_python_android Gradle split (which partitions site-packages into the stored sitepackages.zip vs the extracted extract.zip) runs during `flutter build`, not the `serious_python package` step. Set SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES on build_env (resolved once into self.android_extract_packages) so the default set (certifi) and user list actually reach the split — previously it was set on the package step's env and silently ignored, leaving extract.zip empty. Add the "Android extract packages" publish-docs section (resolution order, CLI + pyproject example), a CHANGELOG entry, and allowlist `certifi` in typos. Verified end-to-end on an arm64 emulator via both `flet build apk` and `flet build aab` (bundletool split install): numpy mmap'd from the APK, certifi extracted to disk, flet icons.json read from the stored zip, flet.version resolved, app launched. * build(android): empty the default extract set — certifi is zip-safe certifi reads cacert.pem via importlib.resources.as_file(), which extracts it to a temp file on demand, so certifi.where() works from inside the stored sitepackages.zip (verified: imported via zipimport, where() returns a valid 234KB cacert.pem). It does not need to ship extracted. Make ANDROID_DEFAULT_EXTRACT_PACKAGES empty — the common data-bundling packages use importlib.resources (zip-safe). The --android-extract-packages / [tool.flet.android].extract_packages mechanism stays for genuinely path-hungry packages (those reading data via __file__ / pkg_resources). Update docs and CHANGELOG accordingly. --- CHANGELOG.md | 1 + sdk/python/_typos.toml | 2 + .../src/flet_cli/commands/build_base.py | 48 +++++++++++++++++++ .../android/app/build.gradle.kts | 15 ++---- .../{{cookiecutter.out_dir}}/pubspec.yaml | 9 ++-- website/docs/publish/index.md | 39 +++++++++++++++ 6 files changed, 99 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4c8d092d..955bc603e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Improvements +* **Smaller Android apps with no native-packaging config.** `flet build apk`/`aab` consume serious_python's new Android packaging: Python extension modules load **memory-mapped directly from the APK** (no extraction to disk), and pure Python ships in stored asset zips read via `zipimport`, so the standard library is no longer duplicated per ABI. Apps no longer need `useLegacyPackaging` / `keepDebugSymbols` — the `flet build` Android template drops them; just use `minSdk 23+`. New `--android-extract-packages` flag and `[tool.flet.android].extract_packages` ship "path-hungry" packages — those that read bundled data via `__file__` / `pkg_resources` instead of `importlib.resources` — extracted to disk instead of inside the zip (most packages, including `certifi`, are zip-safe and need no entry). Requires `serious_python` with the native-mmap packaging (dart_bridge 1.4.0). * Pyodide is no longer pre-baked into the `flet build` template. Each `flet build web` / `flet publish` run downloads the matching `pyodide-core-.tar.bz2` (plus the runtime `micropip` and `packaging` wheels) into a per-version cache at `~/.flet/pyodide//` and copies the files into the build output. Subsequent builds reuse the cache; the older `0.27.5` bundle previously shipped in the cookiecutter template is gone ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * The supported Python / Pyodide / dart_bridge versions are loaded on demand from `flet-dev/python-build`'s date-keyed `manifest.json` (fetched once and cached under `~/.flet/cache`), the single source of truth shared with `serious_python` — replacing flet's hand-mirrored version table. `flet build` forwards only `SERIOUS_PYTHON_VERSION` and lets `serious_python` derive the full version / build date / dart_bridge version from its own committed snapshot. The module exposes `get_supported_python_versions()` / `get_default_python_version()` (the previous `SUPPORTED_PYTHON_VERSIONS` / `DEFAULT_PYTHON_VERSION` constants are removed) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * `flet --version` shows just the Flet and Flutter versions; the static `Pyodide: …` line and the global `flet.version.pyodide_version` export are removed (the supported Python / Pyodide set now lives in python-build's manifest, not the CLI output) ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. diff --git a/sdk/python/_typos.toml b/sdk/python/_typos.toml index e68027a320..b503150fa2 100644 --- a/sdk/python/_typos.toml +++ b/sdk/python/_typos.toml @@ -4,3 +4,5 @@ AACHE = "AACHE" ROUTEROS = "ROUTEROS" # OpenType variable font axis tag for width wdth = "wdth" +# Python package name (Mozilla CA bundle) +certifi = "certifi" diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index d5d44a5223..e29a484980 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -42,6 +42,18 @@ "v{version}/flet-build-template.zip" ) +# Android (serious_python native-mmap packaging): pure Python ships in stored zips +# read via zipimport, which breaks packages that read bundled data through a real +# filesystem path (__file__ / pkg_resources) instead of importlib.resources. Such +# packages are shipped extracted to disk via --android-extract-packages or +# [tool.flet.android].extract_packages. +# +# The default set is empty: the common offenders read their data via +# importlib.resources, which is zip-safe (e.g. certifi.where() works from the zip — +# importlib.resources.as_file() extracts cacert.pem to a temp file on demand). Add +# real offenders here as they are found. +ANDROID_DEFAULT_EXTRACT_PACKAGES: list[str] = [] + class BaseBuildCommand(BaseFlutterCommand): """ @@ -505,6 +517,15 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: default=[], help="The list of Python packages to install from source distributions", ) + parser.add_argument( + "--android-extract-packages", + dest="android_extract_packages", + nargs="+", + default=[], + help="Android only: Python packages (relative paths) to ship extracted " + "to disk instead of inside the app zip — for packages that read bundled " + "data via __file__ / pkg_resources rather than importlib.resources", + ) parser.add_argument( "--python-version", dest="python_version", @@ -2037,6 +2058,26 @@ def package_python_app(self): source_packages ) + # android-extract-packages: path-hungry packages shipped extracted to disk + # instead of inside the zip (serious_python Android native-mmap packaging). + # A built-in default set covers commonly-broken packages; the user list + # (CLI / pyproject) is merged on top. Consumed by the serious_python_android + # Gradle split during `flutter build`, so the env var is set on build_env + # (see _run_flutter_command), not on the package step. + self.android_extract_packages: list[str] = [] + if self.package_platform == "Android": + user_extract_packages = ( + self.options.android_extract_packages + or self.get_pyproject( + f"tool.flet.{self.config_platform}.extract_packages" + ) + or self.get_pyproject("tool.flet.extract_packages") + or [] + ) + self.android_extract_packages = list( + dict.fromkeys(ANDROID_DEFAULT_EXTRACT_PACKAGES + user_extract_packages) + ) + if self.get_bool_setting(self.options.compile_app, "compile.app", False): package_args.append("--compile-app") @@ -2232,6 +2273,13 @@ def _run_flutter_command(self): self.build_dir / "site-packages" ) + # Path-hungry packages to ship extracted to disk: consumed by the + # serious_python_android Gradle split during `flutter build`. + if self.package_platform == "Android" and self.android_extract_packages: + build_env["SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES"] = ",".join( + self.android_extract_packages + ) + if self.package_platform == "Emscripten" and not self.template_data["no_wasm"]: build_args.append("--wasm") diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts index fbc14d0f87..7a75bc2714 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts @@ -23,17 +23,10 @@ android { compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion - packaging { - jniLibs { - useLegacyPackaging = true - keepDebugSymbols += listOf( - "*/arm64-v8a/libpython*.so", - "*/armeabi-v7a/libpython*.so", - "*/x86/libpython*.so", - "*/x86_64/libpython*.so", - ) - } - } + // No native-library packaging config is needed: serious_python loads Python + // extension modules directly from the APK (memory-mapped) and ships pure + // Python in stored asset zips. Modern packaging (the default at minSdk 23+) + // is all that's required. compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index ec2dba4660..c67d74dc08 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -17,13 +17,14 @@ dependencies: flet: path: ../../../../../packages/flet - # PythonBridge (in-process dart_bridge FFI transport) ships on the - # serious-python `dart-bridge` branch. Swap back to a version pin - # (`serious_python: ^2.0.0`) once it's published to pub.dev. + # PythonBridge (in-process dart_bridge FFI transport) + the Android + # native-mmap packaging ship on the serious-python `android-native-mmap` + # branch. Swap back to a version pin (`serious_python: ^2.0.0`) once it's + # published to pub.dev. serious_python: git: url: https://github.com/flet-dev/serious-python.git - ref: dart-bridge + ref: android-native-mmap path: src/serious_python # MsgPack codec used by the dart_bridge FletBackendChannel implementation diff --git a/website/docs/publish/index.md b/website/docs/publish/index.md index 4a512776b4..5e597de6bd 100644 --- a/website/docs/publish/index.md +++ b/website/docs/publish/index.md @@ -695,6 +695,45 @@ source_packages = ["package1", "package2"] +### Android extract packages + +:::note +[Android](android.md) only. +::: + +On Android, pure Python ships in a stored zip read in place (`zipimport`) and native modules are +loaded memory-mapped from the APK. Packages that read their bundled **data files** through a real +filesystem path — `__file__` / `pkg_resources` instead of +[`importlib.resources`](https://docs.python.org/3/library/importlib.resources.html) — don't work +from inside the zip. List such "path-hungry" packages here to ship them **extracted to disk** +instead. + +Most packages that bundle data (including `certifi`) read it through `importlib.resources`, which +is zip-safe, so they need no entry here — only add packages that actually fail to find their data +when imported from the zip. + +#### Resolution order + +1. [`--android-extract-packages`](../cli/flet-build.md#--android-extract-packages) +2. `[tool.flet.android].extract_packages` +3. `[tool.flet].extract_packages` + +#### Example + + + +```bash +flet build apk --android-extract-packages package1 package2 +``` + + +```toml +[tool.flet.android] +extract_packages = ["package1", "package2"] +``` + + + ### Icons :::note[Platform support] From 6afd14ec93a294cec5eaf9bd0d199c725aa94b30 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 12:17:56 -0700 Subject: [PATCH 37/47] Move Android extract packages to android.md Relocate the "Android extract packages" section from publish/index.md into publish/android.md so Android-specific guidance lives on the Android doc. The moved content explains path-hungry packages, zipimport limitations, the resolution order (CLI flag, [tool.flet.android], [tool.flet]) and includes examples for the CLI and pyproject.toml. Removes the duplicate section from the index to keep platform-specific docs consolidated. --- website/docs/publish/android.md | 40 +++++++++++++++++++++++++++++++++ website/docs/publish/index.md | 39 -------------------------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/website/docs/publish/android.md b/website/docs/publish/android.md index fdd4207de4..1f812ab71d 100644 --- a/website/docs/publish/android.md +++ b/website/docs/publish/android.md @@ -763,6 +763,46 @@ adaptive_icon_background = "#0B6BFF" ``` + +## Android extract packages + +:::note +[Android](android.md) only. +::: + +On Android, pure Python ships in a stored zip read in place (`zipimport`) and native modules are +loaded memory-mapped from the APK. Packages that read their bundled **data files** through a real +filesystem path — `__file__` / `pkg_resources` instead of +[`importlib.resources`](https://docs.python.org/3/library/importlib.resources.html) — don't work +from inside the zip. List such "path-hungry" packages here to ship them **extracted to disk** +instead. + +Most packages that bundle data (including `certifi`) read it through `importlib.resources`, which +is zip-safe, so they need no entry here — only add packages that actually fail to find their data +when imported from the zip. + +### Resolution order + +1. [`--android-extract-packages`](../cli/flet-build.md#--android-extract-packages) +2. `[tool.flet.android].extract_packages` +3. `[tool.flet].extract_packages` + +### Example + + + +```bash +flet build apk --android-extract-packages package1 package2 +``` + + +```toml +[tool.flet.android] +extract_packages = ["package1", "package2"] +``` + + + ## ADB Tips [Android Debug Bridge (adb)](https://developer.android.com/tools/adb) is a diff --git a/website/docs/publish/index.md b/website/docs/publish/index.md index 5e597de6bd..4a512776b4 100644 --- a/website/docs/publish/index.md +++ b/website/docs/publish/index.md @@ -695,45 +695,6 @@ source_packages = ["package1", "package2"] -### Android extract packages - -:::note -[Android](android.md) only. -::: - -On Android, pure Python ships in a stored zip read in place (`zipimport`) and native modules are -loaded memory-mapped from the APK. Packages that read their bundled **data files** through a real -filesystem path — `__file__` / `pkg_resources` instead of -[`importlib.resources`](https://docs.python.org/3/library/importlib.resources.html) — don't work -from inside the zip. List such "path-hungry" packages here to ship them **extracted to disk** -instead. - -Most packages that bundle data (including `certifi`) read it through `importlib.resources`, which -is zip-safe, so they need no entry here — only add packages that actually fail to find their data -when imported from the zip. - -#### Resolution order - -1. [`--android-extract-packages`](../cli/flet-build.md#--android-extract-packages) -2. `[tool.flet.android].extract_packages` -3. `[tool.flet].extract_packages` - -#### Example - - - -```bash -flet build apk --android-extract-packages package1 package2 -``` - - -```toml -[tool.flet.android] -extract_packages = ["package1", "package2"] -``` - - - ### Icons :::note[Platform support] From a87385828c2d41b3749badf5247c6ab8bf7a78f6 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 12:25:59 -0700 Subject: [PATCH 38/47] Update serious_python git ref to dart-bridge Change the serious_python git dependency ref from 'android-native-mmap' to 'dart-bridge' in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This ensures the template uses the dart-bridge variant of serious-python, aligning with the dart_bridge FletBackendChannel implementation. --- .../templates/build/{{cookiecutter.out_dir}}/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index c67d74dc08..7643b685e3 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: serious_python: git: url: https://github.com/flet-dev/serious-python.git - ref: android-native-mmap + ref: dart-bridge path: src/serious_python # MsgPack codec used by the dart_bridge FletBackendChannel implementation From 81892b57e0305743b304b9ebfb5dfc89830fca47 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 14:41:48 -0700 Subject: [PATCH 39/47] build: compile app + packages to .pyc by default (with --no-compile-* opt-out) (#6598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: compile app + packages to .pyc by default flet build / flet publish now compile the app and installed packages to bytecode by default (previously off). Shipping .pyc removes per-launch recompilation, which is a large cold-start win on mobile where pure Python is imported in place from a stored zip and can't cache bytecode back to disk. - --compile-app / --compile-packages use argparse.BooleanOptionalAction, adding --no-compile-app / --no-compile-packages; the positive flags still work. - [tool.flet.compile].app / .packages default to true (get_bool_setting default flipped False -> True); precedence unchanged (CLI > per-platform > global). - Verified a compiled `flet build web` bundle: app ships .pyc only and the magic matches Pyodide's CPython 3.14 (standalone 3.14.6 compile vs Pyodide 3.14.0 — magic is frozen across a minor version's patch releases). Docs: publish "Compilation and cleanup" section updated, new breaking-changes guide + index entry, CHANGELOG breaking-changes entry. * docs: update summary to clarify that only `flet build` compiles to .pyc by default --- CHANGELOG.md | 2 +- .../src/flet_cli/commands/build_base.py | 14 ++-- website/docs/publish/index.md | 16 ++-- .../breaking-changes/compile-on-by-default.md | 78 +++++++++++++++++++ .../docs/updates/breaking-changes/index.md | 1 + 5 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 website/docs/updates/breaking-changes/compile-on-by-default.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 955bc603e3..86d4155d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ ### Breaking changes * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. -* The `flet.version.pyodide_version` module attribute and the `PYODIDE_VERSION` constant are removed. Call `flet_cli.utils.python_versions.get_supported_python_versions()` if you need the per-version Pyodide mapping programmatically ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* `flet build` / `flet publish` now **compile your app and packages to `.pyc` by default** (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (`zipimport`) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain `--no-compile-app` / `--no-compile-packages` (via `argparse.BooleanOptionalAction`; the existing `--compile-app` / `--compile-packages` still work), and `[tool.flet.compile].app` / `.packages` now default to `true`. Pass `--no-compile-*` or set them to `false` to restore the old behavior (faster iterative builds, or keeping `.py` source in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the [compile-on-by-default](/docs/updates/breaking-changes/compile-on-by-default) guide ([#6598](https://github.com/flet-dev/flet/pull/6598)) by @FeodorFitsner. * Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. ### Bug fixes diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index e29a484980..48e787eb6e 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -464,16 +464,18 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--compile-app", dest="compile_app", - action="store_true", + action=argparse.BooleanOptionalAction, default=None, - help="Pre-compile app's `.py` files to `.pyc`", + help="Pre-compile app's `.py` files to `.pyc` (on by default; " + "use --no-compile-app to disable)", ) parser.add_argument( "--compile-packages", dest="compile_packages", - action="store_true", + action=argparse.BooleanOptionalAction, default=None, - help="Pre-compile site packages' `.py` files to `.pyc`", + help="Pre-compile site packages' `.py` files to `.pyc` (on by default; " + "use --no-compile-packages to disable)", ) parser.add_argument( "--cleanup-app", @@ -2078,11 +2080,11 @@ def package_python_app(self): dict.fromkeys(ANDROID_DEFAULT_EXTRACT_PACKAGES + user_extract_packages) ) - if self.get_bool_setting(self.options.compile_app, "compile.app", False): + if self.get_bool_setting(self.options.compile_app, "compile.app", True): package_args.append("--compile-app") if self.get_bool_setting( - self.options.compile_packages, "compile.packages", False + self.options.compile_packages, "compile.packages", True ): package_args.append("--compile-packages") diff --git a/website/docs/publish/index.md b/website/docs/publish/index.md index 4a512776b4..adccba312e 100644 --- a/website/docs/publish/index.md +++ b/website/docs/publish/index.md @@ -1081,9 +1081,15 @@ removes known junk files and any additional globs you specify. (implies `cleanup-packages`) * `cleanup-packages`: remove junk files from site-packages (defaults to `true`) -By default, Flet does **not** compile your app files during packaging. -This allows the build process to complete even if there are syntax errors, -which can be useful for debugging or rapid iteration. +By default, Flet **compiles** both your app and the installed packages to `.pyc` +during packaging. Shipping bytecode avoids recompiling every module on each cold +start — a significant startup win on mobile, where pure Python is imported from a +stored zip and cannot cache bytecode back to disk. + +Pass `--no-compile-app` / `--no-compile-packages` (or set `[tool.flet.compile].app` +/ `[tool.flet.compile].packages` to `false`) to disable it — for example to speed +up iterative builds, or to keep `.py` source in the bundle so the build still +completes with syntax errors present and tracebacks show source lines. #### Resolution order @@ -1092,14 +1098,14 @@ The values of `compile-app` and `cleanup-app` are respectively determined in the 1. [`--compile-app`](../cli/flet-build.md#--compile-app) / [`--cleanup-app`](../cli/flet-build.md#--cleanup-app) 2. `[tool.flet..compile].app` / `[tool.flet..cleanup].app` 3. `[tool.flet.compile].app` / `[tool.flet.cleanup].app` -4. empty list / empty list +4. `True` / empty list The values of `compile-packages` and `cleanup-packages` are respectively determined in the following order of precedence: 1. [`--compile-packages`](../cli/flet-build.md#--compile-packages) / [`--cleanup-packages`](../cli/flet-build.md#--cleanup-packages) 2. `[tool.flet..compile].packages` / `[tool.flet..cleanup].packages` 3. `[tool.flet.compile].packages` / `[tool.flet.cleanup].packages` -4. `False` / `True` +4. `True` / `True` The values of `cleanup-app-files` and `cleanup-package-files` are respectively determined in the following order of precedence: diff --git a/website/docs/updates/breaking-changes/compile-on-by-default.md b/website/docs/updates/breaking-changes/compile-on-by-default.md new file mode 100644 index 0000000000..e62778e70d --- /dev/null +++ b/website/docs/updates/breaking-changes/compile-on-by-default.md @@ -0,0 +1,78 @@ +--- +title: "App and packages are compiled to .pyc by default" +--- + +# App and packages are compiled to `.pyc` by default + +:::note +This guide is accurate as of Flet 0.86.0. Later releases might add new APIs or +additional migration paths. + +The [breaking changes and deprecations index](.) lists the guides created for each release. +::: + +## Summary + +`flet build` now **compile your app and the installed +packages to `.pyc` by default** (via `python -m compileall -b`, with the original +`.py` files removed from the bundle). Previously compilation was off unless you +opted in with `--compile-app` / `--compile-packages` or +`[tool.flet.compile]` in `pyproject.toml`. + +Shipping bytecode removes per-launch recompilation. The win is largest on +**mobile**, where pure Python is imported from a stored, in-place zip +(`zipimport`) that cannot cache bytecode back to disk — so without precompiled +`.pyc`, every module is recompiled from source on **every** cold start. On a +mid-range Android device this costs roughly 1–2 seconds of startup, scaling with +the number of modules imported. + +## Background + +Compilation has always been available behind the `--compile-app` / +`--compile-packages` flags; it just defaulted off. With the move to in-place +`zipimport` packaging on Android, leaving it off meant a recurring cold-start tax +on every launch, so the default was flipped on. + +Compiled web builds were verified to load correctly in Pyodide: the standalone +CPython used to compile and the Pyodide runtime share the same minor version +(e.g. 3.14), and CPython keeps the `.pyc` magic number stable across patch +releases of a minor version, so the bytecode is accepted. + +## Migration guide + +Most apps need no changes — builds simply start faster. + +If you relied on uncompiled builds (to keep `.py` source in the bundle for +debugging, to let a build complete despite syntax errors, or to speed up +iterative builds), opt out explicitly. + +### Opt out via the CLI flag + +The flags are now `argparse.BooleanOptionalAction`, so each gets a `--no-` form +(the original `--compile-app` / `--compile-packages` still work): + +```bash +flet build apk --no-compile-app --no-compile-packages +``` + +### Opt out in `pyproject.toml` + +```toml +[tool.flet.compile] +app = false +packages = false +``` + +Per-platform overrides also work, e.g. `[tool.flet.android.compile]`. The +resolution order is CLI flag → `[tool.flet..compile]` → +`[tool.flet.compile]` → default (`true`). + +## Timeline + +- Changed in: `0.86.0` + +## References + +- API documentation: [Compilation and cleanup](/docs/publish#compilation-and-cleanup) +- Issues and PRs: [#6598](https://github.com/flet-dev/flet/pull/6598) +- Release notes: [Flet 0.86.0](../release-notes.md) diff --git a/website/docs/updates/breaking-changes/index.md b/website/docs/updates/breaking-changes/index.md index d95d4715d4..54cb72890d 100644 --- a/website/docs/updates/breaking-changes/index.md +++ b/website/docs/updates/breaking-changes/index.md @@ -27,6 +27,7 @@ Each guide explains the change, the reason for it, and how to migrate your code. #### Breaking changes - [Default bundled Python version is now 3.14](/docs/updates/breaking-changes/default-bundled-python-3-14) +- [App and packages are compiled to `.pyc` by default](/docs/updates/breaking-changes/compile-on-by-default) - [`flet.version.pyodide_version` and `PYODIDE_VERSION` removed](/docs/updates/breaking-changes/removed-pyodide-version-export) - [Flet protocol framing upgraded for DataChannel support](/docs/updates/breaking-changes/data-channel-protocol-upgrade) From 45d6912b09972c744d56514bc41bccb33dad0276 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 15:30:33 -0700 Subject: [PATCH 40/47] perf(flet): lazy public-API imports + defer eager subsystem clusters (mobile cold start) (#6597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(flet): lazily import the public API (PEP 562 __getattr__) `import flet` previously executed the whole ~270-module package eagerly (~240 flet modules, ~2.0s on a mid-range Android device from .pyc). Move the entire public surface behind a generated `_LAZY = {name: module}` map and a module-level `__getattr__` that imports each name on first access and caches it into globals(); the real imports stay under `if TYPE_CHECKING:` so type checkers / IDEs / `__all__` are unaffected. `__version__` stays eager. Apps now load only the modules they touch. On the counter example (moto g15), PY_BOOT -> UI built drops 2.10s -> 0.82s (import flet alone 2.02s -> 0.15s; 22 flet modules at import vs 240). Full pytest suite passes (210 passed, 9 skipped); resolved objects are identical to the eager imports by construction (the map is generated from the original import statements). * perf(flet): defer Cupertino + deprecated-service imports off the Page/View hubs Page/View/base_page are loaded by every app and eagerly pulled clusters a typical Material app never touches: - Cupertino app bar / navigation bar: only referenced in `appbar` / `navigation_bar` field & property annotations. Move the imports to TYPE_CHECKING and string-quote those (non-event) annotations so they are not evaluated at class-definition time. control_event only resolves event-handler fields, so quoting these is safe. - 4 deprecated page service properties (BrowserContextMenu, SharedPreferences, Clipboard, StoragePaths): construct their service on demand via a function-local import instead of importing the whole set at module load. Counter example: flet modules at first-frame 114 -> 108 (cupertino 4 -> 2, services 7 -> 3). Full pytest suite still passes (210 passed, 9 skipped). Note: module-wide `from __future__ import annotations` is intentionally NOT used here — flet's control_event resolves event-handler annotations at runtime via get_args(), which requires real type objects, not PEP 563 strings. * perf(flet): defer the auth subsystem off Page import Page eagerly imported the whole flet.auth subsystem (7 modules) even though a typical app never calls Page.login(). The eager edges were: module-level Authorization / OAuthProvider imports, the AuthorizationImpl default (AuthorizationService / Authorization chosen at import via is_pyodide()), and AT = TypeVar("AT", bound=Authorization). Defer all of it: - Authorization / OAuthProvider imports -> TYPE_CHECKING; their annotations are string-quoted so they aren't evaluated at class-definition time. - TypeVar bound -> "Authorization" (string forward ref). - AuthorizationImpl default -> a lazy _default_authorization_impl() helper that imports AuthorizationService / Authorization on first Page.login() call; the login() authorization parameter defaults to None and resolves to it. Counter example: flet modules at first-frame 108 -> 101 (flet.auth 7 -> 0). Full pytest suite passes (210 passed, 9 skipped), including test_auth_lazy_imports. * perf(flet): defer the components/hooks subsystem off the core import path Page / BasePage / Session pulled the whole components + hooks subsystem (9 modules) at import even though most apps use no components or hooks: - public_utils.unwrap_component (called by Page/BasePage on hot paths) imported Component at module load just for an isinstance check. Import it lazily and cache it, so importing the helper no longer pulls component.py. - Page.render / render_views import Renderer function-locally (only component apps call these). - Session.schedule_effect's EffectHook parameter is annotation-only -> move to TYPE_CHECKING and string-quote (Session never constructs EffectHook; the hook is passed in by the component runtime). Counter example: flet modules at first-frame 101 -> 94 (flet.components 9 -> 2, the two remaining being the empty package and the lightweight public_utils). The components/hooks machinery now loads on first actual use. Full pytest suite passes (210 passed, 9 skipped). --- sdk/python/packages/flet/src/flet/__init__.py | 1849 +++++++++++------ .../flet/src/flet/components/public_utils.py | 17 +- .../flet/src/flet/controls/base_page.py | 18 +- .../flet/src/flet/controls/core/view.py | 16 +- .../packages/flet/src/flet/controls/page.py | 52 +- .../flet/src/flet/messaging/session.py | 10 +- 6 files changed, 1296 insertions(+), 666 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index 37ac5880b9..f8053fc911 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -1,577 +1,626 @@ from typing import TYPE_CHECKING -from flet.app import AppCallable, app, app_async, run, run_async -from flet.components.component import Component -from flet.components.component_decorator import component -from flet.components.hooks.use_callback import use_callback -from flet.components.hooks.use_context import ( - ContextProvider, - create_context, - use_context, -) -from flet.components.hooks.use_dialog import use_dialog -from flet.components.hooks.use_effect import ( - on_mounted, - on_unmounted, - on_updated, - use_effect, -) -from flet.components.hooks.use_memo import use_memo -from flet.components.hooks.use_ref import use_ref -from flet.components.hooks.use_state import use_state -from flet.components.memo import memo -from flet.components.observable import Observable, observable -from flet.components.public_utils import unwrap_component -from flet.components.router import ( - LocationInfo, - Route, - Router, - is_route_active, - use_route_loader_data, - use_route_location, - use_route_outlet, - use_route_params, - use_view_path, -) -from flet.controls import alignment, border, border_radius, margin, padding -from flet.controls.adaptive_control import AdaptiveControl -from flet.controls.alignment import Alignment, Axis -from flet.controls.animation import ( - Animation, - AnimationCurve, - AnimationStyle, - AnimationValue, -) -from flet.controls.base_control import BaseControl, Value, control, value -from flet.controls.base_page import BasePage, PageMediaData, PageResizeEvent -from flet.controls.blur import ( - Blur, - BlurTileMode, - BlurValue, -) -from flet.controls.border import ( - Border, - BorderSide, - BorderSideStrokeAlign, - BorderSideStrokeAlignValue, - BorderStyle, -) -from flet.controls.border_radius import ( - BorderRadius, - BorderRadiusValue, -) -from flet.controls.box import ( - BlurStyle, - BoxConstraints, - BoxDecoration, - BoxFit, - BoxShadow, - BoxShadowValue, - BoxShape, - ColorFilter, - DecorationImage, - FilterQuality, -) -from flet.controls.buttons import ( - BeveledRectangleBorder, - ButtonStyle, - CircleBorder, - ContinuousRectangleBorder, - OutlinedBorder, - RoundedRectangleBorder, - ShapeBorder, - StadiumBorder, -) -from flet.controls.colors import Colors -from flet.controls.context import Context, context -from flet.controls.control import Control -from flet.controls.control_event import ( - ControlEvent, - ControlEventHandler, - Event, - EventControlType, - EventHandler, -) -from flet.controls.control_state import ( - ControlState, - ControlStateValue, -) -from flet.controls.core.animated_switcher import ( - AnimatedSwitcher, - AnimatedSwitcherTransition, -) -from flet.controls.core.autofill_group import ( - AutofillGroup, - AutofillGroupDisposeAction, - AutofillHint, -) -from flet.controls.core.column import Column -from flet.controls.core.dismissible import ( - Dismissible, - DismissibleDismissEvent, - DismissibleUpdateEvent, -) -from flet.controls.core.drag_target import ( - DragTarget, - DragTargetEvent, - DragTargetLeaveEvent, - DragWillAcceptEvent, -) -from flet.controls.core.draggable import Draggable -from flet.controls.core.flet_app import FletApp, FletAppOutputEvent -from flet.controls.core.gesture_detector import GestureDetector -from flet.controls.core.grid_view import GridView -from flet.controls.core.hero import Hero -from flet.controls.core.icon import Icon -from flet.controls.core.image import Image -from flet.controls.core.interactive_viewer import InteractiveViewer -from flet.controls.core.keyboard_listener import ( - KeyboardListener, - KeyDownEvent, - KeyRepeatEvent, - KeyUpEvent, -) -from flet.controls.core.list_view import ListView -from flet.controls.core.markdown import ( - Markdown, - MarkdownCodeTheme, - MarkdownCustomCodeTheme, - MarkdownExtensionSet, - MarkdownStyleSheet, -) -from flet.controls.core.merge_semantics import MergeSemantics -from flet.controls.core.page_view import PageView -from flet.controls.core.pagelet import Pagelet -from flet.controls.core.placeholder import Placeholder -from flet.controls.core.reorderable_drag_handle import ReorderableDragHandle -from flet.controls.core.responsive_row import ResponsiveRow -from flet.controls.core.rotated_box import RotatedBox -from flet.controls.core.row import Row -from flet.controls.core.safe_area import SafeArea -from flet.controls.core.screenshot import Screenshot -from flet.controls.core.semantics import Semantics -from flet.controls.core.shader_mask import ShaderMask -from flet.controls.core.shimmer import Shimmer, ShimmerDirection -from flet.controls.core.stack import Stack, StackFit -from flet.controls.core.text import ( - Text, - TextAffinity, - TextSelection, - TextSelectionChangeCause, - TextSelectionChangeEvent, -) -from flet.controls.core.text_span import TextSpan -from flet.controls.core.transparent_pointer import TransparentPointer -from flet.controls.core.view import View -from flet.controls.core.window import ( - Window, - WindowEvent, - WindowEventType, - WindowResizeEdge, -) -from flet.controls.core.window_drag_area import WindowDragArea -from flet.controls.cupertino import cupertino_colors -from flet.controls.cupertino.cupertino_action_sheet import CupertinoActionSheet -from flet.controls.cupertino.cupertino_action_sheet_action import ( - CupertinoActionSheetAction, -) -from flet.controls.cupertino.cupertino_activity_indicator import ( - CupertinoActivityIndicator, -) -from flet.controls.cupertino.cupertino_alert_dialog import CupertinoAlertDialog -from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar -from flet.controls.cupertino.cupertino_bottom_sheet import CupertinoBottomSheet -from flet.controls.cupertino.cupertino_button import ( - CupertinoButton, - CupertinoButtonSize, -) -from flet.controls.cupertino.cupertino_checkbox import CupertinoCheckbox -from flet.controls.cupertino.cupertino_colors import CupertinoColors -from flet.controls.cupertino.cupertino_context_menu import CupertinoContextMenu -from flet.controls.cupertino.cupertino_context_menu_action import ( - CupertinoContextMenuAction, -) -from flet.controls.cupertino.cupertino_date_picker import ( - CupertinoDatePicker, - CupertinoDatePickerDateOrder, - CupertinoDatePickerMode, -) -from flet.controls.cupertino.cupertino_dialog_action import CupertinoDialogAction -from flet.controls.cupertino.cupertino_filled_button import CupertinoFilledButton -from flet.controls.cupertino.cupertino_list_tile import CupertinoListTile -from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar -from flet.controls.cupertino.cupertino_picker import CupertinoPicker -from flet.controls.cupertino.cupertino_radio import CupertinoRadio -from flet.controls.cupertino.cupertino_segmented_button import CupertinoSegmentedButton -from flet.controls.cupertino.cupertino_slider import CupertinoSlider -from flet.controls.cupertino.cupertino_sliding_segmented_button import ( - CupertinoSlidingSegmentedButton, -) -from flet.controls.cupertino.cupertino_switch import CupertinoSwitch -from flet.controls.cupertino.cupertino_textfield import ( - CupertinoTextField, - OverlayVisibilityMode, -) -from flet.controls.cupertino.cupertino_timer_picker import ( - CupertinoTimerPicker, - CupertinoTimerPickerMode, -) -from flet.controls.cupertino.cupertino_tinted_button import CupertinoTintedButton -from flet.controls.device_info import ( - AndroidBuildVersion, - AndroidDeviceInfo, - DeviceInfo, - IosDeviceInfo, - IosUtsname, - LinuxDeviceInfo, - MacOsDeviceInfo, - WebBrowserName, - WebDeviceInfo, - WindowsDeviceInfo, -) -from flet.controls.dialog_control import DialogControl -from flet.controls.duration import ( - DateTimeValue, - Duration, - DurationValue, -) -from flet.controls.events import ( - DragDownEvent, - DragEndEvent, - DragStartEvent, - DragUpdateEvent, - ForcePressEvent, - HoverEvent, - LongPressDownEvent, - LongPressEndEvent, - LongPressMoveUpdateEvent, - LongPressStartEvent, - MultiTapEvent, - PointerEvent, - ScaleEndEvent, - ScaleStartEvent, - ScaleUpdateEvent, - ScrollEvent, - TapEvent, - TapMoveEvent, -) -from flet.controls.exceptions import ( - FletException, - FletPageDisconnectedException, - FletUnimplementedPlatformException, - FletUnsupportedPlatformException, -) -from flet.controls.geometry import Rect, Size -from flet.controls.gradients import ( - Gradient, - GradientTileMode, - LinearGradient, - RadialGradient, - SweepGradient, -) -from flet.controls.icon_data import IconData -from flet.controls.id_counter import IdCounter -from flet.controls.keys import Key, KeyValue, ScrollKey, ValueKey -from flet.controls.layout_control import ( - ConstrainedControl, - LayoutControl, - LayoutSizeChangeEvent, -) -from flet.controls.margin import Margin, MarginValue -from flet.controls.material import dropdown, dropdownm2 -from flet.controls.material.alert_dialog import AlertDialog -from flet.controls.material.app_bar import AppBar -from flet.controls.material.auto_complete import ( - AutoComplete, - AutoCompleteSelectEvent, - AutoCompleteSuggestion, -) -from flet.controls.material.badge import Badge, BadgeValue -from flet.controls.material.banner import Banner -from flet.controls.material.bottom_app_bar import BottomAppBar -from flet.controls.material.bottom_sheet import BottomSheet -from flet.controls.material.button import Button -from flet.controls.material.card import Card, CardVariant -from flet.controls.material.checkbox import Checkbox -from flet.controls.material.chip import Chip -from flet.controls.material.circle_avatar import CircleAvatar -from flet.controls.material.container import Container -from flet.controls.material.context_menu import ( - ContextMenu, - ContextMenuDismissEvent, - ContextMenuSelectEvent, - ContextMenuTrigger, -) -from flet.controls.material.datatable import ( - DataCell, - DataColumn, - DataColumnSortEvent, - DataRow, - DataTable, -) -from flet.controls.material.date_picker import ( - DatePicker, - DatePickerEntryMode, - DatePickerEntryModeChangeEvent, - DatePickerMode, -) -from flet.controls.material.date_range_picker import DateRangePicker -from flet.controls.material.divider import Divider -from flet.controls.material.dropdown import Dropdown, DropdownOption -from flet.controls.material.dropdownm2 import DropdownM2 -from flet.controls.material.elevated_button import ElevatedButton -from flet.controls.material.expansion_panel import ( - ExpansionPanel, - ExpansionPanelList, - ExpansionPanelListChangeEvent, -) -from flet.controls.material.expansion_tile import ExpansionTile, TileAffinity -from flet.controls.material.filled_button import FilledButton -from flet.controls.material.filled_tonal_button import FilledTonalButton -from flet.controls.material.floating_action_button import FloatingActionButton -from flet.controls.material.form_field_control import FormFieldControl, InputBorder -from flet.controls.material.icon_button import ( - FilledIconButton, - FilledTonalIconButton, - IconButton, - OutlinedIconButton, -) -from flet.controls.material.list_tile import ( - ListTile, - ListTileStyle, - ListTileTitleAlignment, -) -from flet.controls.material.menu_bar import MenuBar, MenuStyle -from flet.controls.material.menu_item_button import MenuItemButton -from flet.controls.material.navigation_bar import ( - NavigationBar, - NavigationBarDestination, - NavigationBarLabelBehavior, -) -from flet.controls.material.navigation_drawer import ( - NavigationDrawer, - NavigationDrawerDestination, -) -from flet.controls.material.navigation_rail import ( - NavigationRail, - NavigationRailDestination, - NavigationRailLabelType, -) -from flet.controls.material.outlined_button import OutlinedButton -from flet.controls.material.popup_menu_button import ( - PopupMenuButton, - PopupMenuItem, - PopupMenuPosition, -) -from flet.controls.material.progress_bar import ProgressBar -from flet.controls.material.progress_ring import ProgressRing -from flet.controls.material.radio import Radio -from flet.controls.material.radio_group import RadioGroup -from flet.controls.material.range_slider import RangeSlider -from flet.controls.material.reorderable_list_view import ( - OnReorderEvent, - ReorderableListView, -) -from flet.controls.material.search_bar import SearchBar -from flet.controls.material.segmented_button import Segment, SegmentedButton -from flet.controls.material.selection_area import SelectionArea -from flet.controls.material.slider import Slider, SliderInteraction -from flet.controls.material.snack_bar import ( - DismissDirection, - SnackBar, - SnackBarAction, - SnackBarBehavior, -) -from flet.controls.material.submenu_button import SubmenuButton -from flet.controls.material.switch import Switch -from flet.controls.material.tabs import ( - Tab, - TabAlignment, - TabBar, - TabBarHoverEvent, - TabBarIndicatorSize, - TabBarView, - TabIndicatorAnimation, - Tabs, - UnderlineTabIndicator, -) -from flet.controls.material.text_button import TextButton -from flet.controls.material.textfield import ( - InputFilter, - KeyboardType, - NumbersOnlyInputFilter, - TextCapitalization, - TextField, - TextOnlyInputFilter, -) -from flet.controls.material.time_picker import ( - TimePicker, - TimePickerEntryMode, - TimePickerEntryModeChangeEvent, - TimePickerHourFormat, -) -from flet.controls.material.tooltip import Tooltip, TooltipTriggerMode, TooltipValue -from flet.controls.material.vertical_divider import VerticalDivider -from flet.controls.multi_view import MultiView -from flet.controls.padding import Padding, PaddingValue -from flet.controls.page import ( - AppLifecycleStateChangeEvent, - KeyboardEvent, - LocaleChangeEvent, - LoginEvent, - MultiViewAddEvent, - MultiViewRemoveEvent, - Page, - PlatformBrightnessChangeEvent, - RouteChangeEvent, - ViewPopEvent, - ViewsPopUntilEvent, -) -from flet.controls.painting import ( - Paint, - PaintGradient, - PaintingStyle, - PaintLinearGradient, - PaintRadialGradient, - PaintSweepGradient, -) -from flet.controls.query_string import QueryString -from flet.controls.ref import Ref -from flet.controls.scrollable_control import ( - OnScrollEvent, - ScrollableControl, - Scrollbar, - ScrollbarOrientation, - ScrollDirection, - ScrollType, -) -from flet.controls.services.accelerometer import ( - Accelerometer, - AccelerometerReadingEvent, -) -from flet.controls.services.barometer import Barometer, BarometerReadingEvent -from flet.controls.services.battery import ( - Battery, - BatteryState, - BatteryStateChangeEvent, -) -from flet.controls.services.browser_context_menu import BrowserContextMenu -from flet.controls.services.clipboard import Clipboard -from flet.controls.services.connectivity import ( - Connectivity, - ConnectivityChangeEvent, - ConnectivityType, -) -from flet.controls.services.file_picker import ( - FilePicker, - FilePickerFile, - FilePickerFileType, - FilePickerUploadEvent, - FilePickerUploadFile, -) -from flet.controls.services.gyroscope import Gyroscope, GyroscopeReadingEvent -from flet.controls.services.haptic_feedback import HapticFeedback -from flet.controls.services.magnetometer import Magnetometer, MagnetometerReadingEvent -from flet.controls.services.screen_brightness import ( - ScreenBrightness, - ScreenBrightnessChangeEvent, -) -from flet.controls.services.semantics_service import Assertiveness, SemanticsService -from flet.controls.services.sensor_error_event import SensorErrorEvent -from flet.controls.services.service import Service -from flet.controls.services.shake_detector import ShakeDetector -from flet.controls.services.share import ( - Share, - ShareCupertinoActivityType, - ShareFile, - ShareResult, - ShareResultStatus, -) -from flet.controls.services.shared_preferences import SharedPreferences -from flet.controls.services.storage_paths import StoragePaths -from flet.controls.services.url_launcher import ( - BrowserConfiguration, - LaunchMode, - UrlLauncher, - WebViewConfiguration, -) -from flet.controls.services.user_accelerometer import ( - UserAccelerometer, - UserAccelerometerReadingEvent, -) -from flet.controls.services.wakelock import Wakelock -from flet.controls.template_route import TemplateRoute -from flet.controls.text_style import ( - StrutStyle, - TextBaseline, - TextDecoration, - TextDecorationStyle, - TextOverflow, - TextStyle, - TextThemeStyle, -) -from flet.controls.transform import ( - Flip, - Matrix4, - Offset, - OffsetValue, - Rotate, - RotateValue, - Scale, - ScaleValue, - Transform, -) -from flet.controls.types import ( - AppLifecycleState, - AppView, - AutomaticNotchShape, - BlendMode, - Brightness, - CircularRectangleNotchShape, - ClipBehavior, - ColorValue, - CrossAxisAlignment, - DeviceOrientation, - FloatingActionButtonLocation, - FontWeight, - IconDataOrControl, - ImageRepeat, - LabelPosition, - Locale, - LocaleConfiguration, - MainAxisAlignment, - MouseCursor, - NotchShape, - Number, - Orientation, - PagePlatform, - PointerDeviceType, - ResponsiveNumber, - ResponsiveRowBreakpoint, - RouteUrlStrategy, - ScrollMode, - StrokeCap, - StrokeJoin, - StrOrControl, - SupportsStr, - TextAlign, - ThemeMode, - Url, - UrlTarget, - VerticalAlignment, - VisualDensity, - WebRenderer, -) -from flet.data_channel import DataChannel, DataChannelOpenEvent -from flet.pubsub.pubsub_client import PubSubClient -from flet.pubsub.pubsub_hub import PubSubHub +# `__version__` is cheap and frequently accessed — keep it eager. from flet.version import flet_version as __version__ +# The public API is imported lazily (PEP 562). `import flet` no longer executes +# the full ~270-module package; each name is imported on first access and cached +# into module globals. The block below exists only for type checkers / IDEs. if TYPE_CHECKING: - from flet.controls.cupertino import cupertino_icons + from flet.app import ( + AppCallable, + app, + app_async, + run, + run_async, + ) + from flet.components.component import Component + from flet.components.component_decorator import component + from flet.components.hooks.use_callback import use_callback + from flet.components.hooks.use_context import ( + ContextProvider, + create_context, + use_context, + ) + from flet.components.hooks.use_dialog import use_dialog + from flet.components.hooks.use_effect import ( + on_mounted, + on_unmounted, + on_updated, + use_effect, + ) + from flet.components.hooks.use_memo import use_memo + from flet.components.hooks.use_ref import use_ref + from flet.components.hooks.use_state import use_state + from flet.components.memo import memo + from flet.components.observable import ( + Observable, + observable, + ) + from flet.components.public_utils import unwrap_component + from flet.components.router import ( + LocationInfo, + Route, + Router, + is_route_active, + use_route_loader_data, + use_route_location, + use_route_outlet, + use_route_params, + use_view_path, + ) + from flet.controls import ( + alignment, + border, + border_radius, + margin, + padding, + ) + from flet.controls.adaptive_control import AdaptiveControl + from flet.controls.alignment import ( + Alignment, + Axis, + ) + from flet.controls.animation import ( + Animation, + AnimationCurve, + AnimationStyle, + AnimationValue, + ) + from flet.controls.base_control import ( + BaseControl, + Value, + control, + value, + ) + from flet.controls.base_page import ( + BasePage, + PageMediaData, + PageResizeEvent, + ) + from flet.controls.blur import ( + Blur, + BlurTileMode, + BlurValue, + ) + from flet.controls.border import ( + Border, + BorderSide, + BorderSideStrokeAlign, + BorderSideStrokeAlignValue, + BorderStyle, + ) + from flet.controls.border_radius import ( + BorderRadius, + BorderRadiusValue, + ) + from flet.controls.box import ( + BlurStyle, + BoxConstraints, + BoxDecoration, + BoxFit, + BoxShadow, + BoxShadowValue, + BoxShape, + ColorFilter, + DecorationImage, + FilterQuality, + ) + from flet.controls.buttons import ( + BeveledRectangleBorder, + ButtonStyle, + CircleBorder, + ContinuousRectangleBorder, + OutlinedBorder, + RoundedRectangleBorder, + ShapeBorder, + StadiumBorder, + ) + from flet.controls.colors import Colors + from flet.controls.context import ( + Context, + context, + ) + from flet.controls.control import Control + from flet.controls.control_event import ( + ControlEvent, + ControlEventHandler, + Event, + EventControlType, + EventHandler, + ) + from flet.controls.control_state import ( + ControlState, + ControlStateValue, + ) + from flet.controls.core.animated_switcher import ( + AnimatedSwitcher, + AnimatedSwitcherTransition, + ) + from flet.controls.core.autofill_group import ( + AutofillGroup, + AutofillGroupDisposeAction, + AutofillHint, + ) + from flet.controls.core.column import Column + from flet.controls.core.dismissible import ( + Dismissible, + DismissibleDismissEvent, + DismissibleUpdateEvent, + ) + from flet.controls.core.drag_target import ( + DragTarget, + DragTargetEvent, + DragTargetLeaveEvent, + DragWillAcceptEvent, + ) + from flet.controls.core.draggable import Draggable + from flet.controls.core.flet_app import ( + FletApp, + FletAppOutputEvent, + ) + from flet.controls.core.gesture_detector import GestureDetector + from flet.controls.core.grid_view import GridView + from flet.controls.core.hero import Hero + from flet.controls.core.icon import Icon + from flet.controls.core.image import Image + from flet.controls.core.interactive_viewer import InteractiveViewer + from flet.controls.core.keyboard_listener import ( + KeyboardListener, + KeyDownEvent, + KeyRepeatEvent, + KeyUpEvent, + ) + from flet.controls.core.list_view import ListView + from flet.controls.core.markdown import ( + Markdown, + MarkdownCodeTheme, + MarkdownCustomCodeTheme, + MarkdownExtensionSet, + MarkdownStyleSheet, + ) + from flet.controls.core.merge_semantics import MergeSemantics + from flet.controls.core.page_view import PageView + from flet.controls.core.pagelet import Pagelet + from flet.controls.core.placeholder import Placeholder + from flet.controls.core.reorderable_drag_handle import ReorderableDragHandle + from flet.controls.core.responsive_row import ResponsiveRow + from flet.controls.core.rotated_box import RotatedBox + from flet.controls.core.row import Row + from flet.controls.core.safe_area import SafeArea + from flet.controls.core.screenshot import Screenshot + from flet.controls.core.semantics import Semantics + from flet.controls.core.shader_mask import ShaderMask + from flet.controls.core.shimmer import ( + Shimmer, + ShimmerDirection, + ) + from flet.controls.core.stack import ( + Stack, + StackFit, + ) + from flet.controls.core.text import ( + Text, + TextAffinity, + TextSelection, + TextSelectionChangeCause, + TextSelectionChangeEvent, + ) + from flet.controls.core.text_span import TextSpan + from flet.controls.core.transparent_pointer import TransparentPointer + from flet.controls.core.view import View + from flet.controls.core.window import ( + Window, + WindowEvent, + WindowEventType, + WindowResizeEdge, + ) + from flet.controls.core.window_drag_area import WindowDragArea + from flet.controls.cupertino import ( + cupertino_colors, + cupertino_icons, + ) + from flet.controls.cupertino.cupertino_action_sheet import CupertinoActionSheet + from flet.controls.cupertino.cupertino_action_sheet_action import ( + CupertinoActionSheetAction, + ) + from flet.controls.cupertino.cupertino_activity_indicator import ( + CupertinoActivityIndicator, + ) + from flet.controls.cupertino.cupertino_alert_dialog import CupertinoAlertDialog + from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar + from flet.controls.cupertino.cupertino_bottom_sheet import CupertinoBottomSheet + from flet.controls.cupertino.cupertino_button import ( + CupertinoButton, + CupertinoButtonSize, + ) + from flet.controls.cupertino.cupertino_checkbox import CupertinoCheckbox + from flet.controls.cupertino.cupertino_colors import CupertinoColors + from flet.controls.cupertino.cupertino_context_menu import CupertinoContextMenu + from flet.controls.cupertino.cupertino_context_menu_action import ( + CupertinoContextMenuAction, + ) + from flet.controls.cupertino.cupertino_date_picker import ( + CupertinoDatePicker, + CupertinoDatePickerDateOrder, + CupertinoDatePickerMode, + ) + from flet.controls.cupertino.cupertino_dialog_action import CupertinoDialogAction + from flet.controls.cupertino.cupertino_filled_button import CupertinoFilledButton from flet.controls.cupertino.cupertino_icons import CupertinoIcons - from flet.controls.material import icons + from flet.controls.cupertino.cupertino_list_tile import CupertinoListTile + from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar + from flet.controls.cupertino.cupertino_picker import CupertinoPicker + from flet.controls.cupertino.cupertino_radio import CupertinoRadio + from flet.controls.cupertino.cupertino_segmented_button import ( + CupertinoSegmentedButton, + ) + from flet.controls.cupertino.cupertino_slider import CupertinoSlider + from flet.controls.cupertino.cupertino_sliding_segmented_button import ( + CupertinoSlidingSegmentedButton, + ) + from flet.controls.cupertino.cupertino_switch import CupertinoSwitch + from flet.controls.cupertino.cupertino_textfield import ( + CupertinoTextField, + OverlayVisibilityMode, + ) + from flet.controls.cupertino.cupertino_timer_picker import ( + CupertinoTimerPicker, + CupertinoTimerPickerMode, + ) + from flet.controls.cupertino.cupertino_tinted_button import CupertinoTintedButton + from flet.controls.device_info import ( + AndroidBuildVersion, + AndroidDeviceInfo, + DeviceInfo, + IosDeviceInfo, + IosUtsname, + LinuxDeviceInfo, + MacOsDeviceInfo, + WebBrowserName, + WebDeviceInfo, + WindowsDeviceInfo, + ) + from flet.controls.dialog_control import DialogControl + from flet.controls.duration import ( + DateTimeValue, + Duration, + DurationValue, + ) + from flet.controls.events import ( + DragDownEvent, + DragEndEvent, + DragStartEvent, + DragUpdateEvent, + ForcePressEvent, + HoverEvent, + LongPressDownEvent, + LongPressEndEvent, + LongPressMoveUpdateEvent, + LongPressStartEvent, + MultiTapEvent, + PointerEvent, + ScaleEndEvent, + ScaleStartEvent, + ScaleUpdateEvent, + ScrollEvent, + TapEvent, + TapMoveEvent, + ) + from flet.controls.exceptions import ( + FletException, + FletPageDisconnectedException, + FletUnimplementedPlatformException, + FletUnsupportedPlatformException, + ) + from flet.controls.geometry import ( + Rect, + Size, + ) + from flet.controls.gradients import ( + Gradient, + GradientTileMode, + LinearGradient, + RadialGradient, + SweepGradient, + ) + from flet.controls.icon_data import IconData + from flet.controls.id_counter import IdCounter + from flet.controls.keys import ( + Key, + KeyValue, + ScrollKey, + ValueKey, + ) + from flet.controls.layout_control import ( + ConstrainedControl, + LayoutControl, + LayoutSizeChangeEvent, + ) + from flet.controls.margin import ( + Margin, + MarginValue, + ) + from flet.controls.material import ( + dropdown, + dropdownm2, + icons, + ) + from flet.controls.material.alert_dialog import AlertDialog + from flet.controls.material.app_bar import AppBar + from flet.controls.material.auto_complete import ( + AutoComplete, + AutoCompleteSelectEvent, + AutoCompleteSuggestion, + ) + from flet.controls.material.badge import ( + Badge, + BadgeValue, + ) + from flet.controls.material.banner import Banner + from flet.controls.material.bottom_app_bar import BottomAppBar + from flet.controls.material.bottom_sheet import BottomSheet + from flet.controls.material.button import Button + from flet.controls.material.card import ( + Card, + CardVariant, + ) + from flet.controls.material.checkbox import Checkbox + from flet.controls.material.chip import Chip + from flet.controls.material.circle_avatar import CircleAvatar + from flet.controls.material.container import Container + from flet.controls.material.context_menu import ( + ContextMenu, + ContextMenuDismissEvent, + ContextMenuSelectEvent, + ContextMenuTrigger, + ) + from flet.controls.material.datatable import ( + DataCell, + DataColumn, + DataColumnSortEvent, + DataRow, + DataTable, + ) + from flet.controls.material.date_picker import ( + DatePicker, + DatePickerEntryMode, + DatePickerEntryModeChangeEvent, + DatePickerMode, + ) + from flet.controls.material.date_range_picker import DateRangePicker + from flet.controls.material.divider import Divider + from flet.controls.material.dropdown import ( + Dropdown, + DropdownOption, + ) + from flet.controls.material.dropdownm2 import DropdownM2 + from flet.controls.material.elevated_button import ElevatedButton + from flet.controls.material.expansion_panel import ( + ExpansionPanel, + ExpansionPanelList, + ExpansionPanelListChangeEvent, + ) + from flet.controls.material.expansion_tile import ( + ExpansionTile, + TileAffinity, + ) + from flet.controls.material.filled_button import FilledButton + from flet.controls.material.filled_tonal_button import FilledTonalButton + from flet.controls.material.floating_action_button import FloatingActionButton + from flet.controls.material.form_field_control import ( + FormFieldControl, + InputBorder, + ) + from flet.controls.material.icon_button import ( + FilledIconButton, + FilledTonalIconButton, + IconButton, + OutlinedIconButton, + ) from flet.controls.material.icons import Icons + from flet.controls.material.list_tile import ( + ListTile, + ListTileStyle, + ListTileTitleAlignment, + ) + from flet.controls.material.menu_bar import ( + MenuBar, + MenuStyle, + ) + from flet.controls.material.menu_item_button import MenuItemButton + from flet.controls.material.navigation_bar import ( + NavigationBar, + NavigationBarDestination, + NavigationBarLabelBehavior, + ) + from flet.controls.material.navigation_drawer import ( + NavigationDrawer, + NavigationDrawerDestination, + ) + from flet.controls.material.navigation_rail import ( + NavigationRail, + NavigationRailDestination, + NavigationRailLabelType, + ) + from flet.controls.material.outlined_button import OutlinedButton + from flet.controls.material.popup_menu_button import ( + PopupMenuButton, + PopupMenuItem, + PopupMenuPosition, + ) + from flet.controls.material.progress_bar import ProgressBar + from flet.controls.material.progress_ring import ProgressRing + from flet.controls.material.radio import Radio + from flet.controls.material.radio_group import RadioGroup + from flet.controls.material.range_slider import RangeSlider + from flet.controls.material.reorderable_list_view import ( + OnReorderEvent, + ReorderableListView, + ) + from flet.controls.material.search_bar import SearchBar + from flet.controls.material.segmented_button import ( + Segment, + SegmentedButton, + ) + from flet.controls.material.selection_area import SelectionArea + from flet.controls.material.slider import ( + Slider, + SliderInteraction, + ) + from flet.controls.material.snack_bar import ( + DismissDirection, + SnackBar, + SnackBarAction, + SnackBarBehavior, + ) + from flet.controls.material.submenu_button import SubmenuButton + from flet.controls.material.switch import Switch + from flet.controls.material.tabs import ( + Tab, + TabAlignment, + TabBar, + TabBarHoverEvent, + TabBarIndicatorSize, + TabBarView, + TabIndicatorAnimation, + Tabs, + UnderlineTabIndicator, + ) + from flet.controls.material.text_button import TextButton + from flet.controls.material.textfield import ( + InputFilter, + KeyboardType, + NumbersOnlyInputFilter, + TextCapitalization, + TextField, + TextOnlyInputFilter, + ) + from flet.controls.material.time_picker import ( + TimePicker, + TimePickerEntryMode, + TimePickerEntryModeChangeEvent, + TimePickerHourFormat, + ) + from flet.controls.material.tooltip import ( + Tooltip, + TooltipTriggerMode, + TooltipValue, + ) + from flet.controls.material.vertical_divider import VerticalDivider + from flet.controls.multi_view import MultiView + from flet.controls.padding import ( + Padding, + PaddingValue, + ) + from flet.controls.page import ( + AppLifecycleStateChangeEvent, + KeyboardEvent, + LocaleChangeEvent, + LoginEvent, + MultiViewAddEvent, + MultiViewRemoveEvent, + Page, + PlatformBrightnessChangeEvent, + RouteChangeEvent, + ViewPopEvent, + ViewsPopUntilEvent, + ) + from flet.controls.painting import ( + Paint, + PaintGradient, + PaintingStyle, + PaintLinearGradient, + PaintRadialGradient, + PaintSweepGradient, + ) + from flet.controls.query_string import QueryString + from flet.controls.ref import Ref + from flet.controls.scrollable_control import ( + OnScrollEvent, + ScrollableControl, + Scrollbar, + ScrollbarOrientation, + ScrollDirection, + ScrollType, + ) + from flet.controls.services.accelerometer import ( + Accelerometer, + AccelerometerReadingEvent, + ) + from flet.controls.services.barometer import ( + Barometer, + BarometerReadingEvent, + ) + from flet.controls.services.battery import ( + Battery, + BatteryState, + BatteryStateChangeEvent, + ) + from flet.controls.services.browser_context_menu import BrowserContextMenu + from flet.controls.services.clipboard import Clipboard + from flet.controls.services.connectivity import ( + Connectivity, + ConnectivityChangeEvent, + ConnectivityType, + ) + from flet.controls.services.file_picker import ( + FilePicker, + FilePickerFile, + FilePickerFileType, + FilePickerUploadEvent, + FilePickerUploadFile, + ) + from flet.controls.services.gyroscope import ( + Gyroscope, + GyroscopeReadingEvent, + ) + from flet.controls.services.haptic_feedback import HapticFeedback + from flet.controls.services.magnetometer import ( + Magnetometer, + MagnetometerReadingEvent, + ) + from flet.controls.services.screen_brightness import ( + ScreenBrightness, + ScreenBrightnessChangeEvent, + ) + from flet.controls.services.semantics_service import ( + Assertiveness, + SemanticsService, + ) + from flet.controls.services.sensor_error_event import SensorErrorEvent + from flet.controls.services.service import Service + from flet.controls.services.shake_detector import ShakeDetector + from flet.controls.services.share import ( + Share, + ShareCupertinoActivityType, + ShareFile, + ShareResult, + ShareResultStatus, + ) + from flet.controls.services.shared_preferences import SharedPreferences + from flet.controls.services.storage_paths import StoragePaths + from flet.controls.services.url_launcher import ( + BrowserConfiguration, + LaunchMode, + UrlLauncher, + WebViewConfiguration, + ) + from flet.controls.services.user_accelerometer import ( + UserAccelerometer, + UserAccelerometerReadingEvent, + ) + from flet.controls.services.wakelock import Wakelock + from flet.controls.template_route import TemplateRoute + from flet.controls.text_style import ( + StrutStyle, + TextBaseline, + TextDecoration, + TextDecorationStyle, + TextOverflow, + TextStyle, + TextThemeStyle, + ) from flet.controls.theme import ( AppBarTheme, BadgeTheme, @@ -618,6 +667,65 @@ TimePickerTheme, TooltipTheme, ) + from flet.controls.transform import ( + Flip, + Matrix4, + Offset, + OffsetValue, + Rotate, + RotateValue, + Scale, + ScaleValue, + Transform, + ) + from flet.controls.types import ( + AppLifecycleState, + AppView, + AutomaticNotchShape, + BlendMode, + Brightness, + CircularRectangleNotchShape, + ClipBehavior, + ColorValue, + CrossAxisAlignment, + DeviceOrientation, + FloatingActionButtonLocation, + FontWeight, + IconDataOrControl, + ImageRepeat, + LabelPosition, + Locale, + LocaleConfiguration, + MainAxisAlignment, + MouseCursor, + NotchShape, + Number, + Orientation, + PagePlatform, + PointerDeviceType, + ResponsiveNumber, + ResponsiveRowBreakpoint, + RouteUrlStrategy, + ScrollMode, + StrokeCap, + StrokeJoin, + StrOrControl, + SupportsStr, + TextAlign, + ThemeMode, + Url, + UrlTarget, + VerticalAlignment, + VisualDensity, + WebRenderer, + ) + from flet.data_channel import ( + DataChannel, + DataChannelOpenEvent, + ) + from flet.pubsub.pubsub_client import PubSubClient + from flet.pubsub.pubsub_hub import PubSubHub + __all__ = [ "Accelerometer", @@ -1151,73 +1259,554 @@ "value", ] -_THEME_EXPORTS = { - "AppBarTheme", - "BadgeTheme", - "BannerTheme", - "BottomAppBarTheme", - "BottomSheetTheme", - "ButtonTheme", - "CardTheme", - "CheckboxTheme", - "ChipTheme", - "ColorScheme", - "DataTableTheme", - "DatePickerTheme", - "DialogTheme", - "DividerTheme", - "DropdownTheme", - "ExpansionTileTheme", - "FilledButtonTheme", - "FloatingActionButtonTheme", - "IconButtonTheme", - "IconTheme", - "ListTileTheme", - "NavigationBarTheme", - "NavigationDrawerTheme", - "NavigationRailTheme", - "OutlinedButtonTheme", - "PageTransitionsTheme", - "PageTransitionTheme", - "PopupMenuTheme", - "ProgressIndicatorTheme", - "RadioTheme", - "ScrollbarTheme", - "SearchBarTheme", - "SearchViewTheme", - "SegmentedButtonTheme", - "SliderTheme", - "SnackBarTheme", - "SwitchTheme", - "SystemOverlayStyle", - "TabBarTheme", - "TextButtonTheme", - "TextTheme", - "Theme", - "TimePickerTheme", - "TooltipTheme", +# Generated: exported name -> module that defines it. +_LAZY = { + "Accelerometer": "flet.controls.services.accelerometer", + "AccelerometerReadingEvent": "flet.controls.services.accelerometer", + "AdaptiveControl": "flet.controls.adaptive_control", + "AlertDialog": "flet.controls.material.alert_dialog", + "Alignment": "flet.controls.alignment", + "AndroidBuildVersion": "flet.controls.device_info", + "AndroidDeviceInfo": "flet.controls.device_info", + "AnimatedSwitcher": "flet.controls.core.animated_switcher", + "AnimatedSwitcherTransition": "flet.controls.core.animated_switcher", + "Animation": "flet.controls.animation", + "AnimationCurve": "flet.controls.animation", + "AnimationStyle": "flet.controls.animation", + "AnimationValue": "flet.controls.animation", + "AppBar": "flet.controls.material.app_bar", + "AppBarTheme": "flet.controls.theme", + "AppCallable": "flet.app", + "AppLifecycleState": "flet.controls.types", + "AppLifecycleStateChangeEvent": "flet.controls.page", + "AppView": "flet.controls.types", + "Assertiveness": "flet.controls.services.semantics_service", + "AutoComplete": "flet.controls.material.auto_complete", + "AutoCompleteSelectEvent": "flet.controls.material.auto_complete", + "AutoCompleteSuggestion": "flet.controls.material.auto_complete", + "AutofillGroup": "flet.controls.core.autofill_group", + "AutofillGroupDisposeAction": "flet.controls.core.autofill_group", + "AutofillHint": "flet.controls.core.autofill_group", + "AutomaticNotchShape": "flet.controls.types", + "Axis": "flet.controls.alignment", + "Badge": "flet.controls.material.badge", + "BadgeTheme": "flet.controls.theme", + "BadgeValue": "flet.controls.material.badge", + "Banner": "flet.controls.material.banner", + "BannerTheme": "flet.controls.theme", + "Barometer": "flet.controls.services.barometer", + "BarometerReadingEvent": "flet.controls.services.barometer", + "BaseControl": "flet.controls.base_control", + "BasePage": "flet.controls.base_page", + "Battery": "flet.controls.services.battery", + "BatteryState": "flet.controls.services.battery", + "BatteryStateChangeEvent": "flet.controls.services.battery", + "BeveledRectangleBorder": "flet.controls.buttons", + "BlendMode": "flet.controls.types", + "Blur": "flet.controls.blur", + "BlurStyle": "flet.controls.box", + "BlurTileMode": "flet.controls.blur", + "BlurValue": "flet.controls.blur", + "Border": "flet.controls.border", + "BorderRadius": "flet.controls.border_radius", + "BorderRadiusValue": "flet.controls.border_radius", + "BorderSide": "flet.controls.border", + "BorderSideStrokeAlign": "flet.controls.border", + "BorderSideStrokeAlignValue": "flet.controls.border", + "BorderStyle": "flet.controls.border", + "BottomAppBar": "flet.controls.material.bottom_app_bar", + "BottomAppBarTheme": "flet.controls.theme", + "BottomSheet": "flet.controls.material.bottom_sheet", + "BottomSheetTheme": "flet.controls.theme", + "BoxConstraints": "flet.controls.box", + "BoxDecoration": "flet.controls.box", + "BoxFit": "flet.controls.box", + "BoxShadow": "flet.controls.box", + "BoxShadowValue": "flet.controls.box", + "BoxShape": "flet.controls.box", + "Brightness": "flet.controls.types", + "BrowserConfiguration": "flet.controls.services.url_launcher", + "BrowserContextMenu": "flet.controls.services.browser_context_menu", + "Button": "flet.controls.material.button", + "ButtonStyle": "flet.controls.buttons", + "ButtonTheme": "flet.controls.theme", + "Card": "flet.controls.material.card", + "CardTheme": "flet.controls.theme", + "CardVariant": "flet.controls.material.card", + "Checkbox": "flet.controls.material.checkbox", + "CheckboxTheme": "flet.controls.theme", + "Chip": "flet.controls.material.chip", + "ChipTheme": "flet.controls.theme", + "CircleAvatar": "flet.controls.material.circle_avatar", + "CircleBorder": "flet.controls.buttons", + "CircularRectangleNotchShape": "flet.controls.types", + "ClipBehavior": "flet.controls.types", + "Clipboard": "flet.controls.services.clipboard", + "ColorFilter": "flet.controls.box", + "ColorScheme": "flet.controls.theme", + "ColorValue": "flet.controls.types", + "Colors": "flet.controls.colors", + "Column": "flet.controls.core.column", + "Component": "flet.components.component", + "Connectivity": "flet.controls.services.connectivity", + "ConnectivityChangeEvent": "flet.controls.services.connectivity", + "ConnectivityType": "flet.controls.services.connectivity", + "ConstrainedControl": "flet.controls.layout_control", + "Container": "flet.controls.material.container", + "Context": "flet.controls.context", + "ContextMenu": "flet.controls.material.context_menu", + "ContextMenuDismissEvent": "flet.controls.material.context_menu", + "ContextMenuSelectEvent": "flet.controls.material.context_menu", + "ContextMenuTrigger": "flet.controls.material.context_menu", + "ContextProvider": "flet.components.hooks.use_context", + "ContinuousRectangleBorder": "flet.controls.buttons", + "Control": "flet.controls.control", + "ControlEvent": "flet.controls.control_event", + "ControlEventHandler": "flet.controls.control_event", + "ControlState": "flet.controls.control_state", + "ControlStateValue": "flet.controls.control_state", + "CrossAxisAlignment": "flet.controls.types", + "CupertinoActionSheet": "flet.controls.cupertino.cupertino_action_sheet", + "CupertinoActionSheetAction": "flet.controls.cupertino.cupertino_action_sheet_action", # noqa: E501 + "CupertinoActivityIndicator": "flet.controls.cupertino.cupertino_activity_indicator", # noqa: E501 + "CupertinoAlertDialog": "flet.controls.cupertino.cupertino_alert_dialog", + "CupertinoAppBar": "flet.controls.cupertino.cupertino_app_bar", + "CupertinoBottomSheet": "flet.controls.cupertino.cupertino_bottom_sheet", + "CupertinoButton": "flet.controls.cupertino.cupertino_button", + "CupertinoButtonSize": "flet.controls.cupertino.cupertino_button", + "CupertinoCheckbox": "flet.controls.cupertino.cupertino_checkbox", + "CupertinoColors": "flet.controls.cupertino.cupertino_colors", + "CupertinoContextMenu": "flet.controls.cupertino.cupertino_context_menu", + "CupertinoContextMenuAction": "flet.controls.cupertino.cupertino_context_menu_action", # noqa: E501 + "CupertinoDatePicker": "flet.controls.cupertino.cupertino_date_picker", + "CupertinoDatePickerDateOrder": "flet.controls.cupertino.cupertino_date_picker", + "CupertinoDatePickerMode": "flet.controls.cupertino.cupertino_date_picker", + "CupertinoDialogAction": "flet.controls.cupertino.cupertino_dialog_action", + "CupertinoFilledButton": "flet.controls.cupertino.cupertino_filled_button", + "CupertinoIcons": "flet.controls.cupertino.cupertino_icons", + "CupertinoListTile": "flet.controls.cupertino.cupertino_list_tile", + "CupertinoNavigationBar": "flet.controls.cupertino.cupertino_navigation_bar", + "CupertinoPicker": "flet.controls.cupertino.cupertino_picker", + "CupertinoRadio": "flet.controls.cupertino.cupertino_radio", + "CupertinoSegmentedButton": "flet.controls.cupertino.cupertino_segmented_button", + "CupertinoSlider": "flet.controls.cupertino.cupertino_slider", + "CupertinoSlidingSegmentedButton": "flet.controls.cupertino.cupertino_sliding_segmented_button", # noqa: E501 + "CupertinoSwitch": "flet.controls.cupertino.cupertino_switch", + "CupertinoTextField": "flet.controls.cupertino.cupertino_textfield", + "CupertinoTimerPicker": "flet.controls.cupertino.cupertino_timer_picker", + "CupertinoTimerPickerMode": "flet.controls.cupertino.cupertino_timer_picker", + "CupertinoTintedButton": "flet.controls.cupertino.cupertino_tinted_button", + "DataCell": "flet.controls.material.datatable", + "DataChannel": "flet.data_channel", + "DataChannelOpenEvent": "flet.data_channel", + "DataColumn": "flet.controls.material.datatable", + "DataColumnSortEvent": "flet.controls.material.datatable", + "DataRow": "flet.controls.material.datatable", + "DataTable": "flet.controls.material.datatable", + "DataTableTheme": "flet.controls.theme", + "DatePicker": "flet.controls.material.date_picker", + "DatePickerEntryMode": "flet.controls.material.date_picker", + "DatePickerEntryModeChangeEvent": "flet.controls.material.date_picker", + "DatePickerMode": "flet.controls.material.date_picker", + "DatePickerTheme": "flet.controls.theme", + "DateRangePicker": "flet.controls.material.date_range_picker", + "DateTimeValue": "flet.controls.duration", + "DecorationImage": "flet.controls.box", + "DeviceInfo": "flet.controls.device_info", + "DeviceOrientation": "flet.controls.types", + "DialogControl": "flet.controls.dialog_control", + "DialogTheme": "flet.controls.theme", + "DismissDirection": "flet.controls.material.snack_bar", + "Dismissible": "flet.controls.core.dismissible", + "DismissibleDismissEvent": "flet.controls.core.dismissible", + "DismissibleUpdateEvent": "flet.controls.core.dismissible", + "Divider": "flet.controls.material.divider", + "DividerTheme": "flet.controls.theme", + "DragDownEvent": "flet.controls.events", + "DragEndEvent": "flet.controls.events", + "DragStartEvent": "flet.controls.events", + "DragTarget": "flet.controls.core.drag_target", + "DragTargetEvent": "flet.controls.core.drag_target", + "DragTargetLeaveEvent": "flet.controls.core.drag_target", + "DragUpdateEvent": "flet.controls.events", + "DragWillAcceptEvent": "flet.controls.core.drag_target", + "Draggable": "flet.controls.core.draggable", + "Dropdown": "flet.controls.material.dropdown", + "DropdownM2": "flet.controls.material.dropdownm2", + "DropdownOption": "flet.controls.material.dropdown", + "DropdownTheme": "flet.controls.theme", + "Duration": "flet.controls.duration", + "DurationValue": "flet.controls.duration", + "ElevatedButton": "flet.controls.material.elevated_button", + "Event": "flet.controls.control_event", + "EventControlType": "flet.controls.control_event", + "EventHandler": "flet.controls.control_event", + "ExpansionPanel": "flet.controls.material.expansion_panel", + "ExpansionPanelList": "flet.controls.material.expansion_panel", + "ExpansionPanelListChangeEvent": "flet.controls.material.expansion_panel", + "ExpansionTile": "flet.controls.material.expansion_tile", + "ExpansionTileTheme": "flet.controls.theme", + "FilePicker": "flet.controls.services.file_picker", + "FilePickerFile": "flet.controls.services.file_picker", + "FilePickerFileType": "flet.controls.services.file_picker", + "FilePickerUploadEvent": "flet.controls.services.file_picker", + "FilePickerUploadFile": "flet.controls.services.file_picker", + "FilledButton": "flet.controls.material.filled_button", + "FilledButtonTheme": "flet.controls.theme", + "FilledIconButton": "flet.controls.material.icon_button", + "FilledTonalButton": "flet.controls.material.filled_tonal_button", + "FilledTonalIconButton": "flet.controls.material.icon_button", + "FilterQuality": "flet.controls.box", + "FletApp": "flet.controls.core.flet_app", + "FletAppOutputEvent": "flet.controls.core.flet_app", + "FletException": "flet.controls.exceptions", + "FletPageDisconnectedException": "flet.controls.exceptions", + "FletUnimplementedPlatformException": "flet.controls.exceptions", + "FletUnsupportedPlatformException": "flet.controls.exceptions", + "Flip": "flet.controls.transform", + "FloatingActionButton": "flet.controls.material.floating_action_button", + "FloatingActionButtonLocation": "flet.controls.types", + "FloatingActionButtonTheme": "flet.controls.theme", + "FontWeight": "flet.controls.types", + "ForcePressEvent": "flet.controls.events", + "FormFieldControl": "flet.controls.material.form_field_control", + "GestureDetector": "flet.controls.core.gesture_detector", + "Gradient": "flet.controls.gradients", + "GradientTileMode": "flet.controls.gradients", + "GridView": "flet.controls.core.grid_view", + "Gyroscope": "flet.controls.services.gyroscope", + "GyroscopeReadingEvent": "flet.controls.services.gyroscope", + "HapticFeedback": "flet.controls.services.haptic_feedback", + "Hero": "flet.controls.core.hero", + "HoverEvent": "flet.controls.events", + "Icon": "flet.controls.core.icon", + "IconButton": "flet.controls.material.icon_button", + "IconButtonTheme": "flet.controls.theme", + "IconData": "flet.controls.icon_data", + "IconDataOrControl": "flet.controls.types", + "IconTheme": "flet.controls.theme", + "Icons": "flet.controls.material.icons", + "IdCounter": "flet.controls.id_counter", + "Image": "flet.controls.core.image", + "ImageRepeat": "flet.controls.types", + "InputBorder": "flet.controls.material.form_field_control", + "InputFilter": "flet.controls.material.textfield", + "InteractiveViewer": "flet.controls.core.interactive_viewer", + "IosDeviceInfo": "flet.controls.device_info", + "IosUtsname": "flet.controls.device_info", + "Key": "flet.controls.keys", + "KeyDownEvent": "flet.controls.core.keyboard_listener", + "KeyRepeatEvent": "flet.controls.core.keyboard_listener", + "KeyUpEvent": "flet.controls.core.keyboard_listener", + "KeyValue": "flet.controls.keys", + "KeyboardEvent": "flet.controls.page", + "KeyboardListener": "flet.controls.core.keyboard_listener", + "KeyboardType": "flet.controls.material.textfield", + "LabelPosition": "flet.controls.types", + "LaunchMode": "flet.controls.services.url_launcher", + "LayoutControl": "flet.controls.layout_control", + "LayoutSizeChangeEvent": "flet.controls.layout_control", + "LinearGradient": "flet.controls.gradients", + "LinuxDeviceInfo": "flet.controls.device_info", + "ListTile": "flet.controls.material.list_tile", + "ListTileStyle": "flet.controls.material.list_tile", + "ListTileTheme": "flet.controls.theme", + "ListTileTitleAlignment": "flet.controls.material.list_tile", + "ListView": "flet.controls.core.list_view", + "Locale": "flet.controls.types", + "LocaleChangeEvent": "flet.controls.page", + "LocaleConfiguration": "flet.controls.types", + "LocationInfo": "flet.components.router", + "LoginEvent": "flet.controls.page", + "LongPressDownEvent": "flet.controls.events", + "LongPressEndEvent": "flet.controls.events", + "LongPressMoveUpdateEvent": "flet.controls.events", + "LongPressStartEvent": "flet.controls.events", + "MacOsDeviceInfo": "flet.controls.device_info", + "Magnetometer": "flet.controls.services.magnetometer", + "MagnetometerReadingEvent": "flet.controls.services.magnetometer", + "MainAxisAlignment": "flet.controls.types", + "Margin": "flet.controls.margin", + "MarginValue": "flet.controls.margin", + "Markdown": "flet.controls.core.markdown", + "MarkdownCodeTheme": "flet.controls.core.markdown", + "MarkdownCustomCodeTheme": "flet.controls.core.markdown", + "MarkdownExtensionSet": "flet.controls.core.markdown", + "MarkdownStyleSheet": "flet.controls.core.markdown", + "Matrix4": "flet.controls.transform", + "MenuBar": "flet.controls.material.menu_bar", + "MenuItemButton": "flet.controls.material.menu_item_button", + "MenuStyle": "flet.controls.material.menu_bar", + "MergeSemantics": "flet.controls.core.merge_semantics", + "MouseCursor": "flet.controls.types", + "MultiTapEvent": "flet.controls.events", + "MultiView": "flet.controls.multi_view", + "MultiViewAddEvent": "flet.controls.page", + "MultiViewRemoveEvent": "flet.controls.page", + "NavigationBar": "flet.controls.material.navigation_bar", + "NavigationBarDestination": "flet.controls.material.navigation_bar", + "NavigationBarLabelBehavior": "flet.controls.material.navigation_bar", + "NavigationBarTheme": "flet.controls.theme", + "NavigationDrawer": "flet.controls.material.navigation_drawer", + "NavigationDrawerDestination": "flet.controls.material.navigation_drawer", + "NavigationDrawerTheme": "flet.controls.theme", + "NavigationRail": "flet.controls.material.navigation_rail", + "NavigationRailDestination": "flet.controls.material.navigation_rail", + "NavigationRailLabelType": "flet.controls.material.navigation_rail", + "NavigationRailTheme": "flet.controls.theme", + "NotchShape": "flet.controls.types", + "Number": "flet.controls.types", + "NumbersOnlyInputFilter": "flet.controls.material.textfield", + "Observable": "flet.components.observable", + "Offset": "flet.controls.transform", + "OffsetValue": "flet.controls.transform", + "OnReorderEvent": "flet.controls.material.reorderable_list_view", + "OnScrollEvent": "flet.controls.scrollable_control", + "Orientation": "flet.controls.types", + "OutlinedBorder": "flet.controls.buttons", + "OutlinedButton": "flet.controls.material.outlined_button", + "OutlinedButtonTheme": "flet.controls.theme", + "OutlinedIconButton": "flet.controls.material.icon_button", + "OverlayVisibilityMode": "flet.controls.cupertino.cupertino_textfield", + "Padding": "flet.controls.padding", + "PaddingValue": "flet.controls.padding", + "Page": "flet.controls.page", + "PageMediaData": "flet.controls.base_page", + "PagePlatform": "flet.controls.types", + "PageResizeEvent": "flet.controls.base_page", + "PageTransitionTheme": "flet.controls.theme", + "PageTransitionsTheme": "flet.controls.theme", + "PageView": "flet.controls.core.page_view", + "Pagelet": "flet.controls.core.pagelet", + "Paint": "flet.controls.painting", + "PaintGradient": "flet.controls.painting", + "PaintLinearGradient": "flet.controls.painting", + "PaintRadialGradient": "flet.controls.painting", + "PaintSweepGradient": "flet.controls.painting", + "PaintingStyle": "flet.controls.painting", + "Placeholder": "flet.controls.core.placeholder", + "PlatformBrightnessChangeEvent": "flet.controls.page", + "PointerDeviceType": "flet.controls.types", + "PointerEvent": "flet.controls.events", + "PopupMenuButton": "flet.controls.material.popup_menu_button", + "PopupMenuItem": "flet.controls.material.popup_menu_button", + "PopupMenuPosition": "flet.controls.material.popup_menu_button", + "PopupMenuTheme": "flet.controls.theme", + "ProgressBar": "flet.controls.material.progress_bar", + "ProgressIndicatorTheme": "flet.controls.theme", + "ProgressRing": "flet.controls.material.progress_ring", + "PubSubClient": "flet.pubsub.pubsub_client", + "PubSubHub": "flet.pubsub.pubsub_hub", + "QueryString": "flet.controls.query_string", + "RadialGradient": "flet.controls.gradients", + "Radio": "flet.controls.material.radio", + "RadioGroup": "flet.controls.material.radio_group", + "RadioTheme": "flet.controls.theme", + "RangeSlider": "flet.controls.material.range_slider", + "Rect": "flet.controls.geometry", + "Ref": "flet.controls.ref", + "ReorderableDragHandle": "flet.controls.core.reorderable_drag_handle", + "ReorderableListView": "flet.controls.material.reorderable_list_view", + "ResponsiveNumber": "flet.controls.types", + "ResponsiveRow": "flet.controls.core.responsive_row", + "ResponsiveRowBreakpoint": "flet.controls.types", + "Rotate": "flet.controls.transform", + "RotateValue": "flet.controls.transform", + "RotatedBox": "flet.controls.core.rotated_box", + "RoundedRectangleBorder": "flet.controls.buttons", + "Route": "flet.components.router", + "RouteChangeEvent": "flet.controls.page", + "RouteUrlStrategy": "flet.controls.types", + "Router": "flet.components.router", + "Row": "flet.controls.core.row", + "SafeArea": "flet.controls.core.safe_area", + "Scale": "flet.controls.transform", + "ScaleEndEvent": "flet.controls.events", + "ScaleStartEvent": "flet.controls.events", + "ScaleUpdateEvent": "flet.controls.events", + "ScaleValue": "flet.controls.transform", + "ScreenBrightness": "flet.controls.services.screen_brightness", + "ScreenBrightnessChangeEvent": "flet.controls.services.screen_brightness", + "Screenshot": "flet.controls.core.screenshot", + "ScrollDirection": "flet.controls.scrollable_control", + "ScrollEvent": "flet.controls.events", + "ScrollKey": "flet.controls.keys", + "ScrollMode": "flet.controls.types", + "ScrollType": "flet.controls.scrollable_control", + "ScrollableControl": "flet.controls.scrollable_control", + "Scrollbar": "flet.controls.scrollable_control", + "ScrollbarOrientation": "flet.controls.scrollable_control", + "ScrollbarTheme": "flet.controls.theme", + "SearchBar": "flet.controls.material.search_bar", + "SearchBarTheme": "flet.controls.theme", + "SearchViewTheme": "flet.controls.theme", + "Segment": "flet.controls.material.segmented_button", + "SegmentedButton": "flet.controls.material.segmented_button", + "SegmentedButtonTheme": "flet.controls.theme", + "SelectionArea": "flet.controls.material.selection_area", + "Semantics": "flet.controls.core.semantics", + "SemanticsService": "flet.controls.services.semantics_service", + "SensorErrorEvent": "flet.controls.services.sensor_error_event", + "Service": "flet.controls.services.service", + "ShaderMask": "flet.controls.core.shader_mask", + "ShakeDetector": "flet.controls.services.shake_detector", + "ShapeBorder": "flet.controls.buttons", + "Share": "flet.controls.services.share", + "ShareCupertinoActivityType": "flet.controls.services.share", + "ShareFile": "flet.controls.services.share", + "ShareResult": "flet.controls.services.share", + "ShareResultStatus": "flet.controls.services.share", + "SharedPreferences": "flet.controls.services.shared_preferences", + "Shimmer": "flet.controls.core.shimmer", + "ShimmerDirection": "flet.controls.core.shimmer", + "Size": "flet.controls.geometry", + "Slider": "flet.controls.material.slider", + "SliderInteraction": "flet.controls.material.slider", + "SliderTheme": "flet.controls.theme", + "SnackBar": "flet.controls.material.snack_bar", + "SnackBarAction": "flet.controls.material.snack_bar", + "SnackBarBehavior": "flet.controls.material.snack_bar", + "SnackBarTheme": "flet.controls.theme", + "Stack": "flet.controls.core.stack", + "StackFit": "flet.controls.core.stack", + "StadiumBorder": "flet.controls.buttons", + "StoragePaths": "flet.controls.services.storage_paths", + "StrOrControl": "flet.controls.types", + "StrokeCap": "flet.controls.types", + "StrokeJoin": "flet.controls.types", + "StrutStyle": "flet.controls.text_style", + "SubmenuButton": "flet.controls.material.submenu_button", + "SupportsStr": "flet.controls.types", + "SweepGradient": "flet.controls.gradients", + "Switch": "flet.controls.material.switch", + "SwitchTheme": "flet.controls.theme", + "SystemOverlayStyle": "flet.controls.theme", + "Tab": "flet.controls.material.tabs", + "TabAlignment": "flet.controls.material.tabs", + "TabBar": "flet.controls.material.tabs", + "TabBarHoverEvent": "flet.controls.material.tabs", + "TabBarIndicatorSize": "flet.controls.material.tabs", + "TabBarTheme": "flet.controls.theme", + "TabBarView": "flet.controls.material.tabs", + "TabIndicatorAnimation": "flet.controls.material.tabs", + "Tabs": "flet.controls.material.tabs", + "TapEvent": "flet.controls.events", + "TapMoveEvent": "flet.controls.events", + "TemplateRoute": "flet.controls.template_route", + "Text": "flet.controls.core.text", + "TextAffinity": "flet.controls.core.text", + "TextAlign": "flet.controls.types", + "TextBaseline": "flet.controls.text_style", + "TextButton": "flet.controls.material.text_button", + "TextButtonTheme": "flet.controls.theme", + "TextCapitalization": "flet.controls.material.textfield", + "TextDecoration": "flet.controls.text_style", + "TextDecorationStyle": "flet.controls.text_style", + "TextField": "flet.controls.material.textfield", + "TextOnlyInputFilter": "flet.controls.material.textfield", + "TextOverflow": "flet.controls.text_style", + "TextSelection": "flet.controls.core.text", + "TextSelectionChangeCause": "flet.controls.core.text", + "TextSelectionChangeEvent": "flet.controls.core.text", + "TextSpan": "flet.controls.core.text_span", + "TextStyle": "flet.controls.text_style", + "TextTheme": "flet.controls.theme", + "TextThemeStyle": "flet.controls.text_style", + "Theme": "flet.controls.theme", + "ThemeMode": "flet.controls.types", + "TileAffinity": "flet.controls.material.expansion_tile", + "TimePicker": "flet.controls.material.time_picker", + "TimePickerEntryMode": "flet.controls.material.time_picker", + "TimePickerEntryModeChangeEvent": "flet.controls.material.time_picker", + "TimePickerHourFormat": "flet.controls.material.time_picker", + "TimePickerTheme": "flet.controls.theme", + "Tooltip": "flet.controls.material.tooltip", + "TooltipTheme": "flet.controls.theme", + "TooltipTriggerMode": "flet.controls.material.tooltip", + "TooltipValue": "flet.controls.material.tooltip", + "Transform": "flet.controls.transform", + "TransparentPointer": "flet.controls.core.transparent_pointer", + "UnderlineTabIndicator": "flet.controls.material.tabs", + "Url": "flet.controls.types", + "UrlLauncher": "flet.controls.services.url_launcher", + "UrlTarget": "flet.controls.types", + "UserAccelerometer": "flet.controls.services.user_accelerometer", + "UserAccelerometerReadingEvent": "flet.controls.services.user_accelerometer", + "Value": "flet.controls.base_control", + "ValueKey": "flet.controls.keys", + "VerticalAlignment": "flet.controls.types", + "VerticalDivider": "flet.controls.material.vertical_divider", + "View": "flet.controls.core.view", + "ViewPopEvent": "flet.controls.page", + "ViewsPopUntilEvent": "flet.controls.page", + "VisualDensity": "flet.controls.types", + "Wakelock": "flet.controls.services.wakelock", + "WebBrowserName": "flet.controls.device_info", + "WebDeviceInfo": "flet.controls.device_info", + "WebRenderer": "flet.controls.types", + "WebViewConfiguration": "flet.controls.services.url_launcher", + "Window": "flet.controls.core.window", + "WindowDragArea": "flet.controls.core.window_drag_area", + "WindowEvent": "flet.controls.core.window", + "WindowEventType": "flet.controls.core.window", + "WindowResizeEdge": "flet.controls.core.window", + "WindowsDeviceInfo": "flet.controls.device_info", + "alignment": "flet.controls", + "app": "flet.app", + "app_async": "flet.app", + "border": "flet.controls", + "border_radius": "flet.controls", + "component": "flet.components.component_decorator", + "context": "flet.controls.context", + "control": "flet.controls.base_control", + "create_context": "flet.components.hooks.use_context", + "cupertino_colors": "flet.controls.cupertino", + "cupertino_icons": "flet.controls.cupertino", + "dropdown": "flet.controls.material", + "dropdownm2": "flet.controls.material", + "icons": "flet.controls.material", + "is_route_active": "flet.components.router", + "margin": "flet.controls", + "memo": "flet.components.memo", + "observable": "flet.components.observable", + "on_mounted": "flet.components.hooks.use_effect", + "on_unmounted": "flet.components.hooks.use_effect", + "on_updated": "flet.components.hooks.use_effect", + "padding": "flet.controls", + "run": "flet.app", + "run_async": "flet.app", + "unwrap_component": "flet.components.public_utils", + "use_callback": "flet.components.hooks.use_callback", + "use_context": "flet.components.hooks.use_context", + "use_dialog": "flet.components.hooks.use_dialog", + "use_effect": "flet.components.hooks.use_effect", + "use_memo": "flet.components.hooks.use_memo", + "use_ref": "flet.components.hooks.use_ref", + "use_route_loader_data": "flet.components.router", + "use_route_location": "flet.components.router", + "use_route_outlet": "flet.components.router", + "use_route_params": "flet.components.router", + "use_state": "flet.components.hooks.use_state", + "use_view_path": "flet.components.router", + "value": "flet.controls.base_control", } def __getattr__(name: str): - if name in _THEME_EXPORTS: - from flet.controls import theme - - return getattr(theme, name) - if name == "Icons": - from flet.controls.material.icons import Icons - - return Icons - if name == "CupertinoIcons": - from flet.controls.cupertino.cupertino_icons import CupertinoIcons + module_path = _LAZY.get(name) + if module_path is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + import importlib - return CupertinoIcons - if name == "icons": - import importlib + module = importlib.import_module(module_path) + try: + value = getattr(module, name) + except AttributeError: + # `name` is itself a submodule (e.g. `flet.alignment`), not an attribute. + value = importlib.import_module(f"{module_path}.{name}") + globals()[name] = value # cache: subsequent access skips __getattr__ + return value - return importlib.import_module("flet.controls.material.icons") - if name == "cupertino_icons": - import importlib - return importlib.import_module("flet.controls.cupertino.cupertino_icons") - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +def __dir__(): + return list(__all__) diff --git a/sdk/python/packages/flet/src/flet/components/public_utils.py b/sdk/python/packages/flet/src/flet/components/public_utils.py index 842e9e7982..62c4f3e7d1 100644 --- a/sdk/python/packages/flet/src/flet/components/public_utils.py +++ b/sdk/python/packages/flet/src/flet/components/public_utils.py @@ -1,6 +1,12 @@ -from typing import Any +from typing import TYPE_CHECKING, Any, Optional -from flet.components.component import Component +if TYPE_CHECKING: + from flet.components.component import Component + +# Cached lazily on first call so merely importing this helper (done by Page / +# BasePage) doesn't pull the whole components subsystem into the cold-start +# import graph for apps that don't use components. +_Component: "Optional[type[Component]]" = None def unwrap_component(c: Any): @@ -17,7 +23,12 @@ def unwrap_component(c: Any): The first non-`Component` value in the chain (for example a control, list of controls/views, or `None`). """ + global _Component + if _Component is None: + from flet.components.component import Component + + _Component = Component p = c - while isinstance(p, Component): + while isinstance(p, _Component): p = p._b return p diff --git a/sdk/python/packages/flet/src/flet/controls/base_page.py b/sdk/python/packages/flet/src/flet/controls/base_page.py index 23a8ce98f2..1b9c3cecf2 100644 --- a/sdk/python/packages/flet/src/flet/controls/base_page.py +++ b/sdk/python/packages/flet/src/flet/controls/base_page.py @@ -18,8 +18,6 @@ EventHandler, ) from flet.controls.core.view import View -from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar -from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar from flet.controls.dialog_control import DialogControl from flet.controls.duration import DurationValue from flet.controls.keys import ScrollKey @@ -50,6 +48,12 @@ _MANAGED_DIALOG_DISMISS_WRAPPER = "_managed_dialog_dismiss_wrapper" if TYPE_CHECKING: + # Annotation-only (quoted at use sites): deferred so a Page doesn't eagerly + # pull the Cupertino controls (cold-start import cost). + from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar + from flet.controls.cupertino.cupertino_navigation_bar import ( + CupertinoNavigationBar, + ) from flet.controls.theme import Theme @@ -596,7 +600,7 @@ def controls(self, value: list[BaseControl]): # appbar @property - def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: + def appbar(self) -> "Union[AppBar, CupertinoAppBar, None]": """ Gets or sets the top application bar (:class:`~flet.AppBar` or \ :class:`~flet.CupertinoAppBar`) for the view. @@ -607,7 +611,7 @@ def appbar(self) -> Union[AppBar, CupertinoAppBar, None]: return self.__root_view().appbar @appbar.setter - def appbar(self, value: Union[AppBar, CupertinoAppBar, None]): + def appbar(self, value: "Union[AppBar, CupertinoAppBar, None]"): self.__root_view().appbar = value # bottom_appbar @@ -625,7 +629,9 @@ def bottom_appbar(self, value: Optional[BottomAppBar]): # navigation_bar @property - def navigation_bar(self) -> Optional[Union[NavigationBar, CupertinoNavigationBar]]: + def navigation_bar( + self, + ) -> "Optional[Union[NavigationBar, CupertinoNavigationBar]]": """ Bottom navigation bar for the root view. """ @@ -635,7 +641,7 @@ def navigation_bar(self) -> Optional[Union[NavigationBar, CupertinoNavigationBar @navigation_bar.setter def navigation_bar( self, - value: Optional[Union[NavigationBar, CupertinoNavigationBar]], + value: "Optional[Union[NavigationBar, CupertinoNavigationBar]]", ): self.__root_view().navigation_bar = value diff --git a/sdk/python/packages/flet/src/flet/controls/core/view.py b/sdk/python/packages/flet/src/flet/controls/core/view.py index 094e248d9b..289b643172 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/view.py +++ b/sdk/python/packages/flet/src/flet/controls/core/view.py @@ -1,12 +1,10 @@ from dataclasses import field -from typing import Optional, Union +from typing import TYPE_CHECKING, Optional, Union from flet.controls.base_control import BaseControl, control from flet.controls.box import BoxDecoration from flet.controls.control import Control from flet.controls.control_event import ControlEventHandler -from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar -from flet.controls.cupertino.cupertino_navigation_bar import CupertinoNavigationBar from flet.controls.layout_control import LayoutControl from flet.controls.material.app_bar import AppBar from flet.controls.material.bottom_app_bar import BottomAppBar @@ -25,6 +23,14 @@ Number, ) +if TYPE_CHECKING: + # Annotation-only (quoted below): deferred so a View doesn't eagerly pull the + # Cupertino controls (cold-start import cost). + from flet.controls.cupertino.cupertino_app_bar import CupertinoAppBar + from flet.controls.cupertino.cupertino_navigation_bar import ( + CupertinoNavigationBar, + ) + __all__ = ["View"] @@ -49,7 +55,7 @@ class View(ScrollableControl, LayoutControl): program to update :attr:`flet.Page.route` when a view popped. """ - appbar: Optional[Union[AppBar, CupertinoAppBar]] = None + appbar: "Optional[Union[AppBar, CupertinoAppBar]]" = None """ An :class:`~flet.AppBar` control to display at the top of the `Page`. """ @@ -71,7 +77,7 @@ class View(ScrollableControl, LayoutControl): Describes position of :attr:`floating_action_button` """ - navigation_bar: Union[NavigationBar, CupertinoNavigationBar, None] = None + navigation_bar: "Union[NavigationBar, CupertinoNavigationBar, None]" = None """ A navigation bar (:class:`~flet.NavigationBar` or \ :class:`~flet.CupertinoNavigationBar`) control to display at the bottom of the \ diff --git a/sdk/python/packages/flet/src/flet/controls/page.py b/sdk/python/packages/flet/src/flet/controls/page.py index 6a874d522a..596f2e40c4 100644 --- a/sdk/python/packages/flet/src/flet/controls/page.py +++ b/sdk/python/packages/flet/src/flet/controls/page.py @@ -18,9 +18,6 @@ ) from urllib.parse import urlparse -from flet.auth.authorization import Authorization -from flet.auth.oauth_provider import OAuthProvider -from flet.components.component import Renderer from flet.components.public_utils import unwrap_component from flet.controls.base_control import BaseControl, control from flet.controls.base_page import BasePage @@ -47,11 +44,7 @@ from flet.controls.multi_view import MultiView from flet.controls.query_string import QueryString from flet.controls.ref import Ref -from flet.controls.services.browser_context_menu import BrowserContextMenu -from flet.controls.services.clipboard import Clipboard from flet.controls.services.service import Service -from flet.controls.services.shared_preferences import SharedPreferences -from flet.controls.services.storage_paths import StoragePaths from flet.controls.services.url_launcher import UrlLauncher from flet.controls.types import ( AppLifecycleState, @@ -68,17 +61,28 @@ from flet.utils.from_dict import from_dict from flet.utils.strings import random_string -if not is_pyodide(): - from flet.auth.authorization_service import AuthorizationService - - AuthorizationImpl = AuthorizationService -else: - AuthorizationImpl = Authorization - if TYPE_CHECKING: + from flet.auth.authorization import Authorization + from flet.auth.oauth_provider import OAuthProvider from flet.messaging.session import Session from flet.pubsub.pubsub_client import PubSubClient + +def _default_authorization_impl() -> "type[Authorization]": + """Resolve the default `Authorization` implementation lazily. + + Deferring these imports keeps the auth subsystem out of the cold-start + import graph for apps that never call `Page.login`. + """ + if not is_pyodide(): + from flet.auth.authorization_service import AuthorizationService + + return AuthorizationService + from flet.auth.authorization import Authorization + + return Authorization + + try: from typing import ParamSpec except ImportError: @@ -88,7 +92,7 @@ logger = logging.getLogger("flet") -AT = TypeVar("AT", bound=Authorization) +AT = TypeVar("AT", bound="Authorization") InputT = ParamSpec("InputT") RetT = TypeVar("RetT") @@ -652,6 +656,8 @@ def render( **kwargs: Keyword arguments passed to `component`. """ + from flet.components.component import Renderer + logger.debug("Page.render()") self._notify = self.__notify self.views[0].controls = Renderer().render(component, *args, **kwargs) @@ -675,6 +681,8 @@ def render_views( **kwargs: Keyword arguments passed to `component`. """ + from flet.components.component import Renderer + logger.debug("Page.render_views()") self._notify = self.__notify self.views = Renderer().render(component, *args, **kwargs) @@ -1045,7 +1053,7 @@ def get_upload_url(self, file_name: str, expires: int) -> str: async def login( self, - provider: OAuthProvider, + provider: "OAuthProvider", fetch_user: bool = True, fetch_groups: bool = False, scope: Optional[list[str]] = None, @@ -1055,14 +1063,16 @@ async def login( ] = None, complete_page_html: Optional[str] = None, redirect_to_page: Optional[bool] = False, - authorization: type[AT] = AuthorizationImpl, - ) -> AT: + authorization: "Optional[type[AT]]" = None, + ) -> "AT": """ Starts OAuth flow. See [Authentication](https://flet.dev/docs/cookbook/authentication) guide for more information and examples. """ + if authorization is None: + authorization = _default_authorization_impl() self.__authorization = authorization( provider, fetch_user=fetch_user, @@ -1273,7 +1283,7 @@ def executor(self) -> Optional[ThreadPoolExecutor]: return self.session.connection.executor @property - def auth(self) -> Optional[Authorization]: + def auth(self) -> "Optional[Authorization]": """ The current authorization context, or `None` if the user is not authorized. """ @@ -1310,6 +1320,7 @@ def browser_context_menu(self): """ The BrowserContextMenu service for the current page. """ + from flet.controls.services.browser_context_menu import BrowserContextMenu return BrowserContextMenu() @@ -1324,6 +1335,7 @@ def shared_preferences(self): """ The SharedPreferences service for the current page. """ + from flet.controls.services.shared_preferences import SharedPreferences return SharedPreferences() @@ -1338,6 +1350,7 @@ def clipboard(self): """ The Clipboard service for the current page. """ + from flet.controls.services.clipboard import Clipboard return Clipboard() @@ -1352,6 +1365,7 @@ def storage_paths(self): """ The StoragePaths service for the current page. """ + from flet.controls.services.storage_paths import StoragePaths return StoragePaths() diff --git a/sdk/python/packages/flet/src/flet/messaging/session.py b/sdk/python/packages/flet/src/flet/messaging/session.py index 460e800431..a3508964fa 100644 --- a/sdk/python/packages/flet/src/flet/messaging/session.py +++ b/sdk/python/packages/flet/src/flet/messaging/session.py @@ -5,9 +5,8 @@ import traceback import weakref from datetime import datetime, timedelta, timezone -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, Optional -from flet.components.hooks.use_effect import EffectHook from flet.controls.base_control import BaseControl from flet.controls.context import _context_page, context from flet.controls.object_patch import ObjectPatch @@ -25,6 +24,11 @@ from flet.utils.object_model import patch_dataclass from flet.utils.strings import random_string +if TYPE_CHECKING: + # Annotation-only (quoted at use site): deferred so a Session doesn't eagerly + # pull the components/hooks subsystem (cold-start import cost). + from flet.components.hooks.use_effect import EffectHook + logger = logging.getLogger("flet") patch_logger = logging.getLogger("flet_object_patch") @@ -631,7 +635,7 @@ def schedule_update(self, control: BaseControl): self.__pending_updates.add(control) self.__updates_ready.set() - def schedule_effect(self, hook: EffectHook, is_cleanup: bool): + def schedule_effect(self, hook: "EffectHook", is_cleanup: bool): """ Queues an effect hook setup/cleanup operation for scheduler execution. From 9578a728e459415813418e74dfe82062e469c737 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 16:20:14 -0700 Subject: [PATCH 41/47] docs: version-prefix the 0.86.0 breaking-changes guides (#6599) Match the `v---` filename convention (introduced in the flet-clean PR for the 0.85.0 guides). Prefix the four guides added on dart-bridge for 0.86.0 with `v0-86-0-`: - compile-on-by-default - data-channel-protocol-upgrade - default-bundled-python-3-14 - removed-pyodide-version-export Update the breaking-changes index links and the two CHANGELOG links (compile-on-by-default, data-channel-protocol-upgrade) to the new paths. --- CHANGELOG.md | 4 ++-- website/docs/updates/breaking-changes/index.md | 8 ++++---- ...-on-by-default.md => v0-86-0-compile-on-by-default.md} | 0 ...pgrade.md => v0-86-0-data-channel-protocol-upgrade.md} | 0 ...hon-3-14.md => v0-86-0-default-bundled-python-3-14.md} | 0 ...xport.md => v0-86-0-removed-pyodide-version-export.md} | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename website/docs/updates/breaking-changes/{compile-on-by-default.md => v0-86-0-compile-on-by-default.md} (100%) rename website/docs/updates/breaking-changes/{data-channel-protocol-upgrade.md => v0-86-0-data-channel-protocol-upgrade.md} (100%) rename website/docs/updates/breaking-changes/{default-bundled-python-3-14.md => v0-86-0-default-bundled-python-3-14.md} (100%) rename website/docs/updates/breaking-changes/{removed-pyodide-version-export.md => v0-86-0-removed-pyodide-version-export.md} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d4155d0a..0127da4362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,8 @@ ### Breaking changes * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. -* `flet build` / `flet publish` now **compile your app and packages to `.pyc` by default** (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (`zipimport`) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain `--no-compile-app` / `--no-compile-packages` (via `argparse.BooleanOptionalAction`; the existing `--compile-app` / `--compile-packages` still work), and `[tool.flet.compile].app` / `.packages` now default to `true`. Pass `--no-compile-*` or set them to `false` to restore the old behavior (faster iterative builds, or keeping `.py` source in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the [compile-on-by-default](/docs/updates/breaking-changes/compile-on-by-default) guide ([#6598](https://github.com/flet-dev/flet/pull/6598)) by @FeodorFitsner. -* Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. +* `flet build` / `flet publish` now **compile your app and packages to `.pyc` by default** (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (`zipimport`) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain `--no-compile-app` / `--no-compile-packages` (via `argparse.BooleanOptionalAction`; the existing `--compile-app` / `--compile-packages` still work), and `[tool.flet.compile].app` / `.packages` now default to `true`. Pass `--no-compile-*` or set them to `false` to restore the old behavior (faster iterative builds, or keeping `.py` source in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the [compile-on-by-default](/docs/updates/breaking-changes/v0-86-0-compile-on-by-default) guide ([#6598](https://github.com/flet-dev/flet/pull/6598)) by @FeodorFitsner. +* Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. ### Bug fixes diff --git a/website/docs/updates/breaking-changes/index.md b/website/docs/updates/breaking-changes/index.md index 54cb72890d..4fea995e46 100644 --- a/website/docs/updates/breaking-changes/index.md +++ b/website/docs/updates/breaking-changes/index.md @@ -26,10 +26,10 @@ Each guide explains the change, the reason for it, and how to migrate your code. #### Breaking changes -- [Default bundled Python version is now 3.14](/docs/updates/breaking-changes/default-bundled-python-3-14) -- [App and packages are compiled to `.pyc` by default](/docs/updates/breaking-changes/compile-on-by-default) -- [`flet.version.pyodide_version` and `PYODIDE_VERSION` removed](/docs/updates/breaking-changes/removed-pyodide-version-export) -- [Flet protocol framing upgraded for DataChannel support](/docs/updates/breaking-changes/data-channel-protocol-upgrade) +- [Default bundled Python version is now 3.14](/docs/updates/breaking-changes/v0-86-0-default-bundled-python-3-14) +- [App and packages are compiled to `.pyc` by default](/docs/updates/breaking-changes/v0-86-0-compile-on-by-default) +- [`flet.version.pyodide_version` and `PYODIDE_VERSION` removed](/docs/updates/breaking-changes/v0-86-0-removed-pyodide-version-export) +- [Flet protocol framing upgraded for DataChannel support](/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade) ### Released in Flet 0.85.0 diff --git a/website/docs/updates/breaking-changes/compile-on-by-default.md b/website/docs/updates/breaking-changes/v0-86-0-compile-on-by-default.md similarity index 100% rename from website/docs/updates/breaking-changes/compile-on-by-default.md rename to website/docs/updates/breaking-changes/v0-86-0-compile-on-by-default.md diff --git a/website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md b/website/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade.md similarity index 100% rename from website/docs/updates/breaking-changes/data-channel-protocol-upgrade.md rename to website/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade.md diff --git a/website/docs/updates/breaking-changes/default-bundled-python-3-14.md b/website/docs/updates/breaking-changes/v0-86-0-default-bundled-python-3-14.md similarity index 100% rename from website/docs/updates/breaking-changes/default-bundled-python-3-14.md rename to website/docs/updates/breaking-changes/v0-86-0-default-bundled-python-3-14.md diff --git a/website/docs/updates/breaking-changes/removed-pyodide-version-export.md b/website/docs/updates/breaking-changes/v0-86-0-removed-pyodide-version-export.md similarity index 100% rename from website/docs/updates/breaking-changes/removed-pyodide-version-export.md rename to website/docs/updates/breaking-changes/v0-86-0-removed-pyodide-version-export.md From d73d12e83d9bee4979dd2cc3b0122bae8e589a9d Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 19:14:13 -0700 Subject: [PATCH 42/47] docs(changelog): add missing 0.86.0 items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three user-facing changes done in the dart-bridge branch had no CHANGELOG entry. Add them: - New feature: in-process Python transport (dart_bridge FFI) — the third protocol transport alongside UDS/TCP sockets, the FletApp(channelBuilder:) seam serious_python uses to embed Python in-process, the build template's migration off sockets, and the Android session-restart rebinding. - Improvement: lazy `import flet` (PEP 562 __getattr__), ~2.0s -> ~0.15s on a mid-range Android device (#6597). - Bug fix: ValueKey value-type preservation so find.byKey / find_by_key locate controls by user-assigned key (Dart + Python tester). --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0127da4362..229bf98ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Multi-version bundled CPython support in `flet build` and `flet publish`. Pick the runtime your app ships with via the new `--python-version` flag (3.12 / 3.13 / 3.14), or let it be derived from `[project].requires-python` in your `pyproject.toml`; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.0), and Emscripten wheel platform tag are all resolved from `flet-dev/python-build`'s date-keyed manifest. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append with `prerelease=True` — opt-in only via an explicit `--python-version 3.15` or `requires-python = "==3.15.*"`, never the auto-resolved default. Requires `serious_python` >= 3.0.0, now pinned in the `flet build` template. See the new [Choosing a Python version](https://flet.dev/docs/publish#choosing-a-python-version) docs section ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Add `ft.DataChannel`: dedicated byte channels for widgets that move bulk binary data (image frames, audio buffers, ML tensors) between Dart and Python, bypassing the MsgPack control protocol. The Dart side opens a channel via `FletBackend.of(context).openDataChannel()` and announces it to Python by firing a `data_channel_open` control event with `{channel_name, channel_id}`; the Python side declares `on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]]` and captures the channel via `self.get_data_channel(e.channel_id)`. Backed by a dedicated `PythonBridge` per channel in embedded native mode (4–7 GiB/s on M2 Pro) and by the default `ProtocolMuxedDataChannelFactory` in dev / web modes (raw-byte frames muxed over the active Flet protocol transport with a 1-byte type discriminator). Pyodide gets zero-copy outbound sends via `postMessage` Transferable ArrayBuffer. First consumer: `flet-charts` `MatplotlibChartCanvas`, migrated from `_invoke_method` PNG dispatch to a 1-byte-opcode data channel by @FeodorFitsner. +* **In-process Python transport (`dart_bridge` FFI).** `package:flet` gains a third protocol transport alongside the UDS / TCP socket servers: it can run Flet's MsgPack protocol over an in-process `dart_bridge` byte channel via a `FletApp(channelBuilder: …)` seam (the `flet` package stays Python-independent — it doesn't depend on `serious_python` or know about `PythonBridge`; the embedder wires the channel). `serious_python` >= 3.0.0 uses this seam to embed the Python interpreter **in-process** instead of talking to it over a localhost socket, and the `flet build` template migrates from sockets to the FFI transport. On Android, where the OS may keep the process alive across a back-button quit and restart only the Dart VM, the transport rebinds to the new VM's `dart_bridge` ports on a session-restart signal (`libdart_bridge` >= 1.3.0) — the Python process and its in-memory state are preserved and the Flet session is rebuilt from `REGISTER_CLIENT` by @FeodorFitsner. ### Improvements @@ -15,6 +16,7 @@ * `client/web/python.js` and the build template's `python.js` no longer hardcode `defaultPyodideUrl`. `patch_index.py` now injects `flet.pyodideUrl` per build (CDN URL by default, or the local `pyodide/pyodide.js` path under `--no-cdn`) so the runtime URL always tracks the resolved Pyodide release ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Stream-oriented Flet protocol transports (UDS / TCP used by `flet run` dev mode) now use length-prefixed framing instead of streaming `msgpack.Unpacker.feed`. Combined with a new 1-byte type discriminator at the head of every packet (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket, `dart_bridge` FFI, Pyodide `postMessage`). `StreamingMsgpackDeserializer` is removed from `package:flet`; each inbound packet is one complete MsgPack value, decoded one-shot via `msgpack.deserialize(bytes)` by @FeodorFitsner. * Bump the bundled Flutter to **3.44.2** (from 3.41.7). The Flet client and the `flet build` template migrate to Flutter 3.44's built-in Kotlin (the Android app no longer applies the Kotlin Gradle plugin itself) and Java 17; the client's Gradle wrapper moves to 8.14 by @FeodorFitsner. +* **Faster mobile cold start: `import flet` is now lazy.** The `flet` package previously executed its full ~270-module public API eagerly on `import flet`; it now resolves public names on first access via a module-level `__getattr__` (PEP 562), so an app loads only the modules it actually uses. On a mid-range Android device this cut `import flet` from ~2.0s to ~0.15s. The eager subsystem clusters that `Page` pulled in (auth, components/hooks, Cupertino controls) are deferred too. Type checkers, IDEs, and `from flet import *` are unaffected ([#6597](https://github.com/flet-dev/flet/pull/6597)) by @FeodorFitsner. ### Breaking changes @@ -27,6 +29,7 @@ * Fix `flet build` failing on Windows when a dependency is pulled in via `[tool.flet.].dev_packages` (or any local-path install): the rewritten ` @ file://` URL now uses `Path.as_uri()`, producing the correct `file:///D:/...` three-slash form instead of `file://D:\...`, which pip on Windows parsed as a UNC path and aborted with `OSError: [Errno 2] No such file or directory: '\\\\D:\\a\\...'` ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. * Fix `flet build web --python-version 3.13` failing to match any Pyodide-built native wheel. The 3.13 row in the Python version registry was set to Pyodide platform tag `pyodide-2025.0-wasm32`, but Pyodide actually publishes 0.29 wheels under `pyemscripten_2025_0_wasm32` (the `pyodide_` → `pyemscripten_` prefix transition happened at 0.28/0.29, not at 314.0). Corrected to `pyemscripten-2025.0-wasm32` so pip's wheel selection picks up the correct tags by @FeodorFitsner. * `flet build` now cleans the build directory when the bundled Python version changes between builds, preventing stale compiled bytecode from the previous version crashing the app at runtime with `ImportError: bad magic number` by @FeodorFitsner. +* Fix locating Flet controls by their user-assigned `key` in tests. `ValueKey(control.key)` was constructed as `ValueKey`, and Flutter's runtimeType-strict `ValueKey.==` never matches that against the `ValueKey` the rendered widget carries — so `find.byKey(Key('foo'))` (flutter_test) and `find_by_key('foo')` (Flet tester) located 0 widgets. The `ValueKey` is now built with the value's concrete type (String → `ValueKey`, int → `ValueKey`, …) on both the Dart and Python sides by @FeodorFitsner. ## 0.85.3 From e1c665734212d3881c71ec7616a51dbbdd0dfa0f Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 17 Jun 2026 19:48:24 -0700 Subject: [PATCH 43/47] docs(extend): add DataChannel section to user-extensions guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Practical reference for extension authors who need to move bulk binary data (image frames, audio buffers, ML tensors) between Python and Dart without the MsgPack control protocol overhead. Source distillation of `dedicated-data-channels.md` in flet-dev/serious-python, scoped to what an extension author actually needs to ship a working widget: * When to reach for DataChannel vs. when the regular property protocol is already fast enough. * Python-side API: `on_data_channel_open` event handler + `Control.get_data_channel(channel_id)` to attach. * Dart-side API: `FletBackend.of(context).openDataChannel()` + firing the `data_channel_open` control event with `{channel_name, channel_id}`. * Multi-channel pattern (`channel_name` dispatch). * Threading + backpressure caveats — `on_bytes` runs under the GIL on a transport thread, receive queues are unbounded by default. * Cross-links to the full design doc, the breaking-change protocol guide, and the MatplotlibChartCanvas reference impl (both halves). Deliberately omits wire-format, Isolate-scope, and throughput-table material — those live in `dedicated-data-channels.md` for anyone needing the deeper picture. Placed between "Customize properties" and "Examples" since it's opt-in advanced functionality most extension authors won't need. --- website/docs/extend/user-extensions.md | 155 +++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/website/docs/extend/user-extensions.md b/website/docs/extend/user-extensions.md index efcf1ada06..ff95181bfd 100644 --- a/website/docs/extend/user-extensions.md +++ b/website/docs/extend/user-extensions.md @@ -464,6 +464,161 @@ Common pitfalls: You can find source code for this example [here](https://github.com/flet-dev/flet-spinkit). +## Dedicated data channels + +Property updates and method calls between Python and Dart go over Flet's MsgPack-framed control protocol. That works well for widget state and small events but caps at ~1 GB/s with several allocations per frame. For widgets that need to move **bulk binary data** — image frames, audio buffers, file blobs, ML inference tensors — Flet provides **dedicated `DataChannel`s** that bypass MsgPack entirely and reach memory-bandwidth-class throughput on every supported platform. + +Examples of widgets that benefit: + +- A chart receiving a stream of bitmaps from Python (1080p RGBA at 60 fps ≈ 480 MB/s — far above what the control protocol can sustain) +- A camera widget pushing frames Dart → Python for ML inference +- A microphone widget streaming PCM samples to a Python DSP pipeline + +For sub-KB widget state, you don't need this — the regular property protocol is faster end-to-end at that size. Reach for `DataChannel` when payloads start at a few KB and especially when they stream at high rates. + +### How it works + +Allocation lives on the **Dart side**: the widget calls `FletBackend.of(context).openDataChannel()` in `initState`, then **fires a control event named `data_channel_open`** carrying `{channel_name, channel_id}`. The Python side declares an `on_data_channel_open` handler, retrieves the matching channel via `Control.get_data_channel(channel_id)`, and starts sending/receiving bytes. + +Transport is chosen per deployment mode automatically — dedicated `PythonBridge` per channel in embedded native (`flet build` for desktop/mobile), raw-byte frames muxed over `postMessage` with Transferable ArrayBuffer in Pyodide, muxed over the protocol socket in `flet run` dev mode, muxed over WebSocket with a Python server. **Widget code is identical across all modes.** + +### Python-side API + +```python +from typing import Callable, Optional + +import flet as ft + + +@ft.control("MyImageChart") +class MyImageChart(ft.LayoutControl): + on_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]] = None + + def init(self) -> None: + # `init` is the @ft.control post-construct hook (runs before `did_mount`). + # Wire up the channel-capture handler here. + self._frames: Optional[ft.DataChannel] = None + if self.on_data_channel_open is None: + self.on_data_channel_open = self._capture_channel + + def _capture_channel(self, e: ft.DataChannelOpenEvent) -> None: + # `e.channel_name` is the label the Dart side put in the event + # payload — dispatch on it when a widget opens multiple channels. + # Single-channel widgets can ignore it. + self._frames = self.get_data_channel(e.channel_id) + # Optional: subscribe to bytes flowing Dart → Python. + self._frames.on_bytes(self._on_frame_from_dart) + + def push_frame(self, rgba_bytes: bytes) -> None: + """Python → Dart — fire-and-forget byte send.""" + if self._frames is not None: + self._frames.send(rgba_bytes) + + def _on_frame_from_dart(self, payload: bytes) -> None: + # Called from the transport's delivery thread (under the GIL in + # embedded native mode). Push to a `queue.Queue` and let a worker + # drain — don't do heavy CPU work here, it'll starve the transport. + ... +``` + +`ft.DataChannel` is an abstract class — instances come back from `Control.get_data_channel(channel_id)`. Its surface: + +- `send(payload: bytes)` — Python → Dart, fire-and-forget +- `on_bytes(callback: Callable[[bytes], None] | None)` — register a handler for bytes pushed from Dart; pass `None` to clear +- `close()` — release the channel (idempotent; the framework auto-closes on control unmount, you rarely need to call it explicitly) + +The `ft.DataChannelOpenEvent` fields are `channel_name: str` and `channel_id: int`. The field is `channel_name`, not `name`, because `name` is reserved on the base `Event` class for the event's own name (`"data_channel_open"`). + +### Dart-side API + +```dart +import 'package:flet/flet.dart'; + +class MyImageChartState extends State { + late final DataChannel _frames; + StreamSubscription? _sub; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_sub != null) return; // initialise lazily, once + _frames = FletBackend.of(context).openDataChannel(); + _sub = _frames.messages.listen(_onFrameFromPython); + // Tell Python about the channel via a regular control event. + widget.control.triggerEvent("data_channel_open", { + "channel_name": "frames", + "channel_id": _frames.id, + }); + } + + void _onFrameFromPython(Uint8List bytes) { + // hand to a Texture, dart:ui.Image.fromPixels, etc. + } + + void _pushFrameToPython(Uint8List bytes) { + _frames.send(bytes); + } + + @override + void dispose() { + _sub?.cancel(); + _frames.close(); + super.dispose(); + } +} +``` + +`DataChannel` Dart-side surface: + +- `int get id` — the channel identifier that goes into the `data_channel_open` event payload +- `Stream get messages` — bytes pushed from Python +- `bool send(Uint8List bytes)` — Dart → Python, fire-and-forget +- `void close()` — release the channel (idempotent) + +**Allocate the channel in `didChangeDependencies`, not `initState`** — `FletBackend.of(context)` needs an active `BuildContext` and that's the first lifecycle hook where it's safely available. + +Neither side imports `serious_python` or `dart_bridge`. The DataChannel API surface lives entirely in `package:flet` / `flet`, so your extension's dependencies stay the same. + +### Multiple channels per widget + +A control can open as many channels as it needs — each `openDataChannel()` call mints a unique id. Disambiguate them by `channel_name` in the event: + +```dart +// Dart — open two channels, label each +_frames = FletBackend.of(context).openDataChannel(); +_audio = FletBackend.of(context).openDataChannel(); +widget.control.triggerEvent("data_channel_open", { + "channel_name": "frames", + "channel_id": _frames.id, +}); +widget.control.triggerEvent("data_channel_open", { + "channel_name": "audio", + "channel_id": _audio.id, +}); +``` + +```python +# Python — dispatch on channel_name +def _on_data_channel_open(self, e: ft.DataChannelOpenEvent) -> None: + match e.channel_name: + case "frames": + self._frames = self.get_data_channel(e.channel_id) + case "audio": + self._audio = self.get_data_channel(e.channel_id) +``` + +### Threading + backpressure + +The `on_bytes` handler runs **synchronously under the GIL** on whatever OS thread the transport delivered from. For anything CPU-heavy (PNG decode, ML inference) push the payload onto a `queue.Queue` or `asyncio.Queue` and let a worker drain — blocking the delivery thread will starve the transport. + +The receiving side's queue is **unbounded by default**. If a producer outpaces the consumer (camera frames into a slow decoder, matplotlib rotation into Flutter paint), memory grows. For media-streaming widgets, implement backpressure or a drop-old policy on the producer side. A reference example is [`MatplotlibChartCanvas`](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py) — it uses a 1-byte ack frame from Dart so the producer waits per-frame, mirroring matplotlib WebAgg's `waiting` flag pattern. + +### See also + +- **First-party reference widget**: `MatplotlibChartCanvas` — a complete migration from `_invoke_method` PNG dispatch to a 1-byte-opcode data channel, including backpressure ack and the GPU / CPU rendering strategies. [Python](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-charts/src/flet_charts/matplotlib_chart_canvas.py) · [Dart](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-charts/src/flutter/flet_charts/lib/src/matplotlib_chart_canvas.dart) +- **Full design / performance notes** (wire format, cross-mode operation, concurrency model, empirical numbers): [`dedicated-data-channels.md`](https://github.com/flet-dev/serious-python/blob/main/docs/dedicated-data-channels.md) in `flet-dev/serious-python`. +- **Wire-format protocol upgrade** for anyone implementing a custom Flet backend or sidecar: [Flet protocol framing upgraded for DataChannel support](/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade). + ## Examples Flet has controls that are implemented as [built-in extensions](built-in-extensions.md) and could serve as a starting point for your own controls. From 47be7ffbfd1b39fb02cfe942a6e3d53f6a6de3a9 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 18 Jun 2026 12:11:54 -0700 Subject: [PATCH 44/47] fix(build): only package requested --arch ABIs in Android APK/AAB Lift the Android-ABI fixes from #6578 onto the dart-bridge branch, adapted to the manifest-driven `PythonRelease`. Per-version ABI set is now sourced from python-build's `pythons..android_abis` (release 20260618+); flet keeps no hardcoded ABI table. - `PythonRelease` gains `android_abis: tuple[str, ...]`, populated from the manifest. Pinned `PYTHON_BUILD_RELEASE_DATE` bumped 20260614 -> 20260618 (the python-build release that introduced the field). - New `utils/android.py` with `ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM`, `flutter_target_platforms()`, `excluded_android_abis()`. - `build_base.py`: `action="extend"` on `--arch`, `--source-packages`, `--permissions` (repeated flags now accumulate instead of overwriting). Android-only `--arch` validation: invalid ABI -> clean error; ABI not in `python_release.android_abis` -> clean error pointing at the bundled Python; empty defaults to the supported ABI list. Cookiecutter context gains `android_excluded_abis`. `--arch` to serious_python is comma-joined (Dart multi-option). `resolve_output_path()` factored out so `build.py`'s pre-build cleanup can share the path resolution. - `build.py`: forwards requested ABIs to Flutter as `--target-platform android-arm,android-arm64,android-x64` (so `--split-per-abi` builds only the requested splits), wipes each platform's output dir before the flutter build so stale artifacts don't leak into the user's output, fixes a `self.target_platform in "apk"` substring-vs-equality bug uncovered by the new code path. - Cookiecutter Android template: `packaging.jniLibs.excludes += listOf("lib//**", ...)` block, gated on `cookiecutter.options.android_excluded_abis`. Needed because AGP unions defaultConfig + buildType `ndk.abiFilters`, and other plugins' jniLibs slip past `--target-platform`. - CHANGELOG breaking-change + two bug-fix bullets; android.md gets per-version ABI support, PEP 738 note, default-by-bundled-Python wording. Refs: #6567, #6578 by @ndonkoHenri. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 3 + .../flet-cli/src/flet_cli/commands/build.py | 37 ++++++- .../src/flet_cli/commands/build_base.py | 98 +++++++++++++++---- .../flet-cli/src/flet_cli/utils/android.py | 53 ++++++++++ .../src/flet_cli/utils/python_versions.py | 6 +- .../android/app/build.gradle.kts | 11 +++ website/docs/publish/android.md | 20 ++-- 7 files changed, 201 insertions(+), 27 deletions(-) create mode 100644 sdk/python/packages/flet-cli/src/flet_cli/utils/android.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 229bf98ae3..01723f4c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Breaking changes * `flet build` and `flet publish` now bundle CPython 3.14 by default (previously 3.12, implicit via the old single-version `serious_python`). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with `--python-version 3.12` (CLI), `requires-python = ">=3.12,<3.13"` (pyproject), or `SERIOUS_PYTHON_VERSION=3.12` in the build environment ([#6577](https://github.com/flet-dev/flet/pull/6577)) by @FeodorFitsner. +* Android builds now include only the ABIs the bundled Python supports: for Python 3.13+, `armeabi-v7a` is no longer supported nor packaged by default. An explicit `--arch armeabi-v7a` fails with a clear error unless combined with `--python-version 3.12`, which is the only Python version supporting it. The per-version ABI set is sourced from python-build's manifest (`pythons..android_abis`), not hardcoded in flet ([#6578](https://github.com/flet-dev/flet/pull/6578)) by @ndonkoHenri. * `flet build` / `flet publish` now **compile your app and packages to `.pyc` by default** (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (`zipimport`) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain `--no-compile-app` / `--no-compile-packages` (via `argparse.BooleanOptionalAction`; the existing `--compile-app` / `--compile-packages` still work), and `[tool.flet.compile].app` / `.packages` now default to `true`. Pass `--no-compile-*` or set them to `false` to restore the old behavior (faster iterative builds, or keeping `.py` source in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the [compile-on-by-default](/docs/updates/breaking-changes/v0-86-0-compile-on-by-default) guide ([#6598](https://github.com/flet-dev/flet/pull/6598)) by @FeodorFitsner. * Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (`0x00` = MsgPack control frame, `0x01` = raw DataChannel frame). WebSocket / `postMessage` / `dart_bridge` transports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — running `flet run` with mismatched `flet` versions across CLI and runtime is no longer supported. See the [DataChannel protocol framing upgrade](/docs/updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade) guide. The `MatplotlibChartCanvas` widget transports its full / diff / clear frames via a `DataChannel` rather than `_invoke_method` arguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner. @@ -30,6 +31,8 @@ * Fix `flet build web --python-version 3.13` failing to match any Pyodide-built native wheel. The 3.13 row in the Python version registry was set to Pyodide platform tag `pyodide-2025.0-wasm32`, but Pyodide actually publishes 0.29 wheels under `pyemscripten_2025_0_wasm32` (the `pyodide_` → `pyemscripten_` prefix transition happened at 0.28/0.29, not at 314.0). Corrected to `pyemscripten-2025.0-wasm32` so pip's wheel selection picks up the correct tags by @FeodorFitsner. * `flet build` now cleans the build directory when the bundled Python version changes between builds, preventing stale compiled bytecode from the previous version crashing the app at runtime with `ImportError: bad magic number` by @FeodorFitsner. * Fix locating Flet controls by their user-assigned `key` in tests. `ValueKey(control.key)` was constructed as `ValueKey`, and Flutter's runtimeType-strict `ValueKey.==` never matches that against the `ValueKey` the rendered widget carries — so `find.byKey(Key('foo'))` (flutter_test) and `find_by_key('foo')` (Flet tester) located 0 widgets. The `ValueKey` is now built with the value's concrete type (String → `ValueKey`, int → `ValueKey`, …) on both the Dart and Python sides by @FeodorFitsner. +* Fix `flet build apk` / `flet build aab` with `--arch` packaging native libraries for *all* Android ABIs instead of only the requested ones. The requested architectures are now forwarded to Flutter as `--target-platform` (so `--split-per-abi` builds only the requested splits), unrequested ABI directories are excluded from the artifact via `packaging.jniLibs.excludes`, Android `--arch` values are validated against the bundled Python's supported ABIs, multiple `--arch` values now correctly reach `serious_python` (comma-joined), and stale artifacts from previous builds are no longer copied into the output directory ([#6567](https://github.com/flet-dev/flet/issues/6567), [#6578](https://github.com/flet-dev/flet/pull/6578)) by @ndonkoHenri. +* Fix repeated `--arch`, `--source-packages` and `--permissions` flags in `flet build` keeping only the values of the last occurrence (`action="extend"` on each) ([#6578](https://github.com/flet-dev/flet/pull/6578)) by @ndonkoHenri. ## 0.85.3 diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py index fb45b8e5a4..033ce5e5e4 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build.py @@ -1,9 +1,13 @@ import argparse +import os +import shutil +from pathlib import Path from rich.console import Group from rich.live import Live from flet_cli.commands.build_base import BaseBuildCommand, console +from flet_cli.utils.android import flutter_target_platforms class Command(BaseBuildCommand): @@ -116,9 +120,24 @@ def add_flutter_command_args(self, args: list[str]): ["build", self.platforms[self.target_platform]["flutter_build_command"]] ) - if self.target_platform in "apk" and self.template_data["split_per_abi"]: + if self.target_platform == "apk" and self.template_data["split_per_abi"]: args.append("--split-per-abi") + if ( + self.target_platform in ("apk", "aab") + and self.template_data["options"]["target_arch"] + ): + args.extend( + [ + "--target-platform", + ",".join( + flutter_target_platforms( + self.template_data["options"]["target_arch"] + ) + ), + ] + ) + if self.target_platform in ["ipa"]: if self.template_data["ios_provisioning_profile"]: args.extend( @@ -166,6 +185,22 @@ def run_flutter(self): f"{self.platforms[self.target_platform]['status_text']}[/cyan]..." ) + # Clear the build output directories of artifacts from previous runs. Flutter + # only ever adds files to them, and copy_build_output harvests them wholesale — + # so without this, a previous build with different options (e.g. --arch, + # --split-per-abi, or a renamed product) would leak its artifacts into the + # user's output directory. + assert self.flutter_dir + flutter_dir = self.flutter_dir.resolve() + for output in self.platforms[self.target_platform]["outputs"]: + output_dir = Path( + os.path.dirname(self.resolve_output_path(output)) + ).resolve() + # only delete directories that are strictly inside the generated Flutter + # project (and never the project directory itself). + if output_dir != flutter_dir and output_dir.is_relative_to(flutter_dir): + shutil.rmtree(output_dir, ignore_errors=True) + self._run_flutter_command() console.log( diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py index 48e787eb6e..a1431164a7 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py @@ -23,6 +23,10 @@ verbose2_style, warning_style, ) +from flet_cli.utils.android import ( + ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM, + excluded_android_abis, +) from flet_cli.utils.cli import parse_cli_bool_value from flet_cli.utils.hash_stamp import HashStamp from flet_cli.utils.merge import merge_dict @@ -260,10 +264,13 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--arch", dest="target_arch", + action="extend", nargs="+", default=[], help="Build for specific CPU architectures " - "(used in macOS and Android builds only). Example: `--arch arm64 x64`", + "(used in macOS and Android builds only). " + "Android: arm64-v8a, armeabi-v7a, x86_64; macOS: arm64, x64. " + "Example: `--arch arm64-v8a`", ) parser.add_argument( "--exclude", @@ -515,6 +522,7 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( "--source-packages", dest="source_packages", + action="extend", nargs="+", default=[], help="The list of Python packages to install from source distributions", @@ -587,6 +595,7 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "--permissions", dest="permissions", type=str.lower, + action="extend", nargs="+", default=[], choices=["location", "camera", "microphone", "photo_library"], @@ -1144,6 +1153,40 @@ def _xml_attr_value(v): or self.get_pyproject(f"tool.flet.{self.config_platform}.target_arch") or self.get_pyproject("tool.flet.target_arch") ) + target_arch = ( + target_arch + if isinstance(target_arch, list) + else [target_arch] + if isinstance(target_arch, str) + else [] + ) + if self.package_platform == "Android": + invalid_archs = [ + arch + for arch in target_arch + if arch not in ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM + ] + if invalid_archs: + self.cleanup( + 1, + f"Invalid Android architecture(s): {', '.join(invalid_archs)}.\n" + f"Supported: " + f"{', '.join(ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM)}.\n" + f"Docs: https://flet.dev/docs/publish/android#supported-target-architectures", + ) + python_abis = list(self.python_release.android_abis) + unsupported_archs = [a for a in target_arch if a not in python_abis] + if unsupported_archs: + self.cleanup( + 1, + f"Architecture(s) not supported by Python " + f"{self.python_release.short}: {', '.join(unsupported_archs)}.\n" + f"Supported: {', '.join(python_abis)}.\n" + f"Docs: https://flet.dev/docs/publish/android#supported-target-architectures", + ) + if not target_arch: + # Build only for the ABIs the bundled Python supports. + target_arch = python_abis ios_export_method = ( self.options.ios_export_method @@ -1260,11 +1303,10 @@ def _xml_attr_value(v): "package_platform": self.package_platform, "config_platform": self.config_platform, "python_version": self.python_release.short, - "target_arch": ( - target_arch - if isinstance(target_arch, list) - else [target_arch] - if isinstance(target_arch, str) + "target_arch": target_arch, + "android_excluded_abis": ( + excluded_android_abis(target_arch) + if self.package_platform == "Android" else [] ), "info_plist": info_plist, @@ -1947,8 +1989,11 @@ def package_python_app(self): ] if self.template_data["options"]["target_arch"]: + # serious_python's --arch is a Dart multi-option: values must be + # comma-separated or the flag repeated. Space-separated values + # after the first are silently treated as positional arguments. package_args.extend( - ["--arch"] + self.template_data["options"]["target_arch"] + ["--arch", ",".join(self.template_data["options"]["target_arch"])] ) # Only the short version is passed; serious_python derives the full @@ -2354,6 +2399,32 @@ def _run_flutter_command(self): console.log(build_result.stderr, style=error_style) self.cleanup(build_result.returncode if build_result.returncode else 1) + def resolve_output_path(self, build_output: str) -> str: + """ + Resolve a platform `outputs` glob to an absolute path inside the + Flutter project, substituting the `{arch}` and name placeholders. + + Args: + build_output: An entry of `self.platforms[...]["outputs"]`. + """ + + assert self.flutter_dir + assert self.template_data + + arch = platform.machine().lower() + if arch in {"x86_64", "amd64"}: + arch = "x64" + elif arch in {"arm64", "aarch64"}: + arch = "arm64" + + return ( + str(self.flutter_dir.joinpath(build_output)) + .replace("{arch}", arch) + .replace("{artifact_name}", self.template_data["artifact_name"]) + .replace("{project_name}", self.template_data["project_name"]) + .replace("{product_name}", self.template_data["product_name"]) + ) + def copy_build_output(self): """ Copy generated platform artifacts into the requested output directory. @@ -2369,11 +2440,6 @@ def copy_build_output(self): self.update_status( f"[bold blue]Copying build to [cyan]{self.rel_out_dir}[/cyan] directory...", ) - arch = platform.machine().lower() - if arch in {"x86_64", "amd64"}: - arch = "x64" - elif arch in {"arm64", "aarch64"}: - arch = "arm64" def make_ignore_fn(out_dir, out_glob): """ @@ -2392,13 +2458,7 @@ def ignore(path, names): return ignore for build_output in self.platforms[self.target_platform]["outputs"]: - build_output_dir = ( - str(self.flutter_dir.joinpath(build_output)) - .replace("{arch}", arch) - .replace("{artifact_name}", self.template_data["artifact_name"]) - .replace("{project_name}", self.template_data["project_name"]) - .replace("{product_name}", self.template_data["product_name"]) - ) + build_output_dir = self.resolve_output_path(build_output) if self.verbose > 0: console.log( diff --git a/sdk/python/packages/flet-cli/src/flet_cli/utils/android.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/android.py new file mode 100644 index 0000000000..256a97d796 --- /dev/null +++ b/sdk/python/packages/flet-cli/src/flet_cli/utils/android.py @@ -0,0 +1,53 @@ +"""Helpers for Android-specific build configuration.""" + +ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM = { + "armeabi-v7a": "android-arm", + "arm64-v8a": "android-arm64", + "x86_64": "android-x64", +} +"""Android ABIs supported by Flutter, mapped to `flutter build --target-platform` +values. Order is meaningful: it defines the order in which +generated artifacts (e.g. Gradle packaging excludes) list ABIs.""" + + +def flutter_target_platforms(archs: list[str]) -> list[str]: + """ + Map Android ABI names to `flutter build --target-platform` values. + + Args: + archs: Android ABI names, e.g. `["arm64-v8a"]`. + + Returns: + The corresponding Flutter target platforms, e.g. `["android-arm64"]`. + + Raises: + ValueError: If any ABI is not supported by Flutter. + """ + + platforms = [] + for arch in archs: + platform = ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM.get(arch) + if platform is None: + raise ValueError( + f"Unsupported Android architecture: {arch!r}. " + f"Supported: {', '.join(ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM)}." + ) + platforms.append(platform) + return platforms + + +def excluded_android_abis(archs: list[str]) -> list[str]: + """ + Return the supported Android ABIs not requested in `archs`. + + Args: + archs: Requested Android ABI names; an empty list means "all ABIs" + and yields no exclusions. + + Returns: + ABIs to exclude from the generated artifact. + """ + + if not archs: + return [] + return [abi for abi in ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM if abi not in archs] diff --git a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py index dea7821f1a..fef64433cf 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/utils/python_versions.py @@ -28,7 +28,7 @@ # python-build release this flet pins. Keep in sync with serious_python's # `pythonReleaseDate` (lib/src/python_versions.dart) — both should track the # same python-build release. -PYTHON_BUILD_RELEASE_DATE = "20260614" +PYTHON_BUILD_RELEASE_DATE = "20260618" RELEASE_DATE_ENV = "FLET_PYTHON_BUILD_RELEASE_DATE" MANIFEST_PATH_ENV = "FLET_PYTHON_BUILD_MANIFEST" @@ -43,6 +43,9 @@ class PythonRelease: short: str standalone: str pyodide: str + # Android ABIs python-build publishes distributions for this minor. 32-bit + # Android was dropped in 3.13 (PEP 738), so only 3.12 carries armeabi-v7a. + android_abis: tuple[str, ...] # When True, this release is supported via `--python-version` (and an # explicit `requires-python = "==X.Y.*"` specifier) but is not picked # automatically by the default or by open-ended `requires-python` @@ -102,6 +105,7 @@ def _load_data() -> tuple[tuple[PythonRelease, ...], str]: short=short, standalone=info["full_version"], pyodide=info["pyodide_version"], + android_abis=tuple(info["android_abis"]), prerelease=bool(info.get("prerelease", False)), ) for short, info in manifest["pythons"].items() diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts index 7a75bc2714..2c70718f6c 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/android/app/build.gradle.kts @@ -28,6 +28,17 @@ android { // Python in stored asset zips. Modern packaging (the default at minSdk 23+) // is all that's required. +// flet: excluded_abis {% if cookiecutter.options.android_excluded_abis %} + packaging { + jniLibs { + // Strip native libs of ABIs not requested via `target_arch`. + // `ndk.abiFilters` alone can't do this: the Flutter Gradle plugin adds all default + // ABIs as buildType-level filters and AGP merges the two levels as a union. + excludes += listOf({% for abi in cookiecutter.options.android_excluded_abis %}"lib/{{ abi }}/**"{% if not loop.last %}, {% endif %}{% endfor %}) + } + } +// flet: end of excluded_abis {% endif %} + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 diff --git a/website/docs/publish/android.md b/website/docs/publish/android.md index 1f812ab71d..1f38cf64f8 100644 --- a/website/docs/publish/android.md +++ b/website/docs/publish/android.md @@ -74,10 +74,17 @@ requires distributing the correct APK for each device. The following target architectures are supported: -- [`arm64-v8a`](https://developer.android.com/ndk/guides/abis#arm64-v8a) -- [`armeabi-v7a`](https://developer.android.com/ndk/guides/abis#v7a) -- [`x86_64`](https://developer.android.com/ndk/guides/abis#86-64) -- [`x86`](https://developer.android.com/ndk/guides/abis#x86) +- [`arm64-v8a`](https://developer.android.com/ndk/guides/abis#arm64-v8a) (64-bit) — Python `3.12` and above +- [`x86_64`](https://developer.android.com/ndk/guides/abis#86-64) (64-bit) — Python `3.12` and above +- [`armeabi-v7a`](https://developer.android.com/ndk/guides/abis#v7a) (32-bit) — Python `3.12` **only** + +:::note +The available architectures depend on the +[bundled Python version](index.md#bundled-python): Python dropped +32-bit Android support in `3.13` ([PEP 738](https://peps.python.org/pep-0738/)), +so targeting `armeabi-v7a` requires building with `--python-version 3.12`. By default, +an app is built for all architectures its bundled Python version supports. +::: #### Resolution order @@ -87,8 +94,9 @@ Its value is determined in the following order of precedence: 2. `[tool.flet.android].split_per_abi` 3. `false` -When enabled, 3 APKs are produced by default, one for each of the following ABIs: `arm64-v8a`, -`armeabi-v7a`, and `x86_64`. These can be customized by setting [`target architectures`](index.md#target-architecture). +When enabled, one APK is produced per ABI — by default, one for each +architecture the bundled Python version supports (see above). These can be +customized by setting [`target architectures`](index.md#target-architecture). #### Example From d2d53c1efeab21a64b836d1e95b398fc1fd96663 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 18 Jun 2026 12:26:57 -0700 Subject: [PATCH 45/47] docs(sidebar): list 0.86.0 breaking-change pages under v0.86.0 PR #6601 review (@ndonkoHenri) flagged that the four breaking-change guides added on dart-bridge weren't wired into website/sidebars.yml's v0.86.0 section (only flet-0.86's clear-cache deprecation entry was). Added them in the same order as updates/breaking-changes/index.md: breaking changes first, then deprecations. --- website/sidebars.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/sidebars.yml b/website/sidebars.yml index cb7baeed73..361f2e80af 100644 --- a/website/sidebars.yml +++ b/website/sidebars.yml @@ -69,6 +69,10 @@ docs: Breaking changes and deprecations: _index: updates/breaking-changes/index.md v0.86.0: + Default bundled Python version is now 3.14: updates/breaking-changes/v0-86-0-default-bundled-python-3-14.md + App and packages are compiled to .pyc by default: updates/breaking-changes/v0-86-0-compile-on-by-default.md + flet.version.pyodide_version and PYODIDE_VERSION removed: updates/breaking-changes/v0-86-0-removed-pyodide-version-export.md + Flet protocol framing upgraded for DataChannel support: updates/breaking-changes/v0-86-0-data-channel-protocol-upgrade.md flet build --clear-cache flag deprecated: updates/breaking-changes/v0-86-0-deprecated-clear-cache-flag.md v0.85.0: Deprecated spacing and border helper functions removed: updates/breaking-changes/v0-85-0-removed-spacing-border-helpers.md From b2a49ee86acf0b7aa1dc65d667eb71f5ff4c5c75 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 18 Jun 2026 13:05:28 -0700 Subject: [PATCH 46/47] Add blog post STUB: Flet v0.86.0 release announcement Create a new blog post stub for the Flet 0.86.0 release at website/blog/2026-07-01-flet-v-0-86-release-announcement.md. The file (author: feodor, tag: releases) includes a short summary highlighting routing and dialogs, smoother video, real-time audio, and bug fixes, plus a CTA to GitHub Discussions and Discord; body content is currently marked TBD. --- ...6-07-01-flet-v-0-86-release-announcement.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 website/blog/2026-07-01-flet-v-0-86-release-announcement.md diff --git a/website/blog/2026-07-01-flet-v-0-86-release-announcement.md b/website/blog/2026-07-01-flet-v-0-86-release-announcement.md new file mode 100644 index 0000000000..7e2754f898 --- /dev/null +++ b/website/blog/2026-07-01-flet-v-0-86-release-announcement.md @@ -0,0 +1,18 @@ +--- +slug: flet-v-0-86-release-announcement +title: "Flet 0.86.0: TBD" +authors: feodor +tags: [releases] +--- + +TBD + +## Conclusion + +TBD + +Flet 0.86.0 fills in two pieces that declarative apps were really missing: routing and dialogs. Combined with smoother video, real-time audio, and a healthy round of bug fixes, this release moves the `@ft.component` programming model from "promising" to "production-ready for real apps". + +Try it in your apps and share feedback in [GitHub Discussions](https://github.com/flet-dev/flet/discussions) or on [Discord](https://discord.gg/dzWXP8SHG8). + +Happy Flet-ing! From 706e45230e8c2200969e47c0f953d35e3b6e4aec Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Thu, 18 Jun 2026 15:49:29 -0700 Subject: [PATCH 47/47] Pin serious_python to 3.0.0 Replace the git-based serious_python dependency in the Python template pubspec with a version pin (3.0.0). Removes the git url/ref/path block that referenced the dart-bridge branch, simplifying the template and using the published package instead of an external repo reference. --- .../build/{{cookiecutter.out_dir}}/pubspec.yaml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml index 7643b685e3..22472a5acd 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml @@ -17,15 +17,7 @@ dependencies: flet: path: ../../../../../packages/flet - # PythonBridge (in-process dart_bridge FFI transport) + the Android - # native-mmap packaging ship on the serious-python `android-native-mmap` - # branch. Swap back to a version pin (`serious_python: ^2.0.0`) once it's - # published to pub.dev. - serious_python: - git: - url: https://github.com/flet-dev/serious-python.git - ref: dart-bridge - path: src/serious_python + serious_python: 3.0.0 # MsgPack codec used by the dart_bridge FletBackendChannel implementation # in lib/main.dart — matches the wire format flet's existing socket