perf(flet): lazy public-API imports + defer eager subsystem clusters (mobile cold start)#6597
Merged
Conversation
`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).
…/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.
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.
…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).
a8b10f8 to
cf0a76a
Compare
Deploying flet-website-v2 with
|
| Latest commit: |
cf0a76a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0c2f00f5.flet-website-v2.pages.dev |
| Branch Preview URL: | https://flet-lazy-init.flet-website-v2.pages.dev |
FeodorFitsner
added a commit
that referenced
this pull request
Jun 18, 2026
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).
FeodorFitsner
added a commit
that referenced
this pull request
Jun 19, 2026
…-version Python, native-mmap, DataChannel) (#6601) * fix(controls): preserve concrete value type when constructing ValueKey `ValueKey(controlKey.value)` produced `ValueKey<Object>(value)` because `controlKey.value` is statically typed `Object`. Flutter's `ValueKey.==` is runtimeType-strict, so `ValueKey<Object>('foo')` never equals `ValueKey<String>('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<String>`, int → `ValueKey<int>`, etc. This matches what `Key('foo')` (factory for `ValueKey<String>('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. * feat(transport): add dart_bridge in-process transport (alongside socket) 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=<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. * feat(transport): export FletBackendChannel + msgpack helpers from flet.dart (lets embedders implement channelBuilder) * fix(transport): park embedded dart_bridge run loop until host shutdown 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. * templates(build): migrate from sockets to PythonBridge FFI transport 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://<port>`; 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. * 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. * Bump 3.13.14 / 3.14.6 / Pyodide 314.0.0; thread date-based python-build vars 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 (`…/<YYYYMMDD>/python-*-<full>-*`): * 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) <noreply@anthropic.com> * feat(transport): DataChannel API for high-throughput widget byte streams 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. * feat(flet-charts): migrate MatplotlibChartCanvas to DataChannel 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. * refactor(build): split native FFI runtime out of main.dart for web compat `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. * fix(web): switch Pyodide worker to module type + pyodide.mjs 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. * docs(0.86.0): DataChannel + protocol framing breaking-change guide * 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. * perf(web): transfer Pyodide-worker bytes to main via Transferable ArrayBuffer 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. * fix(flet-charts): restore await-based backpressure for matplotlib frames 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. * ci: fix web client build after flet.version.pyodide_version removal 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 2d8f4a1 on fix-android-arch-filtering. Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com> * docs(breaking-changes): drop dead /docs/extending-flet/data-channels link 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. * ci: bump Node 20 actions to Node 24 versions 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@<old SHA> → v1.7.2 All six actions' action.yml now declare `runs.using: node24`. * fix(tester): preserve ValueKey value type in find_by_key `ValueKey(controlKey.value)` produces `ValueKey<Object>` because `controlKey.value` is statically typed Object. Flutter's `ValueKey.==` is runtimeType-strict, so `ValueKey<Object>('foo')` never equals the `ValueKey<String>('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 (7367050). Switch-dispatch on the runtime type so String → ValueKey<String>, int → ValueKey<int>, 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. * fix(tests): update example imports after folder rename in #6545 #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 1b2e914. * Docusaurus 3.10.1 and Node.js 24 * 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. * 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. * 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 * Resolve Python/Pyodide versions from python-build's manifest 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. * Pin screen_brightness_macos to 2.1.2 (SPM macOS deployment-target regression) 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: aaassseee/screen_brightness#99 * 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. * Correct 0.86.0 changelog for the manifest-backed version model 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. * 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. * 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 * feat(app): dart_bridge session-restart loop (Android process reuse) `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. * feat(build): wire process-reuse signal + skip-on-reuse in build template 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). * feat(build): mutable _exit_port + native-log tee in python.dart bootstrap 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. * fix(build): line-buffer the native-log side of python.dart's TeeWriter 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. * 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. * fix(build/web): add pythonAlreadyRunning getter to native_runtime_stub 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. * build(android): consume serious_python native-mmap packaging + --android-extract-packages (#6595) * 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. * 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. * 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. * build: compile app + packages to .pyc by default (with --no-compile-* opt-out) (#6598) * 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 * perf(flet): lazy public-API imports + defer eager subsystem clusters (mobile cold start) (#6597) * 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). * docs: version-prefix the 0.86.0 breaking-changes guides (#6599) Match the `v<major>-<minor>-<patch>-` 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. * docs(changelog): add missing 0.86.0 items 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). * docs(extend): add DataChannel section to user-extensions guide 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. * 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.<short>.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/<abi>/**", ...)` 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) <noreply@anthropic.com> * 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. * 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. * 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. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cuts Python cold-start time for
fletapps on mobile by importing the framework lazily instead of eagerly executing the whole ~270-module package onimport flet.Measured on a real device (moto g15, compiled
.pycbuild):import flet: 2.02s → 0.15s (240 flet modules at import → 22).What's here (4 commits)
Lazy public API (PEP 562
__getattr__) —flet/__init__.pyno longer runs ~197 eager imports. All 528 public names resolve on first access via a generated_LAZY = {name: module}map and cache into globals; real imports stay underif TYPE_CHECKING:so type checkers / IDEs /__all__are unaffected.__version__stays eager. Resolved objects are identical to the eager imports by construction (the map is generated from the original import statements).Defer Cupertino + deprecated services off Page/View — Cupertino app bar / nav bar are annotation-only (moved to
TYPE_CHECKING, annotations string-quoted); 4 deprecated page service properties construct their service via a function-local import.Defer the auth subsystem off Page —
Authorization/OAuthProvider→TYPE_CHECKING,TypeVarbound → string, andAuthorizationImpldefault resolved lazily inPage.login().Defer components/hooks off Page/BasePage/Session —
unwrap_componentimportsComponentlazily (cached),Rendereris function-local, andSession.schedule_effect'sEffectHookis annotation-only.Counter example import closure across 2–4: 114 → 94 modules.
Why not
from __future__ import annotationsflet's
control_eventresolves event-handler field annotations at runtime viaget_args(), which needs real type objects, not PEP 563 strings (only 2/201 control modules use future-annotations). Module-wide future-annotations breaks event resolution — confirmed by failingtest_events. So the cluster deferrals string-quote only the specific non-event annotations instead.Testing
pytest tests/: 210 passed, 9 skipped on every commit, including the introspection-sensitive suites (test_auth_lazy_imports,test_griffe_deprecations,test_from_dict,test_patch_dataclass,test_validation,test_events).Notes
import fletand import-only / display-only paths. Commit 1 is the headline win.Summary by Sourcery
Make Flet’s Python public API and several subsystems lazily imported to reduce mobile cold-start time and simplify Android packaging by relying on serious_python’s native-mmap support.
Enhancements:
Build:
Documentation: