diff --git a/sdk/python/packages/flet/src/flet/__init__.py b/sdk/python/packages/flet/src/flet/__init__.py index c105a1d9c..0557be31b 100644 --- a/sdk/python/packages/flet/src/flet/__init__.py +++ b/sdk/python/packages/flet/src/flet/__init__.py @@ -1,4 +1,4 @@ -from flet.app import app, app_async +from flet.app import run, app, run_async, app_async from flet.core.adaptive_control import AdaptiveControl from flet.core.alert_dialog import AlertDialog from flet.core.alignment import Alignment, Axis diff --git a/sdk/python/packages/flet/src/flet/app.py b/sdk/python/packages/flet/src/flet/app.py index 810a571f3..b97bfb13a 100644 --- a/sdk/python/packages/flet/src/flet/app.py +++ b/sdk/python/packages/flet/src/flet/app.py @@ -5,7 +5,7 @@ import signal import traceback from pathlib import Path -from typing import Optional +from typing import Optional, Callable, Union import flet.version from flet.core.event import Event @@ -19,6 +19,7 @@ is_linux_server, is_pyodide, open_in_browser, + deprecated ) from flet.utils.pip import ( ensure_flet_desktop_package_installed, @@ -30,22 +31,21 @@ logger = logging.getLogger(flet.__name__) -def app( - target, - name="", +def run( + target: Callable[[Page], None], + name: str = "", host=None, - port=0, + port: int = 0, view: Optional[AppView] = AppView.FLET_APP, - assets_dir="assets", - upload_dir=None, + assets_dir: Union[str, os.PathLike] = "assets", + upload_dir: Optional[Union[str, os.PathLike]] = None, web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji=False, - route_url_strategy="path", - export_asgi_app=False, + use_color_emoji: bool = False, + route_url_strategy: str = "path", + export_asgi_app: bool = False, ): if is_pyodide(): - __run_pyodide(target) - return + return __run_pyodide(target) if export_asgi_app: ensure_flet_web_package_installed() @@ -62,7 +62,7 @@ def app( ) return asyncio.run( - app_async( + run_async( target=target, name=name, host=host, @@ -77,17 +77,50 @@ def app( ) -async def app_async( - target, - name="", +@deprecated( + reason="Use flet.run() instead.", + version="0.28.1", + delete_version="0.31", +) +def app( + target: Callable[[Page], None], + name: str = "", host=None, - port=0, + port: int = 0, view: Optional[AppView] = AppView.FLET_APP, - assets_dir="assets", - upload_dir=None, + assets_dir: Union[str, os.PathLike] = "assets", + upload_dir: Optional[Union[str, os.PathLike]] = None, web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, - use_color_emoji=False, - route_url_strategy="path", + use_color_emoji: bool = False, + route_url_strategy: str = "path", + export_asgi_app: bool = False, +): + return run( + target=target, + name=name, + host=host, + port=port, + view=view, + assets_dir=assets_dir, + upload_dir=upload_dir, + web_renderer=web_renderer, + use_color_emoji=use_color_emoji, + route_url_strategy=route_url_strategy, + export_asgi_app=export_asgi_app + ) + + +async def run_async( + target: Callable[[Page], None], + name: str = "", + host=None, + port: int = 0, + view: Optional[AppView] = AppView.FLET_APP, + assets_dir: Union[str, os.PathLike] = "assets", + upload_dir: Optional[Union[str, os.PathLike]] = None, + web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, + use_color_emoji: bool = False, + route_url_strategy: str = "path", ): if is_pyodide(): __run_pyodide(target) @@ -104,14 +137,14 @@ async def app_async( view = AppView.WEB_BROWSER env_port = os.getenv("FLET_SERVER_PORT") - if env_port is not None and env_port: + if env_port: port = int(env_port) if port == 0 and force_web_server: port = 8000 env_host = os.getenv("FLET_SERVER_IP") - if env_host is not None and env_host: + if env_host: host = env_host assets_dir = __get_assets_dir_path(assets_dir) @@ -119,7 +152,7 @@ async def app_async( page_name = __get_page_name(name) is_socket_server = ( - is_embedded() or view == AppView.FLET_APP or view == AppView.FLET_APP_HIDDEN + is_embedded() or view in [AppView.FLET_APP, AppView.FLET_APP_HIDDEN] ) and not force_web_server url_prefix = os.getenv("FLET_DISPLAY_URL_PREFIX") @@ -172,11 +205,7 @@ def exit_gracefully(signum, frame): try: if ( - ( - view == AppView.FLET_APP - or view == AppView.FLET_APP_HIDDEN - or view == AppView.FLET_APP_WEB - ) + view in [AppView.FLET_APP, AppView.FLET_APP_HIDDEN, AppView.FLET_APP_WEB] and not force_web_server and not is_embedded() and url_prefix is None @@ -210,7 +239,42 @@ def exit_gracefully(signum, frame): await conn.close() -async def __run_socket_server(port=0, session_handler=None, blocking=False): +@deprecated( + reason="Use flet.run_async() instead.", + version="0.28.1", + delete_version="0.31", +) +async def app_async( + target: Callable[[Page], None], + name: str = "", + host=None, + port: int = 0, + view: Optional[AppView] = AppView.FLET_APP, + assets_dir: Union[str, os.PathLike] = "assets", + upload_dir: Optional[Union[str, os.PathLike]] = None, + web_renderer: WebRenderer = WebRenderer.CANVAS_KIT, + use_color_emoji: bool = False, + route_url_strategy: str = "path", +): + return run_async( + target=target, + name=name, + host=host, + port=port, + view=view, + assets_dir=assets_dir, + upload_dir=upload_dir, + web_renderer=web_renderer, + use_color_emoji=use_color_emoji, + route_url_strategy=route_url_strategy + ) + + +async def __run_socket_server( + port: int = 0, + session_handler=None, + blocking: bool = False, +): from flet.flet_socket_server import FletSocketServer uds_path = os.getenv("FLET_SERVER_UDS_PATH") @@ -312,7 +376,7 @@ async def __run_web_server( ) -def __run_pyodide(target): +def __run_pyodide(target: Callable[[Page], None]) -> None: import flet_js from flet.pyodide_connection import PyodideConnection @@ -354,7 +418,7 @@ def __get_page_name(name: str): return env_page_name if not name and env_page_name else name -def __get_assets_dir_path(assets_dir: Optional[str], relative_to_cwd=False): +def __get_assets_dir_path(assets_dir: Optional[str], relative_to_cwd: bool = False) -> Optional[str]: if assets_dir: if not Path(assets_dir).is_absolute(): if "_MEI" in __file__: @@ -371,15 +435,13 @@ def __get_assets_dir_path(assets_dir: Optional[str], relative_to_cwd=False): logger.info(f"Assets path configured: {assets_dir}") env_assets_dir = os.getenv("FLET_ASSETS_DIR") - if env_assets_dir: - assets_dir = env_assets_dir - return assets_dir + return env_assets_dir or assets_dir -def __get_upload_dir_path(upload_dir: Optional[str], relative_to_cwd=False): +def __get_upload_dir_path(upload_dir: Optional[str], relative_to_cwd: bool = False) -> Optional[str]: if upload_dir: if not Path(upload_dir).is_absolute(): - upload_dir = str( + return str( Path(os.getcwd() if relative_to_cwd else get_current_script_dir()) .joinpath(upload_dir) .resolve() diff --git a/sdk/python/packages/flet/src/flet/core/segmented_button.py b/sdk/python/packages/flet/src/flet/core/segmented_button.py index dc6b97e69..6b20a6055 100644 --- a/sdk/python/packages/flet/src/flet/core/segmented_button.py +++ b/sdk/python/packages/flet/src/flet/core/segmented_button.py @@ -17,6 +17,7 @@ RotateValue, ScaleValue, ) +from flet.utils import warn_deprecated class Segment(Control): @@ -116,13 +117,18 @@ class SegmentedButton(ConstrainedControl): Online docs: https://flet.dev/docs/controls/segmentedbutton """ + def __check_selected_type_is_deprecated(self, selected: Optional[Union[List[str], Set]]) -> list: + if isinstance(selected, set): + warn_deprecated("Using set as type of selected param", "0.28.1", "0.31", "Use list instead.") + return list(selected) if selected is not None else [] + def __init__( self, segments: List[Segment], style: Optional[ButtonStyle] = None, allow_empty_selection: Optional[bool] = None, allow_multiple_selection: Optional[bool] = None, - selected: Optional[Set] = None, + selected: Optional[Union[List[str], Set]] = None, selected_icon: Optional[Control] = None, show_selected_icon: Optional[bool] = None, direction: Optional[Axis] = None, @@ -195,7 +201,8 @@ def __init__( self.allow_multiple_selection = allow_multiple_selection self.allow_empty_selection = allow_empty_selection self.selected_icon = selected_icon - self.selected = selected + + self.selected = self.__check_selected_type_is_deprecated(selected) self.style = style self.direction = direction self.padding = padding @@ -292,16 +299,17 @@ def allow_multiple_selection(self, value: Optional[bool]): # selected @property - def selected(self) -> Optional[Set]: + def selected(self) -> List[str]: s = self._get_attr("selected") - return set(json.loads(s)) if s else s + return list(json.loads(s)) if s else [] @selected.setter - def selected(self, value: Optional[Set]): + def selected(self, value: Optional[Union[List[str], Set]]): + value = self.__check_selected_type_is_deprecated(value) self._set_attr( "selected", ( - json.dumps(list(value), separators=(",", ":")) + json.dumps(value, separators=(",", ":")) if value is not None else None ), diff --git a/sdk/python/packages/flet/src/flet/utils/__init__.py b/sdk/python/packages/flet/src/flet/utils/__init__.py index ce8d09f0d..ccb20622f 100644 --- a/sdk/python/packages/flet/src/flet/utils/__init__.py +++ b/sdk/python/packages/flet/src/flet/utils/__init__.py @@ -1,6 +1,6 @@ from flet.utils.browser import open_in_browser from flet.utils.classproperty import classproperty -from flet.utils.deprecated import deprecated +from flet.utils.deprecated import warn_deprecated, deprecated from flet.utils.files import ( cleanup_path, copy_tree, diff --git a/sdk/python/packages/flet/src/flet/utils/deprecated.py b/sdk/python/packages/flet/src/flet/utils/deprecated.py index 4734dcb01..175835a0c 100644 --- a/sdk/python/packages/flet/src/flet/utils/deprecated.py +++ b/sdk/python/packages/flet/src/flet/utils/deprecated.py @@ -1,26 +1,50 @@ import functools import warnings -from typing import Optional +from typing import TypeVar, Any, Type, Optional, Callable +def warn_deprecated( + name: str, + version: str, + delete_version: Optional[str] = None, + reason: Optional[str] = None +) -> None: + delete_version_message = f" and will be removed in version {delete_version}" if delete_version else "" + reason_message = " " + reason if reason else "" + msg = f"{name} is deprecated since version {version}{delete_version_message}.{reason_message}" -def deprecated(reason: str, version: str, delete_version: str, is_method=True): + warnings.warn( + msg, + category=DeprecationWarning, + stacklevel=2, + ) + +F = TypeVar('F', bound=Callable[..., Any]) +T = TypeVar('T') + +def deprecated( + reason: str, + version: str, + delete_version: Optional[str] = None, + is_method: bool = True, +) -> Callable[[F], F]: """ A decorator function that marks a function/method/property/event as deprecated. :param reason: The reason for deprecation. :param version: The version from which the function was deprecated. :param delete_version: The version in which the function will be removed from the API. - :param is_method: if the deprecated item is a method (True) or property/function/event (False) + :param is_method: if the deprecated item is a method (True) or property/function/event (False). + :return: A decorator that wraps the function/method/property/event and emits a deprecation warning. """ - def decorator(func): + def decorator(func: F): @functools.wraps(func) def new_func(*args, **kwargs): - warnings.warn( - f"{func.__name__}{'()' if is_method else ''} is deprecated since version {version} " - f"and will be removed in version {delete_version}. {reason}", - category=DeprecationWarning, - stacklevel=2, + warn_deprecated( + name=f"{func.__name__}{'()' if is_method else ''}", + version=version, + delete_version=delete_version, + reason=reason ) return func(*args, **kwargs) @@ -29,16 +53,31 @@ def new_func(*args, **kwargs): return decorator -def deprecated_class(reason: str, version: str, delete_version: str): - def decorator(cls): - msg = f"{cls.__name__} is deprecated since version {version} and will be removed in version {delete_version}. {reason}" +def deprecated_class( + reason: str, + version: str, + delete_version: Optional[str] = None, +) -> Callable[[Type[T]], Type[T]]: + """ + A decorator that marks a class as deprecated. + :param reason: The reason for deprecation. + :param version: The version from which the class was deprecated. + :param delete_version: The version in which the class will be removed. + :return: A decorator that wraps the class and emits a deprecation warning. + """ + def decorator(cls): # Wrap the original __init__ method orig_init = cls.__init__ @functools.wraps(orig_init) def new_init(self, *args, **kwargs): - warnings.warn(msg, category=DeprecationWarning, stacklevel=2) + warn_deprecated( + name=cls.__name__, + version=version, + delete_version=delete_version, + reason=reason + ) orig_init(self, *args, **kwargs) cls.__init__ = new_init @@ -48,11 +87,14 @@ def new_init(self, *args, **kwargs): def deprecated_property( - name: str, reason: str, version: str, delete_version: Optional[str] = None -): - warnings.warn( - f"{name} property is deprecated since version {version}" - f"{' and will be removed in version ' + delete_version if delete_version else ''}. {reason}", - category=DeprecationWarning, - stacklevel=2, + name: str, + reason: str, + version: str, + delete_version: Optional[str] = None, +) -> None: + warn_deprecated( + name=f'{name} property', + version=version, + delete_version=delete_version, + reason=reason ) diff --git a/sdk/python/packages/flet/src/flet/utils/strings.py b/sdk/python/packages/flet/src/flet/utils/strings.py index e5ee2d55d..1b6b30747 100644 --- a/sdk/python/packages/flet/src/flet/utils/strings.py +++ b/sdk/python/packages/flet/src/flet/utils/strings.py @@ -2,6 +2,6 @@ import string -def random_string(length): +def random_string(length: int) -> str: alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(length))