Skip to content

API Reference

This comprehensive API reference covers all classes, methods, and functions available in Flet-Easy. Use this as your complete guide for building applications with Flet-Easy.

Core Classes

FletEasy

flet_easy.FletEasy

Main application class for Flet-Easy.

Configure the app with:

  • route_prefix : Route prefix different from /.
  • route_init : Initial route, default is /.
  • route_login : Route for redirect when using protected routes.
  • on_keyboard : Enable on_keyboard event (default: False).
  • on_resize : Enable on_resize event (default: False).
  • secret_key : Configure JWT or client storage with SecretKey.
  • auto_logout : Auto-logout with JWT expiry (default: False).
  • path_views : Folder path for auto-discovered page files.
  • logger : Enable logger (default: False).

Example:

import flet as ft
import flet_easy as fs

app = fs.FletEasy(
    route_prefix="/FletEasy",
    route_init="/FletEasy/home",
)


@app.page("/home", title="Home", page_clear=True)
async def index_page(data: fs.Datasy):
    return ft.View(
        data.route_init,
        controls=[ft.Text("Home", size=40)],
    )


app.run()

Source code in src/flet_easy/core/app.py
class FletEasy:
    """Main application class for Flet-Easy.

    Configure the app with:

    * `route_prefix` : Route prefix different from `/`.
    * `route_init` : Initial route, default is `/`.
    * `route_login` : Route for redirect when using protected routes.
    * `on_keyboard` : Enable on_keyboard event (default: False).
    * `on_resize` : Enable on_resize event (default: False).
    * `secret_key` : Configure JWT or client storage with `SecretKey`.
    * `auto_logout` : Auto-logout with JWT expiry (default: False).
    * `path_views` : Folder path for auto-discovered page files.
    * `logger` : Enable logger (default: False).

    Example:
    ```python
    import flet as ft
    import flet_easy as fs

    app = fs.FletEasy(
        route_prefix="/FletEasy",
        route_init="/FletEasy/home",
    )


    @app.page("/home", title="Home", page_clear=True)
    async def index_page(data: fs.Datasy):
        return ft.View(
            data.route_init,
            controls=[ft.Text("Home", size=40)],
        )


    app.run()
    ```
    """

    __self = None

    # ─── Initialization ───────────────────────────────────────────────

    def __init__(
        self,
        route_prefix: Optional[str] = None,
        route_init: str = "/",
        route_login: Optional[str] = None,
        on_resize: bool = False,
        on_keyboard: bool = False,
        secret_key: Optional[SecretKey] = None,
        auto_logout: bool = False,
        path_views: Optional[Path] = None,
        logger: bool = False,
    ) -> None:
        if logger:
            LoggingFletEasy.enable(logging.DEBUG)
        self._logger = get_logger("FletEasy")

        self.__route_prefix: str = route_prefix or ""
        self.__route_init: str = route_init
        self.__route_login: Optional[str] = route_login
        self.__path_views: Optional[Path] = path_views
        self.__on_resize: bool = on_resize
        self.__on_keyboard: bool = on_keyboard
        self.__secret_key: Optional[SecretKey] = secret_key
        self.__auto_logout: bool = auto_logout

        self.__page_404: Optional[Pagesy] = None
        self.__middlewares: Optional[List[Union[MiddlewareHandler, MiddlewareRequest]]] = None
        self.__middlewares_after: Optional[List[Union[MiddlewareHandler, MiddlewareRequest]]] = None

        self.__pages: deque[Pagesy] = deque()
        self.__view_data: Optional[Callable[[Datasy], Viewsy]] = None
        self.__config_login: Optional[Callable[[Datasy], bool]] = None
        self.__view_config: Optional[Callable[[Datasy], None]] = None
        self.__config_event: Optional[Callable[[Datasy], None]] = None

        FletEasy.__self = self
        self.__pagesys = automatic_routing(self.__path_views) if self.__path_views else None

    # ─── App Lifecycle ────────────────────────────────────────────────

    def __pre_config(self, page: Page) -> None:
        """Configure and start the routing engine."""

        # Add automatic routing pages before initializing FletEasyX
        # so they are included in the exact_routes_map
        if self.__pagesys:
            self.add_pages(self.__pagesys)
            self.__pagesys = None

        fsx = FletEasyX(
            page=page,
            route_prefix=self.__route_prefix,
            route_init=self.__route_init,
            route_login=self.__route_login,
            page_404=self.__page_404,
            pages=self.__pages,
            view_data=self.__view_data,
            config_login=self.__config_login,
            view_config=self.__view_config,
            config_event=self.__config_event,
            middlewares=self.__middlewares,
            middlewares_after=self.__middlewares_after,
            on_resize=self.__on_resize,
            on_keyboard=self.__on_keyboard,
            secret_key=self.__secret_key,
            auto_logout=self.__auto_logout,
        )

        fsx.run()

    def start(self, page: Page) -> None:
        """Start the app in the main function."""
        self._logger.debug("enabling connection with flet app")
        self.__pre_config(page)

    def get_app(self) -> Callable[[Page], None]:
        """Return the app function main."""
        self._logger.debug("getting app from fletEasy to export")

        def main(page: Page) -> None:
            self.__pre_config(page)

        return main

    def run(
        self,
        name: str = "",
        host: Optional[str] = None,
        port: int = 0,
        view: Optional[AppView] = AppView.FLET_APP,
        assets_dir: str = "assets",
        upload_dir: Optional[str] = None,
        web_renderer: WebRenderer = WebRenderer.CANVAS_KIT,
        route_url_strategy: str = "path",
        export_asgi_app: bool = False,
        fastapi: bool = False,
        **kwargs,
    ) -> None:
        """Execute the app. Supports async, fastapi, and export_asgi_app."""
        main = self.get_app()

        if fastapi:
            warn(
                "Avoid using the 'fastapi' parameter in the 'run()' method, "
                "instead use the 'get_app()' method.",
                category=DeprecationWarning,
                stacklevel=2,
            )
            return main

        try:
            self._logger.debug("running app from fletEasy with flet")
            return app(
                target=main,
                name=name,
                host=host,
                port=port,
                view=view,
                assets_dir=assets_dir,
                upload_dir=upload_dir,
                web_renderer=web_renderer,
                route_url_strategy=route_url_strategy,
                export_asgi_app=export_asgi_app,
                **kwargs,
            )
        except RuntimeError:
            raise FletEasyError(
                "If you are using fastapi from flet, set the 'fastapi = True' "
                "parameter of the run() method."
            )

    # ─── Route Registration ───────────────────────────────────────────

    def _build_route(self, route: Optional[str]) -> Optional[str]:
        """Build the full route by prepending the prefix."""
        if not self.__route_prefix or not route:
            return route
        return self.__route_prefix if route == "/" else self.__route_prefix + route

    @classmethod
    def page(
        cls,
        route: str,
        title: Optional[str] = None,
        index: Optional[int] = None,
        page_clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[Dict[str, Any]] = None,
        middleware: Optional[
            Union[
                List[Union[MiddlewareHandler, MiddlewareRequest]],
                MiddlewareHandler,
                MiddlewareRequest,
            ]
        ] = None,
        cache: bool = False,
    ) -> Callable:
        """Decorator to add a new page to the app.

        Args:
            route: URL path, e.g. `'/home'`.
            title: Page title (optional).
            index: Page index for NavigationBar (optional).
            page_clear: Remove previous views (optional).
            share_data: Share data between pages (optional).
            protected_route: Protect with login (optional).
            custom_params: Custom URL parameter validators (optional).
            middleware: Page-level middleware (optional).
            cache: Preserve page state when navigating (optional).
        """
        self = cls.__self

        def decorator(func: Callable) -> Callable:
            pagesy = Pagesy(
                route=self._build_route(route),
                view=func,
                title=title,
                index=index,
                clear=page_clear,
                share_data=share_data,
                protected_route=protected_route,
                custom_params=custom_params,
                middleware=middleware,
                cache=cache,
            )
            self.__pages.append(pagesy)

            # Back-reference for reverse decorator order (@ft.component on top)
            func.__flet_easy_pagesy__ = pagesy

            self._logger.debug(f"Adding page: {self.__pages[-1]}")
            return func

        return decorator

    def page_404(
        self,
        route: Optional[str] = None,
        title: Optional[str] = None,
        page_clear: bool = False,
    ) -> Callable:
        """Decorator to add a custom 404 page.

        Args:
            route: URL path for the 404 page (optional).
            title: Page title (default: 'Flet-Easy 404').
            page_clear: Remove previous views (optional).
        """

        def decorator(func: Callable) -> Callable:
            self.__page_404 = Pagesy(
                self._build_route(route),
                func,
                title or "Flet-Easy 404",
                clear=page_clear,
            )
            self._logger.debug(f"Adding page 404: {self.__page_404}")
            return func

        return decorator

    def add_pages(self, group_pages: Union[List[AddPagesy], AddPagesy]) -> None:
        """Add pages from other files.

        Example:
        ```python
        app.add_pages([index, test, contador, login, task])
        ```
        """
        group_pages = group_pages if isinstance(group_pages, list) else [group_pages]

        try:
            for page in group_pages:
                if self.__route_prefix:
                    self.__pages.extend(page._add_pages(self.__route_prefix))
                else:
                    self.__pages.extend(page._add_pages())

                self._logger.debug(f"Adding group of pages: {len(group_pages)}: {page}\n")
        except Exception as e:
            raise AddPagesError("Add pages error in route: ", e)

    def add_routes(self, add_views: List[Pagesy]) -> None:
        """Add routes without the use of decorators.

        Example:
        ```python
        app.add_routes(
            add_views=[
                fs.Pagesy("/hi", index_page, True),
                fs.Pagesy("/counter", counter_page),
            ]
        )
        ```
        """
        if not add_views:
            raise ConfigurationError("add view (add_view) in 'add_routes'.")

        for page in add_views:
            if self.__route_prefix:
                page.route = self.__route_prefix + page.route

            self.__pages.append(page)
            self._logger.debug(f"Add routes: {page}")

    # ─── Configuration Decorators ─────────────────────────────────────

    def view(self, func: Callable[[Datasy], Viewsy]) -> None:
        """Decorator to add custom view controls (appbar, navigation, etc).

        The decorated function receives `data:fs.Datasy` and returns `fs.Viewsy`.
        """
        self.__view_data = func
        self._logger.debug(f"Adding view: {self.__view_data}")

    def config(self, func: Callable[[Datasy], None]) -> None:
        """Decorator to add custom page configuration (theme, etc).

        The decorated function receives `page:ft.Page` and returns nothing.
        """
        self.__view_config = func
        self._logger.debug(f"Adding config: {self.__view_config}")

    def login(self, func: Callable[[Datasy], bool]) -> None:
        """Decorator to add login configuration for protected routes.

        The decorated function receives `data:fs.Datasy` and must return a boolean.
        """
        self.__config_login = func
        self._logger.debug(f"Adding login: {self.__config_login}")

    def config_event_handler(self, func: Callable[[Datasy], None]) -> None:
        """Decorator to add page event handler settings.

        See: https://flet.dev/docs/controls/page#events
        """
        self.__config_event = func
        self._logger.debug(f"Adding config event: {self.__config_event}")

    # ─── Middleware ────────────────────────────────────────────────────

    def add_middleware(
        self,
        *middleware: Union[
            Tuple[Union[MiddlewareHandler, MiddlewareRequest]],
            MiddlewareHandler,
            MiddlewareRequest,
        ],
    ) -> None:
        """Add global middleware.

        Accepts classes inheriting from `fs.MiddlewareRequest` or functions
        that receive `data:fs.Datasy`. Can be added individually or as a list.

        **More info:** https://daxexs.github.io/flet-easy/latest/middleware/#general-application
        """
        if not middleware:
            raise MiddlewareError(
                "No middleware provided to 'add_middleware'. "
                "Pass at least one middleware function or MiddlewareRequest class."
            )

        items = middleware[0] if isinstance(middleware[0], list) else middleware

        self.__middlewares = []
        self.__middlewares_after = []

        for m in items:
            if isinstance(m, FunctionType):
                self.__middlewares.append(m)
            elif isinstance(m, type) and issubclass(m, MiddlewareRequest):
                self.__middlewares.append(m)
                self.__middlewares_after.append(m)
            else:
                raise MiddlewareError(
                    f"Middleware '{m}' must be a class inheriting from "
                    f"MiddlewareRequest or a callable function."
                )

        self._logger.debug(f"Add middlewares in method 'add_middleware': {items}")

__init__(route_prefix=None, route_init='/', route_login=None, on_resize=False, on_keyboard=False, secret_key=None, auto_logout=False, path_views=None, logger=False)

Source code in src/flet_easy/core/app.py
def __init__(
    self,
    route_prefix: Optional[str] = None,
    route_init: str = "/",
    route_login: Optional[str] = None,
    on_resize: bool = False,
    on_keyboard: bool = False,
    secret_key: Optional[SecretKey] = None,
    auto_logout: bool = False,
    path_views: Optional[Path] = None,
    logger: bool = False,
) -> None:
    if logger:
        LoggingFletEasy.enable(logging.DEBUG)
    self._logger = get_logger("FletEasy")

    self.__route_prefix: str = route_prefix or ""
    self.__route_init: str = route_init
    self.__route_login: Optional[str] = route_login
    self.__path_views: Optional[Path] = path_views
    self.__on_resize: bool = on_resize
    self.__on_keyboard: bool = on_keyboard
    self.__secret_key: Optional[SecretKey] = secret_key
    self.__auto_logout: bool = auto_logout

    self.__page_404: Optional[Pagesy] = None
    self.__middlewares: Optional[List[Union[MiddlewareHandler, MiddlewareRequest]]] = None
    self.__middlewares_after: Optional[List[Union[MiddlewareHandler, MiddlewareRequest]]] = None

    self.__pages: deque[Pagesy] = deque()
    self.__view_data: Optional[Callable[[Datasy], Viewsy]] = None
    self.__config_login: Optional[Callable[[Datasy], bool]] = None
    self.__view_config: Optional[Callable[[Datasy], None]] = None
    self.__config_event: Optional[Callable[[Datasy], None]] = None

    FletEasy.__self = self
    self.__pagesys = automatic_routing(self.__path_views) if self.__path_views else None

page(route, title=None, index=None, page_clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)classmethod

Decorator to add a new page to the app.

Parameters:

NameTypeDescriptionDefault
routestr

URL path, e.g. '/home'.

required
titleOptional[str]

Page title (optional).

None
indexOptional[int]

Page index for NavigationBar (optional).

None
page_clearbool

Remove previous views (optional).

False
share_databool

Share data between pages (optional).

False
protected_routebool

Protect with login (optional).

False
custom_paramsOptional[Dict[str, Any]]

Custom URL parameter validators (optional).

None
middlewareOptional[Union[List[Union[MiddlewareHandler, MiddlewareRequest]], MiddlewareHandler, MiddlewareRequest]]

Page-level middleware (optional).

None
cachebool

Preserve page state when navigating (optional).

False
Source code in src/flet_easy/core/app.py
@classmethod
def page(
    cls,
    route: str,
    title: Optional[str] = None,
    index: Optional[int] = None,
    page_clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[Dict[str, Any]] = None,
    middleware: Optional[
        Union[
            List[Union[MiddlewareHandler, MiddlewareRequest]],
            MiddlewareHandler,
            MiddlewareRequest,
        ]
    ] = None,
    cache: bool = False,
) -> Callable:
    """Decorator to add a new page to the app.

    Args:
        route: URL path, e.g. `'/home'`.
        title: Page title (optional).
        index: Page index for NavigationBar (optional).
        page_clear: Remove previous views (optional).
        share_data: Share data between pages (optional).
        protected_route: Protect with login (optional).
        custom_params: Custom URL parameter validators (optional).
        middleware: Page-level middleware (optional).
        cache: Preserve page state when navigating (optional).
    """
    self = cls.__self

    def decorator(func: Callable) -> Callable:
        pagesy = Pagesy(
            route=self._build_route(route),
            view=func,
            title=title,
            index=index,
            clear=page_clear,
            share_data=share_data,
            protected_route=protected_route,
            custom_params=custom_params,
            middleware=middleware,
            cache=cache,
        )
        self.__pages.append(pagesy)

        # Back-reference for reverse decorator order (@ft.component on top)
        func.__flet_easy_pagesy__ = pagesy

        self._logger.debug(f"Adding page: {self.__pages[-1]}")
        return func

    return decorator

view(func)

Decorator to add custom view controls (appbar, navigation, etc).

The decorated function receives data:fs.Datasy and returns fs.Viewsy.

Source code in src/flet_easy/core/app.py
def view(self, func: Callable[[Datasy], Viewsy]) -> None:
    """Decorator to add custom view controls (appbar, navigation, etc).

    The decorated function receives `data:fs.Datasy` and returns `fs.Viewsy`.
    """
    self.__view_data = func
    self._logger.debug(f"Adding view: {self.__view_data}")

login(func)

Decorator to add login configuration for protected routes.

The decorated function receives data:fs.Datasy and must return a boolean.

Source code in src/flet_easy/core/app.py
def login(self, func: Callable[[Datasy], bool]) -> None:
    """Decorator to add login configuration for protected routes.

    The decorated function receives `data:fs.Datasy` and must return a boolean.
    """
    self.__config_login = func
    self._logger.debug(f"Adding login: {self.__config_login}")

config_event_handler(func)

Decorator to add page event handler settings.

See: https://flet.dev/docs/controls/page#events

Source code in src/flet_easy/core/app.py
def config_event_handler(self, func: Callable[[Datasy], None]) -> None:
    """Decorator to add page event handler settings.

    See: https://flet.dev/docs/controls/page#events
    """
    self.__config_event = func
    self._logger.debug(f"Adding config event: {self.__config_event}")

run(name='', host=None, port=0, view=AppView.FLET_APP, assets_dir='assets', upload_dir=None, web_renderer=WebRenderer.CANVAS_KIT, route_url_strategy='path', export_asgi_app=False, fastapi=False, **kwargs)

Execute the app. Supports async, fastapi, and export_asgi_app.

Source code in src/flet_easy/core/app.py
def run(
    self,
    name: str = "",
    host: Optional[str] = None,
    port: int = 0,
    view: Optional[AppView] = AppView.FLET_APP,
    assets_dir: str = "assets",
    upload_dir: Optional[str] = None,
    web_renderer: WebRenderer = WebRenderer.CANVAS_KIT,
    route_url_strategy: str = "path",
    export_asgi_app: bool = False,
    fastapi: bool = False,
    **kwargs,
) -> None:
    """Execute the app. Supports async, fastapi, and export_asgi_app."""
    main = self.get_app()

    if fastapi:
        warn(
            "Avoid using the 'fastapi' parameter in the 'run()' method, "
            "instead use the 'get_app()' method.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return main

    try:
        self._logger.debug("running app from fletEasy with flet")
        return app(
            target=main,
            name=name,
            host=host,
            port=port,
            view=view,
            assets_dir=assets_dir,
            upload_dir=upload_dir,
            web_renderer=web_renderer,
            route_url_strategy=route_url_strategy,
            export_asgi_app=export_asgi_app,
            **kwargs,
        )
    except RuntimeError:
        raise FletEasyError(
            "If you are using fastapi from flet, set the 'fastapi = True' "
            "parameter of the run() method."
        )

add_pages(group_pages)

Add pages from other files.

Example:

app.add_pages([index, test, contador, login, task])

Source code in src/flet_easy/core/app.py
def add_pages(self, group_pages: Union[List[AddPagesy], AddPagesy]) -> None:
    """Add pages from other files.

    Example:
    ```python
    app.add_pages([index, test, contador, login, task])
    ```
    """
    group_pages = group_pages if isinstance(group_pages, list) else [group_pages]

    try:
        for page in group_pages:
            if self.__route_prefix:
                self.__pages.extend(page._add_pages(self.__route_prefix))
            else:
                self.__pages.extend(page._add_pages())

            self._logger.debug(f"Adding group of pages: {len(group_pages)}: {page}\n")
    except Exception as e:
        raise AddPagesError("Add pages error in route: ", e)

Quick Reference:

import flet_easy as fs

app = fs.FletEasy(
    route_prefix="/app",
    route_init="/app/home",
    route_login="/app/login",
    on_keyboard=True,
    on_resize=True,
    secret_key=fs.SecretKey("your-key"),
    auto_logout=True,
    logger=True
)

@app.page("/home")
def home_page(data: fs.Datasy):
    return ft.View("/home", controls=[...])

app.run()

Datasy

flet_easy.Datasy

Bases: AuthMixin

The decorated function will always receive a parameter which is data (can be any name), which will make an object of type Datasy of Flet-Easy.

This class has the following attributes, in order to access its data:

  • page : We get the values of the page provided by Flet.
  • url_params : We obtain a dictionary with the values passed through the url.
  • view : Get a View object from Flet, previously configured with the view decorator of Flet-Easy.
  • route_prefix : Value entered in the FletEasy class parameters to create the app object.
  • route_init : Value entered in the FletEasy class parameters to create the app object.
  • route_login : Value entered in the FletEasy class parameters to create the app object.

  • share : It is used to be able to store and to obtain values in the client session.
  • on_keyboard_event : get event values to use in the page.
  • on_resize : get event values to use in the page.
  • logout : method to close sessions of all sections in the browser (client storage).
  • login : method to create sessions of all sections in the browser (client storage).
  • go / go_route: Method to change the application path.
  • go_back : Method to go back to the previous route.
  • go_navigation_bar : Handles navigation bar changes.
  • history_routes : Get the history of the routes.
  • route : Route provided by the route event.
  • redirect : To redirect to a path before the page loads, it is used in middleware.
  • page_reload : Use this method to reload the page.
  • dynamic_control : Adds dynamic control to the page.
  • confirm_pop : Confirm pop view.
Source code in src/flet_easy/core/data.py
class Datasy(AuthMixin):
    """The decorated function will always receive a parameter which is `data` (can be any name), which will make an object of type `Datasy` of `Flet-Easy`.

    This class has the following attributes, in order to access its data:

    * `page` : We get the values of the page provided by `Flet`.
    * `url_params` : We obtain a dictionary with the values passed through the url.
    * `view` : Get a `View` object from `Flet`, previously configured with the `view` decorator of `Flet-Easy`.
    * `route_prefix` : Value entered in the `FletEasy` class parameters to create the app object.
    * `route_init` : Value entered in the `FletEasy` class parameters to create the app object.
    * `route_login` : Value entered in the `FletEasy` class parameters to create the app object.
    ---
    * `share` : It is used to be able to store and to obtain values in the client session.
    * `on_keyboard_event` : get event values to use in the page.
    * `on_resize` : get event values to use in the page.
    * `logout` : method to close sessions of all sections in the browser (client storage).
    * `login` : method to create sessions of all sections in the browser (client storage).
    * `go` / `go_route`: Method to change the application path.
    * `go_back` : Method to go back to the previous route.
    * `go_navigation_bar` : Handles navigation bar changes.
    * `history_routes` : Get the history of the routes.
    * `route` : Route provided by the route event.
    * `redirect` : To redirect to a path before the page loads, it is used in middleware.
    * `page_reload` : Use this method to reload the page.
    * `dynamic_control` : Adds dynamic control to the page.
    * `confirm_pop` : Confirm pop view.
    """

    __slots__ = (
        "__page",
        "__url_params",
        "__view",
        "__route_prefix",
        "__route_init",
        "__route_login",
        "__share",
        "__on_keyboard_event",
        "__on_resize",
        "__route",
        "_run_go",
        "__history_routes",
        "_dynamic_control",
        "__secret_key",
        "__auto_logout",
        "__sleep",
        "_key_login",
        "_login_done",
        "_shared_preferences",
    )

    def __init__(
        self,
        page: Page,
        route_prefix: str,
        route_init: str,
        route_login: str,
        secret_key: str,
        auto_logout: bool,
        page_on_keyboard: Keyboardsy,
        page_on_resize: Resizesy,
        go: Callable[[Union[str, int], bool], None] = None,
    ) -> None:
        self.__page: Page = page
        self.__url_params: Dict[str, Any] = None
        self.__view: Viewsy = None
        self.__route_prefix = route_prefix
        self.__route_init = route_init
        self.__route_login = route_login
        self.__share = (
            SharedPreferencesEdit(prefix="fs-share:")
            if NEW_FLET_VERSION
            else SessionStorageEdit(page)
        )
        self.__on_keyboard_event = page_on_keyboard
        self.__on_resize: Resizesy = page_on_resize
        self.__route: str = None
        self._run_go = go
        self.__history_routes: deque[Tuple[str, int]] = deque()
        self._dynamic_control: Dict[str, List[Tuple[Control, Callable[[Control]], None]]] = {}

        self.__secret_key: SecretKey = secret_key
        self.__auto_logout: bool = auto_logout
        self._sleep_auth: int = 1
        self._key_login: str = None
        self._login_done: bool = False
        self._shared_preferences = (
            SharedPreferencesEdit() if NEW_FLET_VERSION else SessionStorageEdit(page)
        )

        _logger.info(
            "Using SharedPreferences (Flet >= 0.80)"
            if NEW_FLET_VERSION
            else "Using SessionStorage (Flet < 0.80)"
        )

    @property
    def page(self) -> Page:
        return self.__page

    @page.setter
    def page(self, page: Page):
        self.__page = page

    @property
    def history_routes(self) -> deque[Tuple[str, int]]:
        return self.__history_routes

    @property
    def url_params(self) -> Dict[str, Any]:
        return self.__url_params

    @url_params.setter
    def url_params(self, url_params: Dict[str, Any]):
        self.__url_params = url_params

    @property
    def view(self) -> Union[Viewsy, View]:
        return self.__view

    @view.setter
    def view(self, view: Union[Viewsy, View]):
        self.__view = view

    @property
    def route_prefix(self) -> str:
        return self.__route_prefix

    @route_prefix.setter
    def route_prefix(self, route_prefix: str):
        self.__route_prefix = route_prefix

    @property
    def route_init(self) -> str:
        return self.__route_init

    @route_init.setter
    def route_init(self, route_init: str):
        self.__route_init = route_init

    @property
    def route_login(self) -> str:
        return self.__route_login

    @route_login.setter
    def route_login(self, route_login: str):
        self.__route_login = route_login

    @property
    def share(self) -> Union[SessionStorageEdit, SharedPreferencesEdit]:
        return self.__share

    # events
    @property
    def on_keyboard_event(self) -> Keyboardsy:
        return self.__on_keyboard_event

    @on_keyboard_event.setter
    def on_keyboard_event(self, on_keyboard_event: object):
        self.__on_keyboard_event = on_keyboard_event

    @property
    def on_resize(self) -> Resizesy:
        return self.__on_resize

    @on_resize.setter
    def on_resize(self, on_resize: object):
        self.__on_resize = on_resize

    @property
    def key_login(self) -> str:
        return self._key_login

    @property
    def auto_logout(self) -> bool:
        return self.__auto_logout

    @property
    def secret_key(self) -> SecretKey:
        return self.__secret_key

    @property
    def route(self) -> str:
        return self.__route

    @route.setter
    def route(self, route: str):
        self.__route = route

    """ Page go  """

    def go(self, route: str) -> Callable[[ControlEvent], None]:
        """To change the application path, it is important for better validation to avoid using `page.go()`."""

        return lambda _=None: self.go_route(route)

    def go_route(self, route: Union[str, int]) -> None:
        """To change the application path, it is important for better validation to avoid using `page.go()`."""

        async def _go_route():
            await self._run_go(route)

        self.page.run_task(_go_route)

    def go_navigation_bar(self, e: ControlEvent) -> None:
        """Handles navigation bar changes. Use this method in the on_change event of
        'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls."""

        async def _go_nav():
            route = e.control.selected_index
            await self._run_go(route)

        self.page.run_task(_go_nav)

    def redirect(self, route: str) -> Redirect:
        """Useful if you do not want to access a route that has already been sent."""
        return Redirect(route)

    def go_back(self, e: ControlEvent = None) -> None:
        """Go back to the previous route."""

        if len(self.history_routes) > 1:
            self.history_routes.pop()
            route, index = self.history_routes.pop()

            if index is not None:
                self.view.navigation_bar.selected_index = index

            async def _go_back():
                await self._run_go(route)

            self.page.run_task(_go_back)
        else:
            print("-> I can't go back! there is no history. ")

    def page_reload(self):
        """Use this method to reload the page, restores the default values of the page"""
        self._run_go(self.page.route, page_reload=True)

    def dynamic_control(self, control: Control, func_update: Callable[[Control], None]) -> None:
        """Adds dynamic control to the page, allowing real-time updates when caching is enabled on the page."""
        if self.page.route not in self._dynamic_control:
            self._dynamic_control[self.page.route] = [(control, func_update)]
        else:
            self._dynamic_control[self.page.route].append((control, func_update))

    def confirm_pop(self, e: ViewPopEvent) -> None:
        """Confirm pop view"""
        self.go_back()
        e.control.confirm_pop(False)

pagepropertywritable

url_paramspropertywritable

viewpropertywritable

route_prefixpropertywritable

route_initpropertywritable

route_loginpropertywritable

shareproperty

on_keyboard_eventpropertywritable

on_resizepropertywritable

history_routesproperty

routepropertywritable

go(route)

To change the application path, it is important for better validation to avoid using page.go().

Source code in src/flet_easy/core/data.py
def go(self, route: str) -> Callable[[ControlEvent], None]:
    """To change the application path, it is important for better validation to avoid using `page.go()`."""

    return lambda _=None: self.go_route(route)

go_back(e=None)

Go back to the previous route.

Source code in src/flet_easy/core/data.py
def go_back(self, e: ControlEvent = None) -> None:
    """Go back to the previous route."""

    if len(self.history_routes) > 1:
        self.history_routes.pop()
        route, index = self.history_routes.pop()

        if index is not None:
            self.view.navigation_bar.selected_index = index

        async def _go_back():
            await self._run_go(route)

        self.page.run_task(_go_back)
    else:
        print("-> I can't go back! there is no history. ")

go_navigation_bar(e)

Handles navigation bar changes. Use this method in the on_change event of 'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls.

Source code in src/flet_easy/core/data.py
def go_navigation_bar(self, e: ControlEvent) -> None:
    """Handles navigation bar changes. Use this method in the on_change event of
    'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls."""

    async def _go_nav():
        route = e.control.selected_index
        await self._run_go(route)

    self.page.run_task(_go_nav)

redirect(route)

Useful if you do not want to access a route that has already been sent.

Source code in src/flet_easy/core/data.py
def redirect(self, route: str) -> Redirect:
    """Useful if you do not want to access a route that has already been sent."""
    return Redirect(route)

Quick Reference:

def my_page(data: fs.Datasy):
    # Navigation
    data.go("/other-page")
    data.go_back()

    # Authentication
    data.login("token", "jwt-value")
    data.logout("token", next_route="/login")

    # JWT decoding (replaces fs.decode / fs.decode_async)
    decoded = data.decode_jwt("token")           # sync
    # decoded = await data.decode_jwt_async("token")  # async

    # Data sharing
    data.share.set("key", "value")
    value = data.share.get("key")

    # Page access
    data.page.title = "New Title"
    data.page.update()

Pagesy

flet_easy.Pagesy

To add pages, it requires the following parameters: * route: text string of the url, for example('/task'). * view: Stores the page function. * title : Define the title of the page. * index : Define the index of the page, use in controls like ft.NavigationBar and ft.CupertinoNavigationBar. * clear: Removes the pages from the page.views list of flet. (optional) * share_data : It is a boolean value, which is useful if you want to share data between pages, in a more restricted way. (optional) * protected_route: Protects the route of the page, according to the configuration of the login decorator of the FletEasy class. (optional) * custom_params: To add validation of parameters in the custom url using a list, where the key is the name of the parameter validation and the value is the custom function that must report a boolean value. * middleware : It acts as an intermediary between different software components, intercepting and processing requests and responses. They allow adding functionalities to an application in a flexible and modular way. (optional) * cache: Boolean that preserves page state when navigating. Controls retain their values instead of resetting. (Optional)

Example:

Pagesy("/test/{id:d}/user/{name:l}", test_page, protected_route=True)

Source code in src/flet_easy/core/pages.py
class Pagesy:
    """To add pages, it requires the following parameters:
    * `route`: text string of the url, for example(`'/task'`).
    * `view`: Stores the page function.
    * `title` : Define the title of the page.
    * `index` : Define the index of the page, use in controls like `ft.NavigationBar` and `ft.CupertinoNavigationBar`.
    * `clear`: Removes the pages from the `page.views` list of flet. (optional)
    * `share_data` : It is a boolean value, which is useful if you want to share data between pages, in a more restricted way. (optional)
    * `protected_route`: Protects the route of the page, according to the configuration of the `login` decorator of the `FletEasy` class. (optional)
    * `custom_params`: To add validation of parameters in the custom url using a list, where the key is the name of the parameter validation and the value is the custom function that must report a boolean value.
    * `middleware` : It acts as an intermediary between different software components, intercepting and processing requests and responses. They allow adding functionalities to an application in a flexible and modular way. (optional)
    * `cache`: Boolean that preserves page state when navigating. Controls retain their values instead of resetting. (Optional)

    Example:
    ```python
    Pagesy("/test/{id:d}/user/{name:l}", test_page, protected_route=True)
    ```
    """

    __slots__ = (
        "route",
        "view",
        "title",
        "index",
        "clear",
        "share_data",
        "protected_route",
        "custom_params",
        "middleware",
        "cache",
        "_is_component",
        "_middlewares_request",
    )

    def __init__(
        self,
        route: str,
        view: ViewHandler,
        title: Optional[str] = None,
        index: Optional[int] = None,
        clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[Dict[str, Callable[[], bool]]] = None,
        middleware: Optional[
            Union[
                List[Union[MiddlewareHandler, MiddlewareRequest]],
                MiddlewareHandler,
                MiddlewareRequest,
            ]
        ] = None,
        cache: bool = False,
    ):
        self.route = route
        self.view = view
        self.title = title
        self.index = index
        self.clear = clear
        self.share_data = share_data
        self.protected_route = protected_route
        self.custom_params = custom_params
        self.middleware = middleware
        self.cache: bool = cache
        self._is_component: bool = getattr(view, "__is_component__", False)
        self._middlewares_request: deque[MiddlewareRequest] = deque()

    def _valid_middlewares_request(self) -> bool:
        return bool(self._middlewares_request)

    def _process_middleware(self, middleware: Union[MiddlewareRequest, MiddlewareHandler]) -> None:
        """Process and validate middleware handlers."""

        if isinstance(middleware, FunctionType):
            self.middleware.append(middleware)
        elif isinstance(middleware, MiddlewareRequest) or (
            isinstance(middleware, type) and issubclass(middleware, MiddlewareRequest)
        ):
            self._middlewares_request.append(middleware)
            self.middleware.append(middleware)
        else:
            raise TypeError(
                f"Class '{getattr(middleware, '__name__', type(middleware).__name__)}' must inherit from MiddlewareRequest class or be a function",
            )

    def _check_middleware(self, middleware: Middleware) -> None:
        if middleware is None and self.middleware is None:
            return

        # Collect page-level items first, then global ones
        page_items = []
        if self.middleware is not None:
            if isinstance(self.middleware, (list, tuple, set, deque)):
                page_items.extend(self.middleware)
            else:
                page_items.append(self.middleware)

        global_items = []
        if middleware is not None:
            if isinstance(middleware, (list, tuple, set, deque)):
                global_items.extend(middleware)
            else:
                global_items.append(middleware)

        self.middleware = deque()
        self._middlewares_request = deque()

        for m in page_items + global_items:
            try:
                self._process_middleware(m)
            except (TypeError, AssertionError) as e:
                from flet_easy.exceptions import ConfigurationError

                raise ConfigurationError(f"Invalid middleware configuration: {str(e)}")

    def __repr__(self):
        return f"Pagesy(route={self.route}, view={self.view}, title={self.title}, index={self.index}, clear={self.clear}, share_data={self.share_data}, protected_route={self.protected_route}, custom_params={self.custom_params}, middleware={self.middleware}, cache={self.cache})"

__init__(route, view, title=None, index=None, clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)

Source code in src/flet_easy/core/pages.py
def __init__(
    self,
    route: str,
    view: ViewHandler,
    title: Optional[str] = None,
    index: Optional[int] = None,
    clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[Dict[str, Callable[[], bool]]] = None,
    middleware: Optional[
        Union[
            List[Union[MiddlewareHandler, MiddlewareRequest]],
            MiddlewareHandler,
            MiddlewareRequest,
        ]
    ] = None,
    cache: bool = False,
):
    self.route = route
    self.view = view
    self.title = title
    self.index = index
    self.clear = clear
    self.share_data = share_data
    self.protected_route = protected_route
    self.custom_params = custom_params
    self.middleware = middleware
    self.cache: bool = cache
    self._is_component: bool = getattr(view, "__is_component__", False)
    self._middlewares_request: deque[MiddlewareRequest] = deque()

Quick Reference:

from flet_easy import Pagesy

page = Pagesy(
    route="/user/{id:d}",
    view=user_view_function,
    title="User Profile",
    protected_route=True,
    middleware=[auth_middleware],
    cache=True,
    share_data=True
)

AddPagesy

flet_easy.AddPagesy

This class allows you to add pages from other files to the main Flet-Easy class.

Requiere los parámetros: - route_prefix: cadena de texto que se unira a la url del decorator page, ejemplo(/users) esto englobara todas las urls de esta clase. (opcional) - middleware: lista de middlewares que se agregaran a la página. (opcional)

Ejemplo:

users = fs.AddPagesy(route_prefix="/user")


@users.page("/task")
async def task_page(data: fs.Datasy):
    page = data.page
    page.title = "Task"
    return ft.View(
        route="/users/task",
        controls=[ft.Text("Task")],
    )

Source code in src/flet_easy/core/pages.py
class AddPagesy:
    """This class allows you to add pages from other files to the main `Flet-Easy` class.

    Requiere los parámetros:
    - **route_prefix:** cadena de texto que se unira a la url del decorator `page`, ejemplo(`/users`) esto englobara todas las urls de esta clase. (opcional)
    - **middleware:** lista de middlewares que se agregaran a la página. (opcional)

    **Ejemplo:**
    ```python
    users = fs.AddPagesy(route_prefix="/user")


    @users.page("/task")
    async def task_page(data: fs.Datasy):
        page = data.page
        page.title = "Task"
        return ft.View(
            route="/users/task",
            controls=[ft.Text("Task")],
        )
    ```
    """

    __slots__ = ("route_prefix", "middleware", "__pages")

    def __init__(
        self,
        route_prefix: Optional[str] = None,
        middleware: Optional[
            Union[
                List[Union[MiddlewareHandler, MiddlewareRequest]],
                MiddlewareHandler,
                MiddlewareRequest,
            ]
        ] = None,
    ):
        self.route_prefix = route_prefix.rstrip("/") if route_prefix else None
        self.middleware = middleware
        self.__pages: deque[Pagesy] = deque()

    def __build_route(self, route: str) -> str:
        """Build complete route with prefix."""
        if not self.route_prefix:
            return route
        if route == "/":
            return self.route_prefix
        return self.route_prefix + route

    def page(
        self,
        route: str,
        title: Optional[str] = None,
        index: Optional[int] = None,
        page_clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[Dict[str, Any]] = None,
        middleware: Optional[
            Union[
                List[Union[MiddlewareHandler, MiddlewareRequest]],
                MiddlewareHandler,
                MiddlewareRequest,
            ]
        ] = None,
        cache: bool = False,
    ) -> Callable:
        """Decorator for adding pages with configuration."""

        def decorator(func: Callable) -> Callable:
            pagesy = Pagesy(
                route=self.__build_route(route),
                view=func,
                title=title,
                index=index,
                clear=page_clear,
                share_data=share_data,
                protected_route=protected_route,
                custom_params=custom_params,
                middleware=middleware,
                cache=cache,
            )
            self.__pages.append(pagesy)

            # Back-reference for reverse decorator order (@ft.component on top)
            func.__flet_easy_pagesy__ = pagesy

            return func

        return decorator

    def _add_pages(self, route: Optional[str] = None) -> deque[Pagesy]:
        """Add pages with optional route prefix override."""

        for page in self.__pages:
            page._check_middleware(self.middleware)

            if route:
                page.route = route if page.route == "/" else route + page.route

        return self.__pages

    def __repr__(self) -> str:
        return f"AddPagesy(route_prefix={self.route_prefix}, middleware={self.middleware}, number_pages={len(self.__pages)}, pages={self.__pages})"

__build_route(route)

Build complete route with prefix.

Source code in src/flet_easy/core/pages.py
def __build_route(self, route: str) -> str:
    """Build complete route with prefix."""
    if not self.route_prefix:
        return route
    if route == "/":
        return self.route_prefix
    return self.route_prefix + route

page(route, title=None, index=None, page_clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)

Decorator for adding pages with configuration.

Source code in src/flet_easy/core/pages.py
def page(
    self,
    route: str,
    title: Optional[str] = None,
    index: Optional[int] = None,
    page_clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[Dict[str, Any]] = None,
    middleware: Optional[
        Union[
            List[Union[MiddlewareHandler, MiddlewareRequest]],
            MiddlewareHandler,
            MiddlewareRequest,
        ]
    ] = None,
    cache: bool = False,
) -> Callable:
    """Decorator for adding pages with configuration."""

    def decorator(func: Callable) -> Callable:
        pagesy = Pagesy(
            route=self.__build_route(route),
            view=func,
            title=title,
            index=index,
            clear=page_clear,
            share_data=share_data,
            protected_route=protected_route,
            custom_params=custom_params,
            middleware=middleware,
            cache=cache,
        )
        self.__pages.append(pagesy)

        # Back-reference for reverse decorator order (@ft.component on top)
        func.__flet_easy_pagesy__ = pagesy

        return func

    return decorator

Quick Reference:

from flet_easy import AddPagesy, Pagesy

pages = AddPagesy([
    Pagesy("/home", home_view),
    Pagesy("/about", about_view),
    Pagesy("/contact", contact_view)
])

app.add_pages(pages)

Viewsy

flet_easy.Viewsy

Bases: View

Source code in src/flet_easy/ui/controls.py
class Viewsy(View):
    pass

Quick Reference:

from flet_easy import Viewsy
import flet as ft

@app.view
def main_view(data: fs.Datasy):
    return Viewsy(
        appbar=ft.AppBar(title=ft.Text("My App")),
        drawer=ft.NavigationDrawer(...),
        bgcolor=ft.Colors.GREY_50,
        padding=ft.padding.all(20)
    )

Authentication & Security

SecretKey

flet_easy.SecretKeydataclass

Correctly add the secret key in the FletEasy class parameter.

Source code in src/flet_easy/security/config.py
@dataclass
class SecretKey:
    """Correctly add the secret key in the `FletEasy` class parameter."""

    algorithm: str = "HS256"
    secret: str = None
    pem_key: PemKey = None
    Jwt: bool = False

EasyKey

flet_easy.EasyKey

To obtain a secret_key more easily, support algorithms [ HS256, RS256 ]

Example:
import flet_easy as fs

key = fs.EasyKey()

# --- HS256
SECRET_KEY = key.secret_key()

# --- RS256
PRIVATE_KEY = key.private_key()
PUBLIC_KEY = key.public_key()
Source code in src/flet_easy/security/jwt.py
class EasyKey:
    """To obtain a `secret_key` more easily, support algorithms [ HS256, RS256 ]

    ### Example:
    ```python
    import flet_easy as fs

    key = fs.EasyKey()

    # --- HS256
    SECRET_KEY = key.secret_key()

    # --- RS256
    PRIVATE_KEY = key.private_key()
    PUBLIC_KEY = key.public_key()
    ```
    """

    def __init__(self):
        (public_key, private_key) = newkeys(2048)
        self.public = public_key
        self.private = private_key

    def private_key(self) -> str:
        return self.private.save_pkcs1().decode("utf-8")

    def public_key(self) -> str:
        return self.public.save_pkcs1().decode("utf-8")

    def secret_key(self) -> str:
        return token_bytes(64).hex().encode("utf-8")

Quick Reference:

from flet_easy import EasyKey, SecretKey

# Generate keys
key_gen = EasyKey()
secret = key_gen.secret_key()  # For HS256
private_key = key_gen.private_key()  # For RS256
public_key = key_gen.public_key()  # For RS256

# Use with FletEasy
app = fs.FletEasy(secret_key=SecretKey(secret))

JWT Functions

[!warning] Deprecated since v0.4.0 fs.decode() and fs.decode_async() are deprecated. Use data.decode_jwt() / data.decode_jwt_async() (methods on Datasy) instead.

flet_easy.Datasy.decode_jwt(key)

Decode JWT

Parameters

  • key: Key to store the value used in the login method of Datasy.
Source code in src/flet_easy/security/auth.py
def decode_jwt(self, key: str) -> Union[Dict[str, Any], bool]:
    """Decode JWT

    ## Parameters
    - key: Key to store the value used in the `login` method of `Datasy`.
    """
    try:
        return self.page.run_task(self.decode_jwt_async, key).result(timeout=5)
    except TimeoutError as e:
        raise LoginError("Decode error, using decode_async:", e)

flet_easy.Datasy.decode_jwt_async(key_login)async

Decode JWT asynchronously

Parameters

  • key: Key to store the value used in the login method of Datasy.
Source code in src/flet_easy/security/auth.py
async def decode_jwt_async(self, key_login: str) -> Union[Dict[str, Any], bool]:
    """Decode JWT asynchronously

    ## Parameters
    - key: Key to store the value used in the `login` method of `Datasy`.
    """
    jwt_token = await self._storage_get_async(key_login)

    try:
        self._key_login = key_login

        self._evaluate_secret_key()

        if jwt_token is None:
            return False

        if self.auto_logout and not self._login_done:
            self.page.pubsub.send_others_on_topic(
                self.page.client_ip, Msg("updateLogin", value=self._login_done)
            )

        decode = _decode_payload(
            jwt=jwt_token,
            secret_key=self._active_key,
            algorithms=self.secret_key.algorithm,
        )

        # It checks if there is a logout time, if there is a logout task running
        if decode.get("exp") and not self._login_done and self.auto_logout:
            self._create_task_login_update(decode)
        return decode

    except (ExpiredSignatureError, InvalidKeyError):
        self.logout(key_login)
        return False
    except DecodeError as e:
        self.logout(key_login)
        raise LogoutError(
            "Decoding error, possibly there is a double use of the storage 'key', Secret key invalid! or ",
            e,
        )

flet_easy.decode(key_login, data)

decodes the jwt and updates the browser sessions.

Parameters to use:
  • key_login : key used to store data in the client, also used in the login method of Datasy.
  • data : Instance object of the Datasy class.
Source code in src/flet_easy/security/jwt.py
@deprecated("Use the 'data.decode_jwt' method instead.", version="0.4.0")
def decode(key_login: str, data: Datasy) -> Union[dict[str, Any], bool]:  # noqa: UP007
    """decodes the jwt and updates the browser sessions.

    ### Parameters to use:
    * `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
    * `data` : Instance object of the `Datasy` class.
    """
    return data.decode_jwt(key_login)

flet_easy.decode_async(key_login, data)async

"decodes the jwt and updates the browser sessions.

Parameters to use:
  • key_login : key used to store data in the client, also used in the login method of Datasy.
  • data : Instance object of the Datasy class.
Source code in src/flet_easy/security/jwt.py
@deprecated("Use the 'data.decode_jwt_async' method instead.", version="0.4.0")
async def decode_async(key_login: str, data: Datasy) -> Union[dict[str, Any], bool]:  # noqa: UP007
    """ "decodes the jwt and updates the browser sessions.

    ### Parameters to use:
    * `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
    * `data` : Instance object of the `Datasy` class.
    """
    return await data.decode_jwt_async(key_login)

flet_easy.encode_HS256(payload, secret_key, time_expiry=None)

Source code in src/flet_easy/security/config.py
def encode_HS256(payload: Dict[str, Any], secret_key: str, time_expiry: timezone = None) -> str:
    payload = _time_exp(time_expiry, payload)
    return encode(
        payload=payload,
        key=secret_key,
        algorithm="HS256",
    )

flet_easy.encode_RS256(payload, private, time_expiry=None)

Source code in src/flet_easy/security/config.py
def encode_RS256(payload: Dict[str, Any], private: str, time_expiry: timezone = None) -> str:
    payload = _time_exp(time_expiry, payload)
    return encode(
        payload=payload,
        key=private,
        algorithm="RS256",
    )

Quick Reference:

from flet_easy import SecretKey
import flet_easy as fs

# --- New API (v0.4.0+) ---
@app.login
async def login_required(data: fs.Datasy) -> bool:
    return await data.decode_jwt_async(key_login="login")

# --- Deprecated (will be removed) ---
# value = fs.decode(key_login="login", data=data)

Event Handling

Keyboardsy

flet_easy.Keyboardsy

Class that manages keyboard input values.

Source code in src/flet_easy/ui/controls.py
class Keyboardsy:
    """Class that manages keyboard input values.

    Methods:
        add_control(function) - Add functions to be executed on key press (supports async).
        key() - Returns the key value.
        shift() - Returns the shift state.
        ctrl() - Returns the ctrl state.
        alt() - Returns the alt state.
        meta() - Returns the meta state.
        test() - Returns a message of all keyboard input values.
    """

    __slots__ = ("__call", "__controls", "__current_route", "__current_controls")

    def __init__(self, call=None) -> None:
        self.__call: KeyboardEvent = call
        self.__controls: Dict[str, list] = {}
        self.__current_route: str = None
        self.__current_controls: list = []

    @property
    def current_route(self) -> str:
        return self.__current_route

    @current_route.setter
    def current_route(self, value: str):
        self.__current_route = value
        if value not in self.__controls:
            self.__controls[value] = []
        self.__current_controls = self.__controls[value]

    @property
    def call(self):
        return self.__call

    @call.setter
    def call(self, call: KeyboardEvent):
        self.__call = call

    def _controls(self) -> bool:
        return len(self.__current_controls) != 0

    def clear(self):
        self.__current_controls.clear()

    def add_control(self, function: Callable):
        """Method to add functions to be executed by pressing a key `(supports async, if the app is one)`."""
        self.__current_controls.append(function)

    async def _run_controls(self):
        """Execute all registered keyboard control functions."""
        if not self.__current_controls:
            return

        for control_func in self.__current_controls:
            try:
                if iscoroutinefunction(control_func):
                    await control_func()
                else:
                    control_func()
            except Exception as e:
                raise KeyBoardEventError(
                    f"Error executing keyboard control in function: {control_func} - {e}"
                )

    def key(self) -> str:
        return self.call.key

    def shift(self) -> bool:
        return self.call.shift

    def ctrl(self) -> bool:
        return self.call.ctrl

    def alt(self) -> bool:
        return self.call.alt

    def meta(self) -> bool:
        return self.call.meta

    def test(self):
        return f"Key: {self.call.key}, Shift: {self.call.shift}, Control: {self.call.ctrl}, Alt: {self.call.alt}, Meta: {self.call.meta}"

add_control(function)

Method to add functions to be executed by pressing a key (supports async, if the app is one).

Source code in src/flet_easy/ui/controls.py
def add_control(self, function: Callable):
    """Method to add functions to be executed by pressing a key `(supports async, if the app is one)`."""
    self.__current_controls.append(function)

key()

Source code in src/flet_easy/ui/controls.py
def key(self) -> str:
    return self.call.key

shift()

Source code in src/flet_easy/ui/controls.py
def shift(self) -> bool:
    return self.call.shift

ctrl()

Source code in src/flet_easy/ui/controls.py
def ctrl(self) -> bool:
    return self.call.ctrl

alt()

Source code in src/flet_easy/ui/controls.py
def alt(self) -> bool:
    return self.call.alt

meta()

Source code in src/flet_easy/ui/controls.py
def meta(self) -> bool:
    return self.call.meta

test()

Source code in src/flet_easy/ui/controls.py
def test(self):
    return f"Key: {self.call.key}, Shift: {self.call.shift}, Control: {self.call.ctrl}, Alt: {self.call.alt}, Meta: {self.call.meta}"

Quick Reference:

@app.config_event_handler
def handle_events(data: fs.Datasy):
    if data.on_keyboard_event:
        key = data.on_keyboard_event.key()
        if key == "Escape":
            data.go("/home")
        elif key == "F1":
            data.go("/help")

Resizesy

flet_easy.Resizesy

For the manipulation of the on_resize event of flet.

Source code in src/flet_easy/ui/controls.py
class Resizesy:
    """For the manipulation of the `on_resize` event of flet.

    Attributes:
        e - Returns `ControlEvent` event, each time the height and width changes.
        page - Returns the `Page` instance.
        height - Returns the updated height value.
        width - Returns the updated width value.
        heightX(pct) - Calculate percentage of page height (1-100).
        widthX(pct) - Calculate percentage of page width (1-100).
        margin_y - Y-axis margin value.
        margin_x - X-axis margin value.
    """

    __slots__ = ("__page", "__height", "__width", "__margin_y", "__margin_x", "__e")

    def __init__(self, page: Page) -> None:
        self.__page = page
        self.__height: float = page.height
        self.__width: float = page.width
        self.__margin_y: Union[float, int] = 0
        self.__margin_x: Union[float, int] = 0
        self.__e: ControlEvent = None

    @property
    def page(self) -> Page:
        return self.__page

    @property
    def e(self):
        return self.__e

    @e.setter
    def e(self, e: ControlEvent):
        self.__e = e
        self.__page = e.page
        self.__height = self.page.height - self.__margin_y
        self.__width = self.page.width - self.__margin_x

    @property
    def margin_y(self):
        return self.__margin_y

    @margin_y.setter
    def margin_y(self, value: int):
        """Enter a value that subtracts the margin of the page, so that 100% of the page can be occupied."""
        self.__margin_y = value * 2

    @property
    def margin_x(self):
        return self.__margin_x

    @margin_x.setter
    def margin_x(self, value: int):
        """Enter a value that subtracts the margin of the page, so that 100% of the page can be occupied."""
        self.__margin_x = value * 2

    @property
    def height(self):
        return self.__height

    @property
    def width(self):
        return self.__width

    def heightX(self, height: int):
        """Function to calculate the percentage of high that will be used in the page, 100 means 100%, the values that can be entered is from (1-100)"""
        if height < 100:
            return (self.height - self.margin_y) * float("0." + str(height))
        else:
            return self.height - self.margin_y

    def widthX(self, width: int):
        """Function to calculate the percentage of width that will be used in the page, 100 means 100%, the values that can be entered is from (1-100)"""
        if width < 100:
            return (self.width - self.margin_x) * float("0." + str(width))
        else:
            return self.width - self.margin_x

widthproperty

heightproperty

Quick Reference:

@app.config_event_handler
def handle_events(data: fs.Datasy):
    if data.on_resize:
        width = data.on_resize.width()
        height = data.on_resize.height()

        # Responsive layout adjustments
        if width < 600:
            # Mobile layout
            pass
        else:
            # Desktop layout
            pass

Responsive Design

ResponsiveControlsy

flet_easy.ResponsiveControlsy

Bases: Canvas

Allows the controls to adapt to the size of the app (responsive).

Source code in src/flet_easy/ui/controls.py
class ResponsiveControlsy(Canvas):
    """Allows the controls to adapt to the size of the app (responsive).

    Parameters:
        content (Control) - Contains a flet control.
        expand (int) - Space that will contain the `content` controller in the app.
        resize_interval (int) - Response time (optional).
        on_resize (callable) - Custom function executed on app resize (optional).
        show_resize (bool) - Observe the size of the controller (optional).
        show_resize_terminal (bool) - See the size in the terminal (optional).
    """

    def __init__(
        self,
        content: Control,
        expand: int,
        resize_interval=1,
        on_resize: Callable = None,
        show_resize: bool = False,
        show_resize_terminal: bool = False,
        **kwargs,
    ):
        super().__init__(**kwargs)
        self.content = content
        self.resize_interval = resize_interval
        self.resize_callback = on_resize
        self.expand = expand
        self.show_resize = show_resize
        self.show_resize_terminal = show_resize_terminal
        self.on_resize = self.__handle_canvas_resize

    def __handle_canvas_resize(self, e):
        if self.resize_callback is not None:
            if iscoroutinefunction(self.resize_callback):
                self.page.run_task(self.resize_callback, e)
            else:
                self.resize_callback(e)

        elif self.show_resize:
            if self.content.content:
                self.content.content.value = f"{e.width} x {e.height}"
                self.update()
            else:
                self.content.alignment = alignment.center
                self.content.content = Text(f"{e.width} x {e.height}")
                self.update()

        if self.show_resize_terminal:
            print(f"{e.width} x {e.height}")

Quick Reference:

from flet_easy import ResponsiveControlsy
import flet as ft

responsive_text = ResponsiveControlsy(
    controls={
        "xs": ft.Text("Mobile", size=14),
        "sm": ft.Text("Tablet", size=16),
        "md": ft.Text("Desktop", size=18),
        "lg": ft.Text("Large Desktop", size=20),
    }
)

Reference System

Ref

flet_easy.Ref

Bases: Ref[T]

Get the reference of the control used by flet, linked to the created component. Similar to flet, but more reduced by getting the value of the control with (c).

Source code in src/flet_easy/ui/controls.py
class Ref(Ref[T]):
    """Get the reference of the control used by flet, linked to the created component.
    Similar to flet, but more reduced by getting the value of the control with (c)."""

    @property
    def c(self) -> T:
        return super().current

Quick Reference:

from flet_easy import Ref
import flet as ft

def my_page(data: fs.Datasy):
    text_ref = Ref[ft.TextField]()

    def handle_click(_):
        value = text_ref.current.value
        print(f"Input value: {value}")

    return ft.View(
        "/page",
        controls=[
            ft.TextField(ref=text_ref, label="Enter text"),
            ft.ElevatedButton("Get Value", on_click=handle_click)
        ]
    )

Middleware System

MiddlewareRequest

flet_easy.MiddlewareRequest

Source code in src/flet_easy/core/middleware.py
class MiddlewareRequest:
    _data: Datasy = None

    def __init__(self):
        self.data = MiddlewareRequest._data

    def before_request(self):
        pass

    def after_request(self):
        pass

before_request()

Source code in src/flet_easy/core/middleware.py
def before_request(self):
    pass

after_request()

Source code in src/flet_easy/core/middleware.py
def after_request(self):
    pass

Quick Reference:

from flet_easy import MiddlewareRequest, Redirect

class AuthMiddleware(MiddlewareRequest):
    def before_request(self):
        if not self.data.page.client_storage.get("auth_token"):
            return Redirect("/login")

    def after_request(self):
        # Log the request
        print(f"Accessed: {self.data.route}")

@app.page("/protected", middleware=[AuthMiddleware])
def protected_page(data: fs.Datasy):
    return ft.View("/protected", controls=[...])

Utility Classes

Redirect

flet_easy.Redirectdataclass

Source code in src/flet_easy/core/models.py
@dataclass
class Redirect:
    route: str = None

Quick Reference:

from flet_easy import Redirect

def auth_middleware(data: fs.Datasy):
    if not data.page.client_storage.get("token"):
        return Redirect("/login")
    return None

Route Parameter Types

Flet-Easy supports several parameter types in dynamic routes:

TypeSyntaxDescriptionExample
Integer{name:d}Matches integers/user/{id:d}/user/123
String{name:str}Matches any string/category/{name:str}/category/electronics
Lowercase{name:l}Matches lowercase strings/tag/{slug:l}/tag/python-tips
Float{name:f}Matches floating point numbers/price/{amount:f}/price/19.99

Example Usage:

@app.page("/blog/{year:d}/{month:d}/{slug:str}")
def blog_post(data: fs.Datasy, year: int, month: int, slug: str):
    # year and month are automatically converted to int
    # slug remains as string
    post = get_blog_post(year, month, slug)
    return ft.View(f"/blog/{year}/{month}/{slug}", controls=[...])

Configuration Options

FletEasy Configuration

ParameterTypeDefaultDescription
route_prefixstr""Base prefix for all routes
route_initstr"/"Initial route when app starts
route_loginstr"/login"Redirect route for protected pages
on_keyboardboolFalseEnable keyboard event handling
on_resizeboolFalseEnable window resize events
secret_keySecretKeyNoneSecret key for JWT and encryption
auto_logoutboolFalseAuto-logout on JWT expiration
path_viewsPathNoneDirectory for automatic page discovery
loggerboolFalseEnable detailed logging

Pagesy Configuration

ParameterTypeDefaultDescription
routestrRequiredURL pattern for the page
viewCallableRequiredFunction that returns a View
titlestrNonePage title for browser
indexintNoneNavigation index for tabs
clearboolFalseClear navigation history
share_databoolFalseEnable data sharing
protected_routeboolFalseRequire authentication
custom_paramsDictNoneCustom parameter validators
middlewareListNonePage-specific middleware
cacheboolFalsePreserve page state

Error Handling

Common Exceptions

from flet_easy.exceptions import (
    FletEasyError,
    AddPagesError,
    LoginError,
    LogoutError,
    MiddlewareError
)

try:
    app = fs.FletEasy()
    app.run()
except FletEasyError as e:
    print(f"FletEasy error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Error Handling in Pages

@app.page("/api-demo")
def api_demo_page(data: fs.Datasy):
    try:
        # Your logic here
        result = fetch_api_data()
        return create_success_view(result)
    except Exception as e:
        # Show error to user
        data.page.show_snack_bar(
            ft.SnackBar(content=ft.Text(f"Error: {str(e)}"))
        )
        # Return error view
        return ft.View(
            "/api-demo",
            controls=[
                ft.Text("An error occurred", color=ft.Colors.RED),
                ft.ElevatedButton("Retry", on_click=data.go("/api-demo")),
                ft.ElevatedButton("Go Home", on_click=data.go("/home"))
            ]
        )

Performance Tips

Use Caching Wisely

# Cache expensive pages
@app.page("/reports", cache=True)
def reports_page(data: fs.Datasy):
    # Expensive computation
    return ft.View("/reports", controls=[...])

# Don't cache dynamic content
@app.page("/live-feed", cache=False)
def live_feed_page(data: fs.Datasy):
    # Real-time data
    return ft.View("/live-feed", controls=[...])

Optimize Page Updates

def update_multiple_controls(data: fs.Datasy):
    # Batch updates
    control1.value = "New Value 1"
    control2.value = "New Value 2"
    control3.value = "New Value 3"

    # Single update call
    data.page.update()

Use Lazy Loading

@app.page("/heavy-page")
def heavy_page(data: fs.Datasy):
    # Load heavy content only when needed
    def load_content(_):
        heavy_data = load_heavy_data()
        content_container.content = create_heavy_ui(heavy_data)
        data.page.update()

    content_container = ft.Container()

    return ft.View(
        "/heavy-page",
        controls=[
            ft.ElevatedButton("Load Content", on_click=load_content),
            content_container
        ]
    )

Migration Guide

From v0.1.x to v0.2.x

Breaking Changes:

  • update_loginlogin
  • logautlogout
  • Function parameters changed for login and config_event_handler decorators

Migration Example:

# Old (v0.1.x)
@app.login
def check_auth(page: ft.Page):
    return page.client_storage.get("token") is not None

# New (v0.2.x)
@app.login
def check_auth(data: fs.Datasy):
    return data.page.client_storage.get("token") is not None

This API reference provides comprehensive coverage of all Flet-Easy features. For detailed examples and tutorials, refer to the other sections of this documentation.