diff --git a/packages/flet/lib/src/controls/button.dart b/packages/flet/lib/src/controls/button.dart index f52e226d0..d741c4d35 100644 --- a/packages/flet/lib/src/controls/button.dart +++ b/packages/flet/lib/src/controls/button.dart @@ -98,20 +98,22 @@ class _ButtonControlState extends State with FletStoreMixin { var theme = Theme.of(context); var style = parseButtonStyle( - widget.control.internals?["style"], Theme.of(context), - defaultForegroundColor: widget.control - .getColor("color", context, theme.colorScheme.primary)!, - defaultBackgroundColor: widget.control - .getColor("bgcolor", context, theme.colorScheme.surface)!, - defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08), - defaultShadowColor: theme.colorScheme.shadow, - defaultSurfaceTintColor: theme.colorScheme.surfaceTint, - defaultElevation: widget.control.getDouble("elevation", 1)!, - defaultPadding: const EdgeInsets.symmetric(horizontal: 8), - defaultBorderSide: BorderSide.none, - defaultShape: theme.useMaterial3 - ? const StadiumBorder() - : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))); + widget.control.internals?["style"], + theme, + defaultForegroundColor: + widget.control.getColor("color", context, theme.colorScheme.primary)!, + defaultBackgroundColor: widget.control + .getColor("bgcolor", context, theme.colorScheme.surface)!, + defaultOverlayColor: theme.colorScheme.primary.withOpacity(0.08), + defaultShadowColor: theme.colorScheme.shadow, + defaultSurfaceTintColor: theme.colorScheme.surfaceTint, + defaultElevation: widget.control.getDouble("elevation", 1)!, + defaultPadding: const EdgeInsets.symmetric(horizontal: 8), + defaultBorderSide: BorderSide.none, + defaultShape: theme.useMaterial3 + ? const StadiumBorder() + : RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ); Widget error = const ErrorControl("Error displaying Button", description: "\"icon\" must be specified together with \"content\""); @@ -203,8 +205,7 @@ class _ButtonControlState extends State with FletStoreMixin { onLongPress: onLongPressHandler, onHover: onHoverHandler, clipBehavior: clipBehavior, - child: - widget.control.buildTextOrWidget("content") ?? const Text("")); + child: content ?? const Text("")); } else if (isOutlinedButton) { button = OutlinedButton( autofocus: autofocus, diff --git a/packages/flet/lib/src/controls/row.dart b/packages/flet/lib/src/controls/row.dart index 71f3f3241..fc0e3756b 100644 --- a/packages/flet/lib/src/controls/row.dart +++ b/packages/flet/lib/src/controls/row.dart @@ -18,12 +18,7 @@ class RowControl extends StatelessWidget { debugPrint("Row build: ${control.id}"); var spacing = control.getDouble("spacing", 10)!; - var mainAlignment = parseMainAxisAlignment( - control.getString("alignment"), MainAxisAlignment.start)!; - var tight = control.getBool("tight", false)!; var wrap = control.getBool("wrap", false)!; - var intrinsicHeight = control.getBool("intrinsic_height", false)!; - var verticalAlignment = control.getString("vertical_alignment"); var controls = control.buildWidgets("controls"); Widget child = wrap @@ -35,20 +30,23 @@ class RowControl extends StatelessWidget { control.getString("alignment"), WrapAlignment.start)!, runAlignment: parseWrapAlignment( control.getString("run_alignment"), WrapAlignment.start)!, - crossAxisAlignment: parseWrapCrossAlignment( - verticalAlignment, WrapCrossAlignment.center)!, + crossAxisAlignment: control.getWrapCrossAlignment( + "vertical_alignment", WrapCrossAlignment.center)!, children: controls, ) : Row( spacing: spacing, - mainAxisAlignment: mainAlignment, - mainAxisSize: tight ? MainAxisSize.min : MainAxisSize.max, - crossAxisAlignment: parseCrossAxisAlignment( - verticalAlignment, CrossAxisAlignment.center)!, + mainAxisSize: control.getBool("tight", false)! + ? MainAxisSize.min + : MainAxisSize.max, + mainAxisAlignment: control.getMainAxisAlignment( + "alignment", MainAxisAlignment.start)!, + crossAxisAlignment: control.getCrossAxisAlignment( + "vertical_alignment", CrossAxisAlignment.center)!, children: controls, ); - if (intrinsicHeight) { + if (control.getBool("intrinsic_height", false)!) { child = IntrinsicHeight(child: child); } diff --git a/packages/flet/lib/src/utils/buttons.dart b/packages/flet/lib/src/utils/buttons.dart index 2809e8e0e..456ef8423 100644 --- a/packages/flet/lib/src/utils/buttons.dart +++ b/packages/flet/lib/src/utils/buttons.dart @@ -29,6 +29,22 @@ ButtonStyle? parseButtonStyle(dynamic value, ThemeData theme, TextStyle? defaultTextStyle, ButtonStyle? defaultValue}) { if (value == null) return defaultValue; + + WidgetStateProperty? parseButtonTextStyle( + dynamic value, ThemeData theme, + {TextStyle? defaultTextStyle}) { + final ts = parseWidgetStateTextStyle(value, theme, + defaultTextStyle: defaultTextStyle); + if (ts == null) return null; + + // Match Material button defaults to avoid TextStyle.lerp assertions when + // animating between states. + return WidgetStateProperty.resolveWith((Set states) { + final resolved = ts.resolve(states); + return resolved?.copyWith(inherit: false); + }); + } + return ButtonStyle( foregroundColor: parseWidgetStateColor(value["color"], theme, defaultColor: defaultForegroundColor), @@ -54,7 +70,7 @@ ButtonStyle? parseButtonStyle(dynamic value, ThemeData theme, defaultColor: defaultForegroundColor), alignment: parseAlignment(value["alignment"]), enableFeedback: parseBool(value["enable_feedback"]), - textStyle: parseWidgetStateTextStyle(value["text_style"], theme, + textStyle: parseButtonTextStyle(value["text_style"], theme, defaultTextStyle: defaultTextStyle), iconSize: parseWidgetStateDouble(value["icon_size"]), visualDensity: parseVisualDensity(value["visual_density"]), diff --git a/packages/flet/lib/src/utils/text.dart b/packages/flet/lib/src/utils/text.dart index 9c21cdeaa..605eab1d6 100644 --- a/packages/flet/lib/src/utils/text.dart +++ b/packages/flet/lib/src/utils/text.dart @@ -140,20 +140,19 @@ TextAffinity? parseTextAffinity(String? value, [TextAffinity? defaultValue]) { defaultValue; } -TextStyle? parseTextStyle(dynamic value, ThemeData theme, - [TextStyle? defaultValue]) { - if (value == null) return defaultValue; - var fontWeight = value["weight"]; - - List? variations; - if (fontWeight != null && fontWeight.startsWith("w")) { - variations = [ - FontVariation('wght', parseDouble(fontWeight.substring(1), 0)!) - ]; +List? parseFontVariations(dynamic fontWeight, + [List? defaultValue]) { + if (fontWeight != null && + fontWeight is String && + fontWeight.startsWith("w")) { + return [FontVariation('wght', parseDouble(fontWeight.substring(1), 0)!)]; } + return defaultValue; +} +List parseTextDecorations(dynamic decorationValue) { List decorations = []; - var decor = parseInt(value["decoration"], 0)!; + var decor = parseInt(decorationValue, 0)!; if (decor & 0x1 > 0) { decorations.add(TextDecoration.underline); } @@ -163,6 +162,16 @@ TextStyle? parseTextStyle(dynamic value, ThemeData theme, if (decor & 0x4 > 0) { decorations.add(TextDecoration.lineThrough); } + return decorations; +} + +TextStyle? parseTextStyle(dynamic value, ThemeData theme, + [TextStyle? defaultValue]) { + if (value == null) return defaultValue; + + var fontWeight = value["weight"]; + List? variations = parseFontVariations(fontWeight); + List decorations = parseTextDecorations(value["decoration"]); return TextStyle( fontSize: parseDouble(value["size"]), diff --git a/sdk/python/examples/apps/timer/declarative.py b/sdk/python/examples/apps/timer/declarative.py new file mode 100644 index 000000000..b160e0dd9 --- /dev/null +++ b/sdk/python/examples/apps/timer/declarative.py @@ -0,0 +1,135 @@ +import asyncio +import time +from dataclasses import dataclass + +import flet as ft + + +def format_hhmmss(seconds: int) -> str: + """Format elapsed seconds as HH:MM:SS.""" + h = seconds // 3600 + m = (seconds % 3600) // 60 + s = seconds % 60 + return f"{h:02}:{m:02}:{s:02}" + + +@ft.observable +@dataclass +class TimerState: + """ + Declarative timer state. + + This class is the single source of truth for the UI. + Any mutation of its public fields automatically triggers a re-render. + """ + + running: bool = False + paused: bool = False + elapsed: int = 0 # seconds shown in UI + + # Internals + _base_elapsed: int = 0 # accumulated time before the current run + _started_at: float = 0.0 # wall-clock start time + _task: asyncio.Task | None = None # background ticker task + + async def _ticker(self): + """ + Background task that updates elapsed time once per second + while the timer is running. + """ + while self.running: + if not self.paused: + self.elapsed = self._base_elapsed + int(time.time() - self._started_at) + await asyncio.sleep(1) + + # Task cleanup when stopped + self._task = None + + def toggle(self): + """ + Toggle button handler: + - stopped → start + - running → pause + - paused → resume + """ + # stopped → start + if not self.running: + self.running = True + self.paused = False + self._base_elapsed = self.elapsed + self._started_at = time.time() + + # Ensure only one ticker task runs + if self._task is None or self._task.done(): + self._task = asyncio.create_task(self._ticker()) + return + + # running → pause + if not self.paused: + self._base_elapsed += int(time.time() - self._started_at) + self.elapsed = self._base_elapsed + self.paused = True + return + + # paused → resume + self.paused = False + self._started_at = time.time() + + def stop(self): + """Stop the timer and reset all state.""" + self.running = False + self.paused = False + self.elapsed = 0 + self._base_elapsed = 0 + self._started_at = 0.0 + + +@ft.component +def App(): + state, _ = ft.use_state(TimerState()) + + # Button appearance derived entirely from state + label = "Pause" if state.running and not state.paused else "Start" + icon = ft.Icons.PAUSE if state.running and not state.paused else ft.Icons.PLAY_ARROW + + return ft.SafeArea( + ft.Column( + spacing=20, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + alignment=ft.MainAxisAlignment.CENTER, + controls=[ + ft.Text( + format_hhmmss(state.elapsed), + size=30, + weight=ft.FontWeight.BOLD, + ), + ft.Row( + alignment=ft.MainAxisAlignment.CENTER, + controls=[ + ft.FilledButton( + label, + icon=icon, + on_click=state.toggle, + ), + ft.TextButton( + "Stop", + icon=ft.Icons.STOP, + on_click=state.stop, + disabled=not state.running and state.elapsed == 0, + ), + ], + ), + ], + ) + ) + + +def main(page: ft.Page): + """Application entry point.""" + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + page.render(App) + + +ft.run(main) diff --git a/sdk/python/examples/apps/timer/imperative.py b/sdk/python/examples/apps/timer/imperative.py new file mode 100644 index 000000000..46254fbdb --- /dev/null +++ b/sdk/python/examples/apps/timer/imperative.py @@ -0,0 +1,133 @@ +import asyncio +import time + +import flet as ft + + +def format_hhmmss(seconds: int) -> str: + """Convert elapsed seconds into HH:MM:SS string.""" + h = seconds // 3600 + m = (seconds % 3600) // 60 + s = seconds % 60 + return f"{h:02}:{m:02}:{s:02}" + + +def main(page: ft.Page): + page.vertical_alignment = ft.MainAxisAlignment.CENTER + page.horizontal_alignment = ft.CrossAxisAlignment.CENTER + + # State variables + running = False + paused = False + elapsed = 0 # seconds shown in the UI + base_elapsed = 0 # accumulated time before the current run + started_at = 0.0 # wall-clock start timestamp + + def sync_ui(): + """Synchronize UI controls with current state variables.""" + timer.value = format_hhmmss(elapsed) + + # Toggle button switches appearance based on state + if running and not paused: + toggle_btn.text = "Pause" + toggle_btn.icon = ft.Icons.PAUSE + else: + toggle_btn.text = "Start" + toggle_btn.icon = ft.Icons.PLAY_ARROW + + # Stop button only enabled when there is something to stop/reset + stop_btn.disabled = (not running) and (elapsed == 0) + + page.update() + + async def ticker(): + """ + Background task that updates elapsed time once per second + while the timer is running. + """ + nonlocal elapsed + while running: + if not paused: + elapsed = base_elapsed + int(time.time() - started_at) + timer.value = format_hhmmss(elapsed) + timer.update() + await asyncio.sleep(1) + + def handle_toggle(): + """ + Toggle button handler: + - stopped → start + - running → pause + - paused → resume + """ + nonlocal running, paused, elapsed, base_elapsed, started_at + + # stopped → start + if not running: + running = True + paused = False + base_elapsed = elapsed + started_at = time.time() + + # Start background ticker task + page.run_task(ticker) + sync_ui() + return + + # running → pause + if not paused: + base_elapsed += int(time.time() - started_at) + elapsed = base_elapsed + paused = True + sync_ui() + return + + # paused → resume + paused = False + started_at = time.time() + sync_ui() + + def handle_stop(): + """Stop the timer and reset it to 00:00:00.""" + nonlocal running, paused, elapsed, base_elapsed, started_at + running = False + paused = False + elapsed = 0 + base_elapsed = 0 + started_at = 0.0 + sync_ui() + + page.add( + ft.SafeArea( + ft.Column( + spacing=20, + horizontal_alignment=ft.CrossAxisAlignment.CENTER, + alignment=ft.MainAxisAlignment.CENTER, + controls=[ + timer := ft.Text("00:00:00", size=30, weight=ft.FontWeight.BOLD), + ft.Row( + alignment=ft.MainAxisAlignment.CENTER, + controls=[ + toggle_btn := ft.FilledButton( + "Start", + icon=ft.Icons.PLAY_ARROW, + on_click=handle_toggle, + ), + stop_btn := ft.TextButton( + "Stop", + icon=ft.Icons.STOP, + disabled=True, + on_click=handle_stop, + ), + ], + ), + ], + ) + ) + ) + + # Initial UI sync + sync_ui() + + +ft.run(main) diff --git a/sdk/python/packages/flet-lottie/src/flet_lottie/lottie.py b/sdk/python/packages/flet-lottie/src/flet_lottie/lottie.py index 5f365c7fe..4da8e4dec 100644 --- a/sdk/python/packages/flet-lottie/src/flet_lottie/lottie.py +++ b/sdk/python/packages/flet-lottie/src/flet_lottie/lottie.py @@ -9,7 +9,12 @@ class Lottie(ft.LayoutControl): """ Displays lottie animations. - """ + + Note: + - Layer effects are currently not supported. + See [airbnb/lottie-android#1964](https://github.com/airbnb/lottie-android/issues/1964) + and [xvrh/lottie-flutter#189](https://github.com/xvrh/lottie-flutter/issues/189) for details. + """ # noqa: E501 src: Union[str, bytes] """ diff --git a/sdk/python/packages/flet/docs/types/badge.md b/sdk/python/packages/flet/docs/types/badge.md index 323cc6c87..e393ff734 100644 --- a/sdk/python/packages/flet/docs/types/badge.md +++ b/sdk/python/packages/flet/docs/types/badge.md @@ -16,7 +16,7 @@ example_images: ../test-images/examples/material/golden/macos/badge --8<-- "{{ examples }}/basic.py" ``` +{{ image(example_images + "/basic.png", width="80%") }} -{{ image(example_images + "/badge-navigation-bar.png", alt="badge-navigation-bar", width="80%") }} {{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/badge/badge-navigation-bar.png b/sdk/python/packages/flet/integration_tests/examples/material/golden/macos/badge/basic.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/material/golden/macos/badge/badge-navigation-bar.png rename to sdk/python/packages/flet/integration_tests/examples/material/golden/macos/badge/basic.png diff --git a/sdk/python/packages/flet/integration_tests/examples/material/test_badge.py b/sdk/python/packages/flet/integration_tests/examples/material/test_badge.py index 0db0554ae..67a8ee52b 100644 --- a/sdk/python/packages/flet/integration_tests/examples/material/test_badge.py +++ b/sdk/python/packages/flet/integration_tests/examples/material/test_badge.py @@ -32,7 +32,7 @@ async def test_basic(flet_app_function: ftt.FletTestApp): flet_app_function.page.update() await flet_app_function.tester.pump_and_settle() flet_app_function.assert_screenshot( - "badge-navigation-bar", + "basic", await flet_app_function.page.take_screenshot( pixel_ratio=flet_app_function.screenshots_pixel_ratio ), diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index ae213addd..76297a95b 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -576,6 +576,7 @@ ) from flet.pubsub.pubsub_client import PubSubClient from flet.pubsub.pubsub_hub import PubSubHub +from flet.version import version as __version__ __all__ = [ "Accelerometer", @@ -1047,6 +1048,7 @@ "WindowEventType", "WindowResizeEdge", "WindowsDeviceInfo", + "__version__", "alignment", "app", "app_async", diff --git a/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py b/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py index 185822c91..895d7de1a 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py +++ b/sdk/python/packages/flet/src/flet/controls/core/responsive_row.py @@ -27,6 +27,7 @@ class ResponsiveRow(LayoutControl, AdaptiveControl): Similar to `expand` property, every control has [`col`][flet.Control.] property which allows specifying how many columns a control should span. + Example: ```python ft.ResponsiveRow( controls=[ @@ -40,10 +41,10 @@ class ResponsiveRow(LayoutControl, AdaptiveControl): }, ) for i in range(1, 6) - ], + ], ) ``` - + """ controls: list[Control] = field(default_factory=list) @@ -64,8 +65,13 @@ class ResponsiveRow(LayoutControl, AdaptiveControl): vertical_alignment: CrossAxisAlignment = CrossAxisAlignment.START """ - Defines how the child [`controls`][(c).] should be placed - vertically. + Defines how the child [`controls`][(c).] should be placed vertically. + + Note: + When [`wrap`][(c).] is `True`, this property doesn't support + [`CrossAxisAlignment.STRETCH`][flet.] or + [`CrossAxisAlignment.BASELINE`][flet.]. If either is used, + [`CrossAxisAlignment.START`][flet.] will be applied instead. """ spacing: ResponsiveNumber = 10 @@ -80,7 +86,7 @@ class ResponsiveRow(LayoutControl, AdaptiveControl): run_spacing: ResponsiveNumber = 10 """ - The spacing between runs when row content is wrapped on multiple lines. + The spacing between runs when [`wrap`][(c).] is `True`. """ breakpoints: dict[Union[ResponsiveRowBreakpoint, str], Number] = field( diff --git a/sdk/python/packages/flet/src/flet/controls/core/row.py b/sdk/python/packages/flet/src/flet/controls/core/row.py index 9717c7083..2d4d3b94b 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/row.py +++ b/sdk/python/packages/flet/src/flet/controls/core/row.py @@ -18,21 +18,24 @@ class Row(LayoutControl, ScrollableControl, AdaptiveControl): To cause a child control to expand and fill the available horizontal space, set its [`expand`][(c).] property. + Example: ```python - ft.Row( - controls=[ - ft.Card( - shape=ft.ContinuousRectangleBorder(radius=10), - content=ft.Container( - padding=5, - border_radius=ft.BorderRadius.all(5), - bgcolor=ft.Colors.AMBER_100, - content=ft.Text(f"Control {i}"), - ), - ) - for i in range(1, 6) - ], - ), + ( + ft.Row( + controls=[ + ft.Card( + shape=ft.ContinuousRectangleBorder(radius=10), + content=ft.Container( + padding=5, + border_radius=ft.BorderRadius.all(5), + bgcolor=ft.Colors.AMBER_100, + content=ft.Text(f"Control {i}"), + ), + ) + for i in range(1, 6) + ], + ), + ) ``` """ @@ -50,6 +53,12 @@ class Row(LayoutControl, ScrollableControl, AdaptiveControl): vertical_alignment: CrossAxisAlignment = CrossAxisAlignment.CENTER """ Defines how the child [`controls`][(c).] should be placed vertically. + + Note: + When [`wrap`][(c).] is `True`, this property doesn't support + [`CrossAxisAlignment.STRETCH`][flet.] or + [`CrossAxisAlignment.BASELINE`][flet.]. If either is used, + [`CrossAxisAlignment.CENTER`][flet.] will be applied instead. """ spacing: Number = 10 @@ -64,30 +73,33 @@ class Row(LayoutControl, ScrollableControl, AdaptiveControl): tight: bool = False """ - Specifies how much space should be occupied horizontally. + Whether this row should occupy all available horizontal space (`True`), + or only as much as needed by its children [`controls`][(c).] (`False`). - Defaults to `False`, meaning all space is allocated to children. + Note: + Has effect only when [`wrap`][(c).] is `False`. """ wrap: bool = False """ - When set to `True` the Row will put child controls into additional rows (runs) if - they don't fit a single row. + Whether this row should put child [`controls`][(c).] into additional rows (runs) if + they don't fit in a single row. """ run_spacing: Number = 10 """ - Spacing between runs when `wrap=True`. + The spacing between runs when [`wrap`][(c).] is `True`. """ run_alignment: MainAxisAlignment = MainAxisAlignment.START """ - How the runs should be placed in the cross-axis when `wrap=True`. + How the runs should be placed in the cross-axis when [`wrap`][(c).] is `True`. """ intrinsic_height: bool = False """ - If `True`, the Row will be as tall as the tallest child control. + Whether this row should be as tall as the tallest child control in + [`controls`][(c).]. """ def init(self): diff --git a/sdk/python/packages/flet/src/flet/messaging/protocol.py b/sdk/python/packages/flet/src/flet/messaging/protocol.py index 6889f6289..f2813aea6 100644 --- a/sdk/python/packages/flet/src/flet/messaging/protocol.py +++ b/sdk/python/packages/flet/src/flet/messaging/protocol.py @@ -73,8 +73,13 @@ def encode_object_for_msgpack(obj): elif isinstance(obj, Enum): return obj.value elif isinstance(obj, (datetime.datetime, datetime.date)): - if isinstance(obj, datetime.datetime) and obj.tzinfo is None: - obj = obj.astimezone() + if isinstance(obj, datetime.datetime): + if obj.tzinfo is None: + # interprete naive datetimes as UTC + obj = obj.replace(tzinfo=datetime.timezone.utc) + + # Normalize to UTC + obj = obj.astimezone(datetime.timezone.utc) return msgpack.ExtType(1, obj.isoformat().encode("utf-8")) elif isinstance(obj, datetime.time): return msgpack.ExtType(2, obj.strftime("%H:%M").encode("utf-8")) diff --git a/sdk/python/packages/flet/src/flet/utils/pip.py b/sdk/python/packages/flet/src/flet/utils/pip.py index d41a49415..8ccc4f200 100644 --- a/sdk/python/packages/flet/src/flet/utils/pip.py +++ b/sdk/python/packages/flet/src/flet/utils/pip.py @@ -21,7 +21,9 @@ def install_flet_package(name: str): print("OK") else: print( - f'Unable to upgrade "{name}" package to version {flet.version.version}. Please use "pip install \'flet[all]=={flet.version.version}\' --upgrade" command to upgrade Flet.' + f'Unable to upgrade "{name}" package to version {flet.version.version}. ' + f"Please use \"pip install 'flet[all]=={flet.version.version}' --upgrade\" " + f"command to upgrade Flet." ) exit(1) @@ -30,14 +32,12 @@ def ensure_flet_desktop_package_installed(): try: import flet_desktop.version - import flet.version - if ( flet_desktop.version.version and flet_desktop.version.version != flet.version.version ): raise RuntimeError("flet-desktop version mismatch") - except: + except Exception: install_flet_package("flet-desktop") @@ -45,20 +45,17 @@ def ensure_flet_web_package_installed(): try: import flet_web.version - import flet.version - if ( flet_web.version.version and flet_web.version.version != flet.version.version ): raise RuntimeError("flet-web version mismatch") - except: + except Exception: install_flet_package("flet-web") def ensure_flet_cli_package_installed(): try: - import flet.version import flet_cli.version if ( @@ -66,5 +63,5 @@ def ensure_flet_cli_package_installed(): and flet_cli.version.version != flet.version.version ): raise RuntimeError("flet-cli version mismatch") - except: + except Exception: install_flet_package("flet-cli") diff --git a/sdk/python/packages/flet/src/flet/version.py b/sdk/python/packages/flet/src/flet/version.py index 1796700f5..502655aaa 100644 --- a/sdk/python/packages/flet/src/flet/version.py +++ b/sdk/python/packages/flet/src/flet/version.py @@ -4,8 +4,8 @@ import subprocess as sp from pathlib import Path -import flet -from flet.utils import is_mobile, is_windows, which +from flet.utils.files import which +from flet.utils.platform_utils import is_mobile, is_windows DEFAULT_VERSION = "0.1.0" @@ -17,7 +17,7 @@ def from_git(): """Try to get the version from Git tags.""" working = Path().absolute() try: - version_file_path = Path(flet.__file__).absolute().parent / "version.py" + version_file_path = Path(__file__).resolve() repo_root = find_repo_root(version_file_path.parent) if repo_root: diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 07880750d..6b012ebe5 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -110,6 +110,8 @@ pydocstyle = { convention = 'google' } isort = { known-first-party = [ "flet", "flet_cli", + "flet_desktop", + "flet_web", "flet_ads", "flet_audio", "flet_audio_recorder",