dinhlongviolin1 hai 1 mes
pai
achega
c6e4a71ec6
Modificáronse 48 ficheiros con 585 adicións e 268 borrados
  1. 1 1
      taipy/_run.py
  2. 1 1
      taipy/gui/_default_config.py
  3. 2 2
      taipy/gui/config.py
  4. 3 6
      taipy/gui/custom/utils.py
  5. 134 101
      taipy/gui/gui.py
  6. 37 0
      taipy/gui/servers/__init__.py
  7. 14 0
      taipy/gui/servers/fastapi/__init__.py
  8. 44 0
      taipy/gui/servers/fastapi/server.py
  9. 14 0
      taipy/gui/servers/flask/__init__.py
  10. 17 38
      taipy/gui/servers/flask/server.py
  11. 18 0
      taipy/gui/servers/request.py
  12. 83 0
      taipy/gui/servers/server.py
  13. 90 0
      taipy/gui/servers/utils.py
  14. 6 5
      taipy/gui/state.py
  15. 15 9
      taipy/gui/utils/_locals_context.py
  16. 1 1
      taipy/gui/utils/proxy.py
  17. 5 5
      tests/gui/actions/test_download.py
  18. 2 2
      tests/gui/actions/test_get_module_context.py
  19. 1 1
      tests/gui/actions/test_get_state_id.py
  20. 1 1
      tests/gui/actions/test_get_user_content_url.py
  21. 2 2
      tests/gui/actions/test_hold_control.py
  22. 2 2
      tests/gui/actions/test_invoke_callback.py
  23. 2 2
      tests/gui/actions/test_navigate.py
  24. 4 4
      tests/gui/actions/test_notify.py
  25. 2 2
      tests/gui/actions/test_resume_control.py
  26. 1 1
      tests/gui/config/test_cli.py
  27. 34 33
      tests/gui/data/test_pandas_data_accessor.py
  28. 5 3
      tests/gui/gui_specific/test_broadcast.py
  29. 1 2
      tests/gui/gui_specific/test_favicon.py
  30. 13 10
      tests/gui/gui_specific/test_gui.py
  31. 1 1
      tests/gui/gui_specific/test_locals_context.py
  32. 2 2
      tests/gui/gui_specific/test_navigate.py
  33. 2 6
      tests/gui/gui_specific/test_state.py
  34. 1 1
      tests/gui/gui_specific/test_variable_binding.py
  35. 1 1
      tests/gui/gui_specific/test_variable_directory.py
  36. 1 1
      tests/gui/helpers.py
  37. 1 1
      tests/gui/long_runnig/test_long_running.py
  38. 4 4
      tests/gui/server/http/test_file_upload.py
  39. 1 1
      tests/gui/server/ws/test_a.py
  40. 1 1
      tests/gui/server/ws/test_broadcast.py
  41. 1 1
      tests/gui/server/ws/test_df.py
  42. 1 1
      tests/gui/server/ws/test_du.py
  43. 2 2
      tests/gui/server/ws/test_on_change.py
  44. 1 1
      tests/gui/server/ws/test_ru.py
  45. 1 1
      tests/gui/server/ws/test_u.py
  46. 2 2
      tests/gui/server/ws/test_with.py
  47. 6 6
      tests/gui/utils/test_evaluator.py
  48. 1 1
      tests/gui/utils/test_map_dict.py

+ 1 - 1
taipy/_run.py

@@ -57,7 +57,7 @@ def _run(*services: _AppType, **kwargs) -> t.Optional[Flask]:
         return None
         return None
 
 
     if gui and rest:
     if gui and rest:
-        gui._set_flask(rest._app)  # type: ignore[union-attr]
+        gui._set_web_server(rest._app)  # type: ignore[union-attr]
         return gui.run(**kwargs)
         return gui.run(**kwargs)
     else:
     else:
         app = rest or gui
         app = rest or gui

+ 1 - 1
taipy/gui/_default_config.py

@@ -53,7 +53,7 @@ default_config: Config = {
     "debug": False,
     "debug": False,
     "extended_status": False,
     "extended_status": False,
     "favicon": None,
     "favicon": None,
-    "flask_log": False,
+    "server_log": False,
     "host": "127.0.0.1",
     "host": "127.0.0.1",
     "light_theme": None,
     "light_theme": None,
     "margin": "1em",
     "margin": "1em",

+ 2 - 2
taipy/gui/config.py

@@ -42,7 +42,7 @@ ConfigParameter = t.Literal[
     "debug",
     "debug",
     "extended_status",
     "extended_status",
     "favicon",
     "favicon",
-    "flask_log",
+    "server_log",
     "host",
     "host",
     "light_theme",
     "light_theme",
     "margin",
     "margin",
@@ -117,7 +117,7 @@ Config = t.TypedDict(
         "debug": bool,
         "debug": bool,
         "extended_status": bool,
         "extended_status": bool,
         "favicon": t.Optional[str],
         "favicon": t.Optional[str],
-        "flask_log": bool,
+        "server_log": bool,
         "host": str,
         "host": str,
         "light_theme": t.Optional[t.Dict[str, t.Any]],
         "light_theme": t.Optional[t.Dict[str, t.Any]],
         "margin": t.Optional[str],
         "margin": t.Optional[str],

+ 3 - 6
taipy/gui/custom/utils.py

@@ -12,10 +12,7 @@
 import contextlib
 import contextlib
 import typing as t
 import typing as t
 
 
-from flask import has_request_context
-from flask.globals import request
-
-from ..server import _Server
+from ..servers import _Server, get_request, has_request_context
 from ._page import ResourceHandler, _ExternalResourceHandlerManager
 from ._page import ResourceHandler, _ExternalResourceHandlerManager
 
 
 
 
@@ -24,7 +21,7 @@ def is_in_custom_page_context() -> bool:
     resource_handler_id = None
     resource_handler_id = None
     with contextlib.suppress(Exception):
     with contextlib.suppress(Exception):
         if has_request_context():
         if has_request_context():
-            resource_handler_id = request.cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
+            resource_handler_id = get_request().cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
     return resource_handler_id is not None
     return resource_handler_id is not None
 
 
 
 
@@ -33,5 +30,5 @@ def get_current_resource_handler() -> t.Optional[ResourceHandler]:
     resource_handler_id = None
     resource_handler_id = None
     with contextlib.suppress(Exception):
     with contextlib.suppress(Exception):
         if has_request_context():
         if has_request_context():
-            resource_handler_id = request.cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
+            resource_handler_id = get_request().cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
     return _ExternalResourceHandlerManager().get(resource_handler_id) if resource_handler_id else None
     return _ExternalResourceHandlerManager().get(resource_handler_id) if resource_handler_id else None

+ 134 - 101
taipy/gui/gui.py

@@ -32,16 +32,8 @@ from urllib.parse import unquote, urlencode, urlparse
 
 
 import markdown as md_lib
 import markdown as md_lib
 import tzlocal
 import tzlocal
-from flask import (
-    Blueprint,
-    Flask,
-    g,
-    has_app_context,
-    jsonify,
-    request,
-    send_file,
-    send_from_directory,
-)
+from fastapi import FastAPI
+from flask import Blueprint, Flask
 from werkzeug.utils import secure_filename
 from werkzeug.utils import secure_filename
 
 
 import __main__  # noqa: F401
 import __main__  # noqa: F401
@@ -71,7 +63,18 @@ from .data.data_scope import _DataScopes
 from .extension.library import Element, ElementLibrary
 from .extension.library import Element, ElementLibrary
 from .page import Page
 from .page import Page
 from .partial import Partial
 from .partial import Partial
-from .server import _Server
+from .servers import (
+    ServerFrameworks,
+    _Server,
+    create_server,
+    get_request,
+    get_request_meta,
+    get_server_type,
+    has_server_context,
+    send_file,
+    send_from_directory,
+    set_server_type,
+)
 from .state import State, _AsyncState, _GuiState
 from .state import State, _AsyncState, _GuiState
 from .types import _WsType
 from .types import _WsType
 from .utils import (
 from .utils import (
@@ -185,8 +188,8 @@ class Gui:
         path_mapping: t.Optional[dict] = None,
         path_mapping: t.Optional[dict] = None,
         env_filename: t.Optional[str] = None,
         env_filename: t.Optional[str] = None,
         libraries: t.Optional[t.List[ElementLibrary]] = None,
         libraries: t.Optional[t.List[ElementLibrary]] = None,
-        flask: t.Optional[Flask] = None,
         script_paths: t.Union[str, Path, t.List[t.Union[str, Path]], None] = None,
         script_paths: t.Union[str, Path, t.List[t.Union[str, Path]], None] = None,
+        server: t.Union[ServerFrameworks, Flask, FastAPI] = "flask",
     ):
     ):
         """Initialize a new Gui instance.
         """Initialize a new Gui instance.
 
 
@@ -227,13 +230,11 @@ class Gui:
                 instances that pages can reference.<br/>
                 instances that pages can reference.<br/>
                 Using this argument is equivalent to calling `(Gui.)add_library()^` for each
                 Using this argument is equivalent to calling `(Gui.)add_library()^` for each
                 list's elements.
                 list's elements.
-            flask (Optional[Flask]): An optional instance of a Flask application object.<br/>
-                If this argument is set, this `Gui` instance will use the value of this argument
-                as the underlying server. If omitted or set to None, this `Gui` will create its
-                own Flask application instance and use it to serve the pages.
             script_paths (Union[str, Path, List[Union[str, Path]], None]):
             script_paths (Union[str, Path, List[Union[str, Path]], None]):
                 Specifies the path(s) to the JavaScript files or external resources used by the application.
                 Specifies the path(s) to the JavaScript files or external resources used by the application.
                 It can be a single URL or path, or a list containing multiple URLs and/or paths.
                 It can be a single URL or path, or a list containing multiple URLs and/or paths.
+            server (Union[ServerFrameworks, Flask, FastAPI]): The server type to use for the application.<br/>
+                The default value is `flask`.<br/>
         """
         """
         # store suspected local containing frame
         # store suspected local containing frame
         self.__frame = t.cast(FrameType, t.cast(FrameType, currentframe()).f_back)
         self.__frame = t.cast(FrameType, t.cast(FrameType, currentframe()).f_back)
@@ -248,7 +249,21 @@ class Gui:
         if path_mapping is None:
         if path_mapping is None:
             path_mapping = {}
             path_mapping = {}
         self._path_mapping = path_mapping
         self._path_mapping = path_mapping
-        self._flask = flask
+
+        # Server config
+        self._server_instance: t.Union[Flask, FastAPI, None] = None
+        if isinstance(server, Flask):
+            set_server_type("flask")
+            self._server_instance = server
+        elif isinstance(server, FastAPI):
+            set_server_type("fastapi")
+            self._server_instance = server
+        elif isinstance(server, str) and server in t.get_args(ServerFrameworks):
+            set_server_type(server)  # type: ignore[arg-type]
+        else:
+            raise ValueError(
+                f"Invalid 'server' option. 'server' must be one of {t.get_args(ServerFrameworks)} or a Flask or FastAPI instance."  # noqa: E501
+            )
 
 
         self._config = _Config()
         self._config = _Config()
         self.__content_accessor = None
         self.__content_accessor = None
@@ -374,7 +389,6 @@ class Gui:
         self.__client_id_2_sid: t.Dict[str, t.Set[str]] = {}
         self.__client_id_2_sid: t.Dict[str, t.Set[str]] = {}
 
 
         # Load default config
         # Load default config
-        self._flask_blueprint: t.List[Blueprint] = []
         self._config._load(default_config)
         self._config._load(default_config)
 
 
         # get taipy version
         # get taipy version
@@ -625,35 +639,35 @@ class Gui:
         return (
         return (
             _DataScopes._GLOBAL_ID
             _DataScopes._GLOBAL_ID
             if self._bindings()._is_single_client()
             if self._bindings()._is_single_client()
-            else getattr(g, Gui.__ARG_CLIENT_ID, "unknown id")
+            else getattr(get_request_meta(), Gui.__ARG_CLIENT_ID, "unknown id")
         )
         )
 
 
     def __set_client_id_in_context(self, client_id: t.Optional[str] = None, force=False):
     def __set_client_id_in_context(self, client_id: t.Optional[str] = None, force=False):
-        if not client_id and request:
-            client_id = request.args.get(Gui.__ARG_CLIENT_ID, "")
-        if not client_id and (ws_client_id := getattr(g, "ws_client_id", None)):
+        if not client_id and get_request():
+            client_id = get_request().args.get(Gui.__ARG_CLIENT_ID, "")
+        if not client_id and (ws_client_id := getattr(get_request_meta(), "ws_client_id", None)):
             client_id = ws_client_id
             client_id = ws_client_id
         if not client_id and force:
         if not client_id and force:
             res = self._bindings()._get_or_create_scope("")
             res = self._bindings()._get_or_create_scope("")
             client_id = res[0] if res[1] else None
             client_id = res[0] if res[1] else None
-        if client_id and request:
-            if sid := getattr(request, "sid", None):
+        if client_id and get_request():
+            if sid := getattr(get_request(), "sid", None):
                 sids = self.__client_id_2_sid.get(client_id, None)
                 sids = self.__client_id_2_sid.get(client_id, None)
                 if sids is None:
                 if sids is None:
                     sids = set()
                     sids = set()
                     self.__client_id_2_sid[client_id] = sids
                     self.__client_id_2_sid[client_id] = sids
                 sids.add(sid)
                 sids.add(sid)
-        g.client_id = client_id
+        get_request_meta().client_id = client_id
 
 
     def __is_var_modified_in_context(self, var_name: str, derived_vars: t.Set[str]) -> bool:
     def __is_var_modified_in_context(self, var_name: str, derived_vars: t.Set[str]) -> bool:
-        modified_vars: t.Optional[t.Set[str]] = getattr(g, "modified_vars", None)
-        der_vars: t.Optional[t.Set[str]] = getattr(g, "derived_vars", None)
-        setattr(g, "update_count", getattr(g, "update_count", 0) + 1)  # noqa: B010
+        modified_vars: t.Optional[t.Set[str]] = getattr(get_request_meta(), "modified_vars", None)
+        der_vars: t.Optional[t.Set[str]] = getattr(get_request_meta(), "derived_vars", None)
+        setattr(get_request_meta(), "update_count", getattr(get_request_meta(), "update_count", 0) + 1)  # noqa: B010
         if modified_vars is None:
         if modified_vars is None:
             modified_vars = set()
             modified_vars = set()
-            g.modified_vars = modified_vars
+            get_request_meta().modified_vars = modified_vars
         if der_vars is None:
         if der_vars is None:
-            g.derived_vars = derived_vars
+            get_request_meta().derived_vars = derived_vars
         else:
         else:
             der_vars.update(derived_vars)
             der_vars.update(derived_vars)
         if var_name in modified_vars:
         if var_name in modified_vars:
@@ -662,15 +676,15 @@ class Gui:
         return False
         return False
 
 
     def __clean_vars_on_exit(self) -> t.Optional[t.Set[str]]:
     def __clean_vars_on_exit(self) -> t.Optional[t.Set[str]]:
-        update_count = getattr(g, "update_count", 0) - 1
+        update_count = getattr(get_request_meta(), "update_count", 0) - 1
         if update_count < 1:
         if update_count < 1:
-            derived_vars: t.Set[str] = getattr(g, "derived_vars", set())
-            delattr(g, "update_count")
-            delattr(g, "modified_vars")
-            delattr(g, "derived_vars")
+            derived_vars: t.Set[str] = getattr(get_request_meta(), "derived_vars", set())
+            delattr(get_request_meta(), "update_count")
+            delattr(get_request_meta(), "modified_vars")
+            delattr(get_request_meta(), "derived_vars")
             return derived_vars
             return derived_vars
         else:
         else:
-            setattr(g, "update_count", update_count)  # noqa: B010
+            setattr(get_request_meta(), "update_count", update_count)  # noqa: B010
             return None
             return None
 
 
     def _handle_connect(self):
     def _handle_connect(self):
@@ -678,7 +692,9 @@ class Gui:
 
 
     def _handle_disconnect(self):
     def _handle_disconnect(self):
         _Hooks()._handle_disconnect(self)
         _Hooks()._handle_disconnect(self)
-        if (sid := getattr(request, "sid", None)) and (st_to := self._get_config("state_retention_period", 0)) > 0:
+        if (sid := getattr(get_request(), "sid", None)) and (
+            st_to := self._get_config("state_retention_period", 0)
+        ) > 0:
             for cl_id, sids in self.__client_id_2_sid.items():
             for cl_id, sids in self.__client_id_2_sid.items():
                 if sid in sids:
                 if sid in sids:
                     if len(sids) == 1:
                     if len(sids) == 1:
@@ -695,7 +711,7 @@ class Gui:
             except Exception as e:
             except Exception as e:
                 _warn(f"Unexpected error removing state {client_id}", e)
                 _warn(f"Unexpected error removing state {client_id}", e)
 
 
-    def _manage_message(self, msg_type: _WsType, message: dict) -> None:
+    def _manage_ws_message(self, msg_type: _WsType, message: dict) -> None:
         try:
         try:
             client_id = None
             client_id = None
             if msg_type == _WsType.CLIENT_ID.value:
             if msg_type == _WsType.CLIENT_ID.value:
@@ -710,7 +726,7 @@ class Gui:
                         self.__handle_ws_app_id({"name": message.get("name"), "payload": front_app_id})
                         self.__handle_ws_app_id({"name": message.get("name"), "payload": front_app_id})
             expected_client_id = client_id or message.get(Gui.__ARG_CLIENT_ID)
             expected_client_id = client_id or message.get(Gui.__ARG_CLIENT_ID)
             self.__set_client_id_in_context(expected_client_id)
             self.__set_client_id_in_context(expected_client_id)
-            g.ws_client_id = expected_client_id
+            get_request_meta().ws_client_id = expected_client_id
             with self._set_locals_context(message.get("module_context") or None):
             with self._set_locals_context(message.get("module_context") or None):
                 with self._get_authorization():
                 with self._get_authorization():
                     payload = message.get("payload", {})
                     payload = message.get("payload", {})
@@ -903,7 +919,7 @@ class Gui:
         if len(parts) > 1:
         if len(parts) > 1:
             file_name = parts[-1]
             file_name = parts[-1]
             (dir_path, as_attachment) = self.__get_content_accessor().get_content_path(
             (dir_path, as_attachment) = self.__get_content_accessor().get_content_path(
-                path[: -len(file_name) - 1], file_name, request.args.get("bypass")
+                path[: -len(file_name) - 1], file_name, get_request().args.get("bypass")
             )
             )
             if dir_path:
             if dir_path:
                 return send_from_directory(str(dir_path), file_name, as_attachment=as_attachment)
                 return send_from_directory(str(dir_path), file_name, as_attachment=as_attachment)
@@ -919,7 +935,7 @@ class Gui:
     def __serve_user_content(self, path: str) -> t.Any:
     def __serve_user_content(self, path: str) -> t.Any:
         self.__set_client_id_in_context()
         self.__set_client_id_in_context()
         q_args: t.Dict[str, str] = {}
         q_args: t.Dict[str, str] = {}
-        q_args.update(request.args)
+        q_args.update(get_request().args)
         q_args.pop(Gui.__ARG_CLIENT_ID, None)
         q_args.pop(Gui.__ARG_CLIENT_ID, None)
         cb_function: t.Union[t.Callable, str, None] = None
         cb_function: t.Union[t.Callable, str, None] = None
         cb_function_name = None
         cb_function_name = None
@@ -1028,17 +1044,17 @@ class Gui:
 
 
     def __upload_files(self):
     def __upload_files(self):
         self.__set_client_id_in_context()
         self.__set_client_id_in_context()
-        on_upload_action = request.form.get("on_action", None)
-        var_name = t.cast(str, request.form.get("var_name", None))
+        on_upload_action = get_request().form.get("on_action", None)
+        var_name = t.cast(str, get_request().form.get("var_name", None))
         if not var_name and not on_upload_action:
         if not var_name and not on_upload_action:
             _warn("upload files: No var name")
             _warn("upload files: No var name")
             return ("upload files: No var name", 400)
             return ("upload files: No var name", 400)
-        context = request.form.get("context", None)
-        upload_data = request.form.get("upload_data", None)
-        multiple = "multiple" in request.form and request.form["multiple"] == "True"
+        context = get_request().form.get("context", None)
+        upload_data = get_request().form.get("upload_data", None)
+        multiple = "multiple" in get_request().form and get_request().form["multiple"] == "True"
 
 
         # File parsing and checks
         # File parsing and checks
-        file = request.files.get("blob", None)
+        file = get_request().files.get("blob", None)
         if not file:
         if not file:
             _warn("upload files: No file part")
             _warn("upload files: No file part")
             return ("upload files: No file part", 400)
             return ("upload files: No file part", 400)
@@ -1049,15 +1065,15 @@ class Gui:
             return ("upload files: No selected file", 400)
             return ("upload files: No selected file", 400)
 
 
         # Path parsing and checks
         # Path parsing and checks
-        path = request.form.get("path", "")
+        path = get_request().form.get("path", "")
         suffix = ""
         suffix = ""
         complete = True
         complete = True
         part = 0
         part = 0
 
 
-        if "total" in request.form:
-            total = int(request.form["total"])
-            if total > 1 and "part" in request.form:
-                part = int(request.form["part"])
+        if "total" in get_request().form:
+            total = int(get_request().form["total"])
+            if total > 1 and "part" in get_request().form:
+                part = int(get_request().form["part"])
                 suffix = f".part.{part}"
                 suffix = f".part.{part}"
                 complete = part == total - 1
                 complete = part == total - 1
 
 
@@ -1405,8 +1421,7 @@ class Gui:
         grouping_message = self.__get_message_grouping() if allow_grouping else None
         grouping_message = self.__get_message_grouping() if allow_grouping else None
         if grouping_message is None:
         if grouping_message is None:
             try:
             try:
-                self._server._ws.emit(
-                    "message",
+                self._server.send_ws_message(
                     payload,
                     payload,
                     to=t.cast(str, self.__get_ws_receiver(send_back_only)),
                     to=t.cast(str, self.__get_ws_receiver(send_back_only)),
                 )
                 )
@@ -1419,7 +1434,7 @@ class Gui:
     def __broadcast_ws(self, payload: dict, client_id: t.Optional[str] = None):
     def __broadcast_ws(self, payload: dict, client_id: t.Optional[str] = None):
         try:
         try:
             to = list(self.__get_sids(client_id)) if client_id else []
             to = list(self.__get_sids(client_id)) if client_id else []
-            self._server._ws.emit("message", payload, to=t.cast(str, to) if to else None, include_self=True)
+            self._server.send_ws_message(payload, to=t.cast(str, to) if to else None, include_self=True)
             time.sleep(0.001)
             time.sleep(0.001)
         except Exception as e:  # pragma: no cover
         except Exception as e:  # pragma: no cover
             _warn(f"Exception raised in WebSocket communication in '{self.__frame.f_code.co_name}'", e)
             _warn(f"Exception raised in WebSocket communication in '{self.__frame.f_code.co_name}'", e)
@@ -1427,8 +1442,7 @@ class Gui:
     def __send_ack(self, ack_id: t.Optional[str]) -> None:
     def __send_ack(self, ack_id: t.Optional[str]) -> None:
         if ack_id:
         if ack_id:
             try:
             try:
-                self._server._ws.emit(
-                    "message",
+                self._server.send_ws_message(
                     {"type": _WsType.ACKNOWLEDGEMENT.value, "id": ack_id},
                     {"type": _WsType.ACKNOWLEDGEMENT.value, "id": ack_id},
                     to=t.cast(str, self.__get_ws_receiver(True)),
                     to=t.cast(str, self.__get_ws_receiver(True)),
                 )
                 )
@@ -1539,7 +1553,7 @@ class Gui:
     def __get_ws_receiver(self, send_back_only=False) -> t.Union[t.List[str], t.Any, None]:
     def __get_ws_receiver(self, send_back_only=False) -> t.Union[t.List[str], t.Any, None]:
         if self._bindings()._is_single_client():
         if self._bindings()._is_single_client():
             return None
             return None
-        sid = getattr(request, "sid", None) if request else None
+        sid = getattr(get_request(), "sid", None) if get_request() else None
         sids = self.__get_sids(self._get_client_id())
         sids = self.__get_sids(self._get_client_id())
         if sid:
         if sid:
             sids.add(sid)
             sids.add(sid)
@@ -1705,13 +1719,13 @@ class Gui:
             module_context (Optional[str]): The name of the module that will be used.
             module_context (Optional[str]): The name of the module that will be used.
         """  # noqa: E501
         """  # noqa: E501
         this_sid = None
         this_sid = None
-        if request:
+        if get_request():
             # avoid messing with the client_id => Set(ws id)
             # avoid messing with the client_id => Set(ws id)
-            this_sid = getattr(request, "sid", None)
-            request.sid = None  # type: ignore[attr-defined]
+            this_sid = getattr(get_request(), "sid", None)
+            get_request().sid = None  # type: ignore[attr-defined]
         try:
         try:
-            with self.get_flask_app().app_context():
-                setattr(g, Gui.__ARG_CLIENT_ID, state_id)
+            with self.get_server_instance().app_context():
+                setattr(get_request_meta(), Gui.__ARG_CLIENT_ID, state_id)
                 with self._set_module_context(module_context):
                 with self._set_module_context(module_context):
                     if not _is_function(callback):
                     if not _is_function(callback):
                         callback = self._get_user_function(t.cast(str, callback))
                         callback = self._get_user_function(t.cast(str, callback))
@@ -1727,7 +1741,7 @@ class Gui:
                 )
                 )
         finally:
         finally:
             if this_sid:
             if this_sid:
-                request.sid = this_sid  # type: ignore[attr-defined]
+                get_request().sid = this_sid  # type: ignore[attr-defined]
         return None
         return None
 
 
     def broadcast_callback(
     def broadcast_callback(
@@ -1798,7 +1812,7 @@ class Gui:
 
 
     def _is_in_brdcst_callback(self):
     def _is_in_brdcst_callback(self):
         try:
         try:
-            return getattr(g, Gui.__BRDCST_CALLBACK_G_ID, False)
+            return getattr(get_request_meta(), Gui.__BRDCST_CALLBACK_G_ID, False)
         except RuntimeError:
         except RuntimeError:
             return False
             return False
 
 
@@ -2069,8 +2083,14 @@ class Gui:
     def _get_root_page_name():
     def _get_root_page_name():
         return Gui.__root_page_name
         return Gui.__root_page_name
 
 
+    # Deprecated
     def _set_flask(self, flask: Flask):
     def _set_flask(self, flask: Flask):
-        self._flask = flask
+        raise RuntimeError(
+            "'_set_flask()' is deprecated. Use '_set_web_server()' instead as multiple web frameworks has been supported."  # noqa: E501
+        )
+
+    def _set_web_server(self, server: t.Union[Flask, FastAPI]):
+        self._server_instance = server
 
 
     def _get_default_module_name(self):
     def _get_default_module_name(self):
         return self.__default_module_name
         return self.__default_module_name
@@ -2378,11 +2398,11 @@ class Gui:
 
 
     def _set_broadcast(self, broadcast: bool = True):
     def _set_broadcast(self, broadcast: bool = True):
         with contextlib.suppress(RuntimeError):
         with contextlib.suppress(RuntimeError):
-            setattr(g, Gui.__BROADCAST_G_ID, broadcast)
+            setattr(get_request_meta(), Gui.__BROADCAST_G_ID, broadcast)
 
 
     def _is_broadcasting(self) -> bool:
     def _is_broadcasting(self) -> bool:
         try:
         try:
-            return getattr(g, Gui.__BROADCAST_G_ID, False)
+            return getattr(get_request_meta(), Gui.__BROADCAST_G_ID, False)
         except RuntimeError:
         except RuntimeError:
             return False
             return False
 
 
@@ -2549,7 +2569,7 @@ class Gui:
         nav_page = page_name
         nav_page = page_name
         if hasattr(self, "on_navigate") and _is_function(self.on_navigate):
         if hasattr(self, "on_navigate") and _is_function(self.on_navigate):
             try:
             try:
-                params = request.args.to_dict() if hasattr(request, "args") else {}
+                params = get_request().args.to_dict() if hasattr(get_request(), "args") else {}
                 params.pop("client_id", None)
                 params.pop("client_id", None)
                 params.pop("v", None)
                 params.pop("v", None)
                 nav_page = self._call_function_with_state(
                 nav_page = self._call_function_with_state(
@@ -2589,7 +2609,7 @@ class Gui:
         """Handle the bindings of custom page variables"""
         """Handle the bindings of custom page variables"""
         if not isinstance(page, CustomPage):
         if not isinstance(page, CustomPage):
             return
             return
-        with self.get_flask_app().app_context() if has_app_context() else contextlib.nullcontext():  # type: ignore[attr-defined]
+        with self.get_server_instance().app_context() if has_server_context() else contextlib.nullcontext():  # type: ignore[attr-defined]
             self.__set_client_id_in_context(client_id)
             self.__set_client_id_in_context(client_id)
             with self._set_locals_context(page._get_module_name()):
             with self._set_locals_context(page._get_module_name()):
                 for k, v in self._get_locals_bind().items():
                 for k, v in self._get_locals_bind().items():
@@ -2612,7 +2632,7 @@ class Gui:
         # Make sure that there is a page instance found
         # Make sure that there is a page instance found
         if page is None:
         if page is None:
             return (
             return (
-                jsonify({"error": f"Page '{nav_page}' doesn't exist."}),
+                self._server.direct_render_json({"error": f"Page '{nav_page}' doesn't exist."}),
                 400,
                 400,
                 {"Content-Type": "application/json; charset=utf-8"},
                 {"Content-Type": "application/json; charset=utf-8"},
             )
             )
@@ -2640,7 +2660,7 @@ class Gui:
         if page._rendered_jsx is not None:
         if page._rendered_jsx is not None:
             with self._set_locals_context(context):
             with self._set_locals_context(context):
                 self._call_on_page_load(nav_page)
                 self._call_on_page_load(nav_page)
-            return self._server._render(
+            return self._server.render(
                 page._rendered_jsx,
                 page._rendered_jsx,
                 page._script_paths if page._script_paths is not None else [],
                 page._script_paths if page._script_paths is not None else [],
                 page._style if page._style is not None else "",
                 page._style if page._style is not None else "",
@@ -2651,7 +2671,7 @@ class Gui:
             return ("No page template", 404)
             return ("No page template", 404)
 
 
     def _render_route(self) -> t.Any:
     def _render_route(self) -> t.Any:
-        return self._server._direct_render_json(
+        return self._server.direct_render_json(
             {
             {
                 "locations": {
                 "locations": {
                     "/" if route == Gui.__root_page_name else f"/{route}": f"/{route}" for route in self._config.routes
                     "/" if route == Gui.__root_page_name else f"/{route}": f"/{route}" for route in self._config.routes
@@ -2660,17 +2680,20 @@ class Gui:
             }
             }
         )
         )
 
 
-    def get_flask_app(self) -> Flask:
-        """Get the internal Flask application.
+    def get_flask_app(self):
+        raise RuntimeError("'get_flask_app()' is deprecated. Use 'get_server_instance()' instead.")
+
+    def get_server_instance(self) -> t.Union[Flask, FastAPI]:
+        """Get the internal server application.
 
 
         This method must be called **after** `(Gui.)run()^` was invoked.
         This method must be called **after** `(Gui.)run()^` was invoked.
 
 
         Returns:
         Returns:
-            The Flask instance used.
+            The server instance used.
         """
         """
         if hasattr(self, "_server"):
         if hasattr(self, "_server"):
-            return t.cast(Flask, self._server.get_flask())
-        raise RuntimeError("get_flask_app() cannot be invoked before run() has been called.")
+            return t.cast(Flask, self._server.get_server_instance())
+        raise RuntimeError("get_server_instance() cannot be invoked before run() has been called.")
 
 
     def _get_port(self) -> int:
     def _get_port(self) -> int:
         return self._server.get_port()
         return self._server.get_port()
@@ -2745,10 +2768,10 @@ class Gui:
         app_config = self._config.config
         app_config = self._config.config
         # Init server if there is no server
         # Init server if there is no server
         if not hasattr(self, "_server"):
         if not hasattr(self, "_server"):
-            self._server = _Server(
+            self._server = create_server(
                 self,  # type: ignore[arg-type]
                 self,  # type: ignore[arg-type]
                 path_mapping=self._path_mapping,
                 path_mapping=self._path_mapping,
-                flask=self._flask,
+                flask=self._server_instance,
                 async_mode=app_config.get("async_mode"),
                 async_mode=app_config.get("async_mode"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
                 server_config=app_config.get("server_config"),
@@ -2757,11 +2780,10 @@ class Gui:
         # Stop and reinitialize the server if it is still running as a thread
         # Stop and reinitialize the server if it is still running as a thread
         if (_is_in_notebook() or app_config.get("run_in_thread")) and hasattr(self._server, "_thread"):
         if (_is_in_notebook() or app_config.get("run_in_thread")) and hasattr(self._server, "_thread"):
             self.stop()
             self.stop()
-            self._flask_blueprint = []
-            self._server = _Server(
+            self._server = create_server(
                 self,  # type: ignore[arg-type]
                 self,  # type: ignore[arg-type]
                 path_mapping=self._path_mapping,
                 path_mapping=self._path_mapping,
-                flask=self._flask,
+                flask=self._server_instance,
                 async_mode=app_config.get("async_mode"),
                 async_mode=app_config.get("async_mode"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
                 server_config=app_config.get("server_config"),
@@ -2785,7 +2807,7 @@ class Gui:
             _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
             _TaipyLogger._get_logger().info(f" * NGROK Public Url: {self._ngrok[0].public_url}")
 
 
     def __bind_default_function(self):
     def __bind_default_function(self):
-        with self.get_flask_app().app_context():
+        with self.get_server_instance().app_context():
             if additional_pages := _Hooks()._get_additional_pages():
             if additional_pages := _Hooks()._get_additional_pages():
                 # add page context for additional pages so that they can be managed by the variable directory
                 # add page context for additional pages so that they can be managed by the variable directory
                 for page in additional_pages:
                 for page in additional_pages:
@@ -2803,6 +2825,14 @@ class Gui:
             self.__bind_local_func("on_user_content")
             self.__bind_local_func("on_user_content")
 
 
     def __register_blueprint(self):
     def __register_blueprint(self):
+        server_type = get_server_type()
+        if server_type == "flask":
+            self.__register_flask_blueprint()
+        if server_type == "fastapi":
+            self.__register_fastapi_blueprint()
+
+    def __register_flask_blueprint(self):
+        flask_blueprint: t.List[Blueprint] = []
         # add en empty main page if it is not defined
         # add en empty main page if it is not defined
         if Gui.__root_page_name not in self._config.routes:
         if Gui.__root_page_name not in self._config.routes:
             new_page = _Page()
             new_page = _Page()
@@ -2812,22 +2842,22 @@ class Gui:
             self._config.routes.append(Gui.__root_page_name)
             self._config.routes.append(Gui.__root_page_name)
 
 
         pages_bp = Blueprint("taipy_pages", __name__)
         pages_bp = Blueprint("taipy_pages", __name__)
-        self._flask_blueprint.append(pages_bp)
+        flask_blueprint.append(pages_bp)
 
 
         # server URL Rule for taipy images
         # server URL Rule for taipy images
         images_bp = Blueprint("taipy_images", __name__)
         images_bp = Blueprint("taipy_images", __name__)
         images_bp.add_url_rule(f"/{Gui.__CONTENT_ROOT}/<path:path>", view_func=self.__serve_content)
         images_bp.add_url_rule(f"/{Gui.__CONTENT_ROOT}/<path:path>", view_func=self.__serve_content)
-        self._flask_blueprint.append(images_bp)
+        flask_blueprint.append(images_bp)
 
 
         # server URL for uploaded files
         # server URL for uploaded files
         upload_bp = Blueprint("taipy_upload", __name__)
         upload_bp = Blueprint("taipy_upload", __name__)
         upload_bp.add_url_rule(f"/{Gui.__UPLOAD_URL}", view_func=self.__upload_files, methods=["POST"])
         upload_bp.add_url_rule(f"/{Gui.__UPLOAD_URL}", view_func=self.__upload_files, methods=["POST"])
-        self._flask_blueprint.append(upload_bp)
+        flask_blueprint.append(upload_bp)
 
 
         # server URL for user content
         # server URL for user content
         user_content_bp = Blueprint("taipy_user_content", __name__)
         user_content_bp = Blueprint("taipy_user_content", __name__)
         user_content_bp.add_url_rule(f"/{Gui.__USER_CONTENT_URL}/<path:path>", view_func=self.__serve_user_content)
         user_content_bp.add_url_rule(f"/{Gui.__USER_CONTENT_URL}/<path:path>", view_func=self.__serve_user_content)
-        self._flask_blueprint.append(user_content_bp)
+        flask_blueprint.append(user_content_bp)
 
 
         # server URL for extension resources
         # server URL for extension resources
         extension_bp = Blueprint("taipy_extensions", __name__)
         extension_bp = Blueprint("taipy_extensions", __name__)
@@ -2854,11 +2884,11 @@ class Gui:
         if self.__script_files:
         if self.__script_files:
             scripts.extend(self.__script_files)
             scripts.extend(self.__script_files)
 
 
-        self._flask_blueprint.append(extension_bp)
+        flask_blueprint.append(extension_bp)
 
 
         _webapp_path = self._get_webapp_path()
         _webapp_path = self._get_webapp_path()
 
 
-        self._flask_blueprint.append(
+        flask_blueprint.append(
             self._server._get_default_blueprint(
             self._server._get_default_blueprint(
                 static_folder=_webapp_path,
                 static_folder=_webapp_path,
                 template_folder=_webapp_path,
                 template_folder=_webapp_path,
@@ -2884,8 +2914,11 @@ class Gui:
         _Hooks()._add_external_blueprint(self, __name__)
         _Hooks()._add_external_blueprint(self, __name__)
 
 
         # Register Flask Blueprint if available
         # Register Flask Blueprint if available
-        for bp in self._flask_blueprint:
-            t.cast(Flask, self._server.get_flask()).register_blueprint(bp)
+        for bp in flask_blueprint:
+            t.cast(Flask, self._server.get_server_instance()).register_blueprint(bp)
+
+    def __register_fastapi_blueprint(self):
+        pass
 
 
     def _get_accessor(self):
     def _get_accessor(self):
         if self.__accessors is None:
         if self.__accessors is None:
@@ -2898,7 +2931,7 @@ class Gui:
         run_in_thread: bool = False,
         run_in_thread: bool = False,
         async_mode: str = "gevent",
         async_mode: str = "gevent",
         **kwargs,
         **kwargs,
-    ) -> t.Optional[Flask]:
+    ) -> t.Union[Flask, FastAPI, None]:
         """Start the server that delivers pages to web clients.
         """Start the server that delivers pages to web clients.
 
 
         Once you enter `run()`, users can run web browsers and point to the web server
         Once you enter `run()`, users can run web browsers and point to the web server
@@ -3052,7 +3085,7 @@ class Gui:
 
 
         # Start Flask Server
         # Start Flask Server
         if not run_server:
         if not run_server:
-            return self.get_flask_app()
+            return self.get_server_instance()
 
 
         return self._server.run(
         return self._server.run(
             host=app_config.get("host"),
             host=app_config.get("host"),
@@ -3060,7 +3093,7 @@ class Gui:
             client_url=app_config.get("client_url"),
             client_url=app_config.get("client_url"),
             debug=app_config.get("debug"),
             debug=app_config.get("debug"),
             use_reloader=app_config.get("use_reloader"),
             use_reloader=app_config.get("use_reloader"),
-            flask_log=app_config.get("flask_log"),
+            server_log=app_config.get("server_log"),
             run_in_thread=app_config.get("run_in_thread"),
             run_in_thread=app_config.get("run_in_thread"),
             allow_unsafe_werkzeug=app_config.get("allow_unsafe_werkzeug"),
             allow_unsafe_werkzeug=app_config.get("allow_unsafe_werkzeug"),
             notebook_proxy=app_config.get("notebook_proxy"),
             notebook_proxy=app_config.get("notebook_proxy"),
@@ -3147,16 +3180,16 @@ class Gui:
         self, event_name: str, client_id: t.Optional[str] = None, payload: t.Optional[t.Dict[str, t.Any]] = None
         self, event_name: str, client_id: t.Optional[str] = None, payload: t.Optional[t.Dict[str, t.Any]] = None
     ):
     ):
         this_sid = None
         this_sid = None
-        if request:
+        if get_request():
             # avoid messing with the client_id => Set(ws id)
             # avoid messing with the client_id => Set(ws id)
-            this_sid = getattr(request, "sid", None)
-            request.sid = None  # type: ignore[attr-defined]
+            this_sid = getattr(get_request(), "sid", None)
+            get_request().sid = None  # type: ignore[attr-defined]
 
 
         try:
         try:
-            with self.get_flask_app().app_context(), self.__event_manager:
+            with self.get_server_instance().app_context(), self.__event_manager:
                 if client_id:
                 if client_id:
-                    setattr(g, Gui.__ARG_CLIENT_ID, client_id)
+                    setattr(get_request_meta(), Gui.__ARG_CLIENT_ID, client_id)
                 _Hooks()._fire_event(event_name, client_id, payload)
                 _Hooks()._fire_event(event_name, client_id, payload)
         finally:
         finally:
             if this_sid:
             if this_sid:
-                request.sid = this_sid  # type: ignore[attr-defined]
+                get_request().sid = this_sid  # type: ignore[attr-defined]

+ 37 - 0
taipy/gui/servers/__init__.py

@@ -0,0 +1,37 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from .server import ServerFrameworks, _Server
+from .utils import (
+    create_server,
+    get_request,
+    get_request_meta,
+    get_server_type,
+    has_request_context,
+    has_server_context,
+    send_file,
+    send_from_directory,
+    set_server_type,
+)
+
+__all__ = [
+    "_Server",
+    "create_server",
+    "get_request",
+    "get_request_meta",
+    "get_server_type",
+    "has_request_context",
+    "has_server_context",
+    "send_file",
+    "send_from_directory",
+    "set_server_type",
+    "ServerFrameworks",
+]

+ 14 - 0
taipy/gui/servers/fastapi/__init__.py

@@ -0,0 +1,14 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from .server import FastAPIServer
+
+__all__ = ["FastAPIServer"]

+ 44 - 0
taipy/gui/servers/fastapi/server.py

@@ -0,0 +1,44 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from ..server import _Server
+
+
+class FastAPIServer(_Server):
+    def get_server_instance(self):
+        raise NotImplementedError
+
+    def get_port(self) -> int:
+        raise NotImplementedError
+
+    def send_ws_message(self, *args, **kwargs):
+        raise NotImplementedError
+
+    def direct_render_json(self, data):
+        raise NotImplementedError
+
+    def run(
+        self,
+        host,
+        port,
+        client_url,
+        debug,
+        use_reloader,
+        server_log,
+        run_in_thread,
+        allow_unsafe_werkzeug,
+        notebook_proxy,
+        port_auto_ranges,
+    ):
+        raise NotImplementedError
+
+    def stop_thread(self):
+        raise NotImplementedError

+ 14 - 0
taipy/gui/servers/flask/__init__.py

@@ -0,0 +1,14 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from .server import FlaskServer
+
+__all__ = ["FlaskServer"]

+ 17 - 38
taipy/gui/server.py → taipy/gui/servers/flask/server.py

@@ -15,7 +15,6 @@ import contextlib
 import logging
 import logging
 import os
 import os
 import pathlib
 import pathlib
-import re
 import sys
 import sys
 import time
 import time
 import typing as t
 import typing as t
@@ -43,23 +42,17 @@ from werkzeug.serving import is_running_from_reloader
 import __main__
 import __main__
 from taipy.common.logger._taipy_logger import _TaipyLogger
 from taipy.common.logger._taipy_logger import _TaipyLogger
 
 
-from ._renderers.json import _TaipyJsonProvider
-from .config import ServerConfig
-from .custom._page import _ExternalResourceHandlerManager
-from .utils import _is_in_notebook, _is_port_open, _RuntimeManager
-from .utils._css import get_style
+from ..._renderers.json import _TaipyJsonProvider
+from ...config import ServerConfig
+from ...custom._page import _ExternalResourceHandlerManager
+from ...utils import _is_in_notebook, _is_port_open, _RuntimeManager
+from ..server import _Server
 
 
 if t.TYPE_CHECKING:
 if t.TYPE_CHECKING:
-    from .gui import Gui
+    from ...gui import Gui
 
 
 
 
-class _Server:
-    __RE_OPENING_CURLY = re.compile(r"([^\"])(\{)")
-    __RE_CLOSING_CURLY = re.compile(r"(\})([^\"])")
-    __OPENING_CURLY = r"\1&#x7B;"
-    __CLOSING_CURLY = r"&#x7D;\2"
-    _RESOURCE_HANDLER_ARG = "tprh"
-
+class FlaskServer(_Server):
     def __init__(
     def __init__(
         self,
         self,
         gui: Gui,
         gui: Gui,
@@ -119,7 +112,7 @@ class _Server:
             if "status" in message:
             if "status" in message:
                 _TaipyLogger._get_logger().info(message["status"])
                 _TaipyLogger._get_logger().info(message["status"])
             elif "type" in message:
             elif "type" in message:
-                gui._manage_message(message["type"], message)  # type: ignore[attr-defined]
+                gui._manage_ws_message(message["type"], message)  # type: ignore[attr-defined]
 
 
         @self._ws.on("connect")
         @self._ws.on("connect")
         def handle_connect():
         def handle_connect():
@@ -201,7 +194,7 @@ class _Server:
                     ) from None
                     ) from None
 
 
             if path == "taipy.status.json":
             if path == "taipy.status.json":
-                return self._direct_render_json(self._gui._serve_status(pathlib.Path(template_folder) / path))  # type: ignore[attr-defined]
+                return self.direct_render_json(self._gui._serve_status(pathlib.Path(template_folder) / path))  # type: ignore[attr-defined]
             if (file_path := str(os.path.normpath((base_path := static_folder + os.path.sep) + path))).startswith(
             if (file_path := str(os.path.normpath((base_path := static_folder + os.path.sep) + path))).startswith(
                 base_path
                 base_path
             ) and os.path.isfile(file_path):
             ) and os.path.isfile(file_path):
@@ -239,27 +232,10 @@ class _Server:
 
 
         return taipy_bp
         return taipy_bp
 
 
-    # Update to render as JSX
-    def _render(self, html_fragment, script_paths, style, head, context):
-        template_str = _Server.__RE_OPENING_CURLY.sub(_Server.__OPENING_CURLY, html_fragment)
-        template_str = _Server.__RE_CLOSING_CURLY.sub(_Server.__CLOSING_CURLY, template_str)
-        template_str = template_str.replace('"{!', "{")
-        template_str = template_str.replace('!}"', "}")
-        style = get_style(style)
-        return self._direct_render_json(
-            {
-                "jsx": template_str,
-                "style": (style + os.linesep) if style else "",
-                "head": head or [],
-                "context": context or self._gui._get_default_module_name(),  # type: ignore[attr-defined]
-                "scriptPaths": script_paths,
-            }
-        )
-
-    def _direct_render_json(self, data):
+    def direct_render_json(self, data):
         return jsonify(data)
         return jsonify(data)
 
 
-    def get_flask(self):
+    def get_server_instance(self):
         return self._flask
         return self._flask
 
 
     def get_port(self):
     def get_port(self):
@@ -301,6 +277,9 @@ class _Server:
             if port not in _RuntimeManager().get_used_port() and not _is_port_open(self._host, port):
             if port not in _RuntimeManager().get_used_port() and not _is_port_open(self._host, port):
                 return port
                 return port
 
 
+    def send_ws_message(self, *args, **kwargs):
+        self._ws.emit("message", *args, **kwargs)
+
     def run(
     def run(
         self,
         self,
         host,
         host,
@@ -308,7 +287,7 @@ class _Server:
         client_url,
         client_url,
         debug,
         debug,
         use_reloader,
         use_reloader,
-        flask_log,
+        server_log,
         run_in_thread,
         run_in_thread,
         allow_unsafe_werkzeug,
         allow_unsafe_werkzeug,
         notebook_proxy,
         notebook_proxy,
@@ -321,7 +300,7 @@ class _Server:
         server_url = f"http://{host_value}:{port}"
         server_url = f"http://{host_value}:{port}"
         self._port = port
         self._port = port
         if _is_in_notebook() and notebook_proxy:  # pragma: no cover
         if _is_in_notebook() and notebook_proxy:  # pragma: no cover
-            from .utils.proxy import NotebookProxy
+            from ...utils.proxy import NotebookProxy
 
 
             # Start proxy if not already started
             # Start proxy if not already started
             self._proxy = NotebookProxy(gui=self._gui, listening_port=port)
             self._proxy = NotebookProxy(gui=self._gui, listening_port=port)
@@ -334,7 +313,7 @@ class _Server:
             raise ConnectionError(
             raise ConnectionError(
                 f"Port {port} is already opened on {host} because another application is running on the same port.\nPlease pick another port number and rerun with the 'port=<new_port>' setting.\nYou can also let Taipy choose a port number for you by running with the 'port=\"auto\"' setting."  # noqa: E501
                 f"Port {port} is already opened on {host} because another application is running on the same port.\nPlease pick another port number and rerun with the 'port=<new_port>' setting.\nYou can also let Taipy choose a port number for you by running with the 'port=\"auto\"' setting."  # noqa: E501
             )
             )
-        if not flask_log:
+        if not server_log:
             log = logging.getLogger("werkzeug")
             log = logging.getLogger("werkzeug")
             log.disabled = True
             log.disabled = True
             if not is_running_from_reloader():
             if not is_running_from_reloader():

+ 18 - 0
taipy/gui/servers/request.py

@@ -0,0 +1,18 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+from contextvars import ContextVar
+
+from flask import Request
+from flask.ctx import _AppCtxGlobals
+
+request: ContextVar[Request] = ContextVar("request")
+request_meta: ContextVar[_AppCtxGlobals] = ContextVar("request_meta")

+ 83 - 0
taipy/gui/servers/server.py

@@ -0,0 +1,83 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import os
+import re
+import typing as t
+from abc import ABC, abstractmethod
+from contextvars import ContextVar
+
+from ..utils._css import get_style
+
+
+class _Server(ABC):
+    _RE_OPENING_CURLY = re.compile(r"([^\"])(\{)")
+    _RE_CLOSING_CURLY = re.compile(r"(\})([^\"])")
+    _OPENING_CURLY = r"\1&#x7B;"
+    _CLOSING_CURLY = r"&#x7D;\2"
+    _RESOURCE_HANDLER_ARG = "tprh"
+
+    @abstractmethod
+    def get_server_instance(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def get_port(self) -> int:
+        raise NotImplementedError
+
+    @abstractmethod
+    def send_ws_message(self, *args, **kwargs):
+        raise NotImplementedError
+
+    @abstractmethod
+    def direct_render_json(self, data):
+        raise NotImplementedError
+
+    def render(self, html_fragment, script_paths, style, head, context):
+        template_str = _Server._RE_OPENING_CURLY.sub(_Server._OPENING_CURLY, html_fragment)
+        template_str = _Server._RE_CLOSING_CURLY.sub(_Server._CLOSING_CURLY, template_str)
+        template_str = template_str.replace('"{!', "{")
+        template_str = template_str.replace('!}"', "}")
+        style = get_style(style)
+        return self.direct_render_json(
+            {
+                "jsx": template_str,
+                "style": (style + os.linesep) if style else "",
+                "head": head or [],
+                "context": context or self._gui._get_default_module_name(),  # type: ignore[attr-defined]
+                "scriptPaths": script_paths,
+            }
+        )
+
+    @abstractmethod
+    def run(
+        self,
+        host,
+        port,
+        client_url,
+        debug,
+        use_reloader,
+        server_log,
+        run_in_thread,
+        allow_unsafe_werkzeug,
+        notebook_proxy,
+        port_auto_ranges,
+    ):
+        raise NotImplementedError
+
+    @abstractmethod
+    def stop_thread(self):
+        raise NotImplementedError
+
+
+ServerFrameworks = t.Literal["flask", "fastapi"]
+server_type: ContextVar[ServerFrameworks] = ContextVar("server_type", default="flask")
+server: ContextVar[_Server] = ContextVar("server")

+ 90 - 0
taipy/gui/servers/utils.py

@@ -0,0 +1,90 @@
+# Copyright 2021-2025 Avaiga Private Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+
+import typing as t
+
+from flask import g as flask_meta
+from flask import has_app_context
+from flask import has_request_context as flask_has_request_context
+from flask import request as flask_request
+from flask import send_file as flask_send_file
+from flask import send_from_directory as flask_send_from_directory
+
+from .fastapi import FastAPIServer
+from .flask import FlaskServer
+from .request import request, request_meta
+from .server import ServerFrameworks, _Server, server, server_type
+
+
+def set_server_type(framework: ServerFrameworks) -> None:
+    server_type.set(framework)
+
+
+def get_server_type() -> ServerFrameworks:
+    return server_type.get()
+
+
+def create_server(*args, **kwargs) -> _Server:
+    new_server: t.Union[FlaskServer, FastAPIServer, None] = None
+    if server_type.get() == "flask":
+        new_server = FlaskServer(*args, **kwargs)
+    elif server_type.get() == "fastapi":
+        new_server = FastAPIServer(*args, **kwargs)
+    if new_server is None:
+        raise ValueError(f"Invalid server type: {type}")
+    server.set(new_server)
+    return new_server
+
+
+def send_file(*args, **kwargs):
+    if server_type.get() == "flask":
+        return flask_send_file(*args, **kwargs)
+    elif server_type.get() == "fastapi":
+        raise NotImplementedError("send_file is not supported in FastAPI server")
+
+
+def send_from_directory(*args, **kwargs):
+    if server_type.get() == "flask":
+        return flask_send_from_directory(*args, **kwargs)
+    elif server_type.get() == "fastapi":
+        raise NotImplementedError("send_from_directory is not supported in FastAPI server")
+
+
+def get_request():
+    if server_type.get() == "flask":
+        return flask_request
+    elif server_type.get() == "fastapi":
+        return request.get()
+    return None
+
+
+def get_request_meta():
+    if server_type.get() == "flask":
+        return flask_meta
+    elif server_type.get() == "fastapi":
+        return request_meta.get()
+    return {}
+
+
+def has_server_context():
+    if server_type.get() == "flask":
+        return has_app_context()
+    elif server_type.get() == "fastapi":
+        return True
+    return False
+
+
+def has_request_context():
+    if server_type.get() == "flask":
+        return flask_has_request_context()
+    elif server_type.get() == "fastapi":
+        return True
+    return False

+ 6 - 5
taipy/gui/state.py

@@ -17,8 +17,6 @@ from operator import attrgetter
 from pathlib import Path
 from pathlib import Path
 from types import FrameType, SimpleNamespace
 from types import FrameType, SimpleNamespace
 
 
-from flask import has_app_context
-
 from .utils import _get_module_name_from_frame, _is_in_notebook
 from .utils import _get_module_name_from_frame, _is_in_notebook
 from .utils._attributes import _attrsetter
 from .utils._attributes import _attrsetter
 
 
@@ -150,8 +148,7 @@ class State(SimpleNamespace, metaclass=ABCMeta):
         self._gui.set_favicon(favicon_path, self)
         self._gui.set_favicon(favicon_path, self)
 
 
     @abstractmethod
     @abstractmethod
-    def __getitem__(self, key: str) -> "State":
-        ...
+    def __getitem__(self, key: str) -> "State": ...
 
 
 
 
 class _GuiState(State):
 class _GuiState(State):
@@ -255,7 +252,11 @@ class _GuiState(State):
         return nullcontext()
         return nullcontext()
 
 
     def _notebook_context(self, gui: "Gui"):
     def _notebook_context(self, gui: "Gui"):
-        return gui.get_flask_app().app_context() if not has_app_context() and _is_in_notebook() else nullcontext()
+        from .servers import has_server_context
+
+        return (
+            gui.get_server_instance().app_context() if not has_server_context() and _is_in_notebook() else nullcontext()
+        )
 
 
     def _get_placeholder(self, name: str):
     def _get_placeholder(self, name: str):
         if name in _GuiState.__placeholder_attrs:
         if name in _GuiState.__placeholder_attrs:

+ 15 - 9
taipy/gui/utils/_locals_context.py

@@ -14,8 +14,6 @@ from __future__ import annotations
 import contextlib
 import contextlib
 import typing as t
 import typing as t
 
 
-from flask import g
-
 
 
 class _LocalsContext:
 class _LocalsContext:
     __ctx_g_name = "locals_context"
     __ctx_g_name = "locals_context"
@@ -51,26 +49,34 @@ class _LocalsContext:
 
 
     @contextlib.contextmanager
     @contextlib.contextmanager
     def set_locals_context(self, context: t.Optional[str]) -> t.Iterator[None]:
     def set_locals_context(self, context: t.Optional[str]) -> t.Iterator[None]:
+        from ..servers import get_request_meta
+
         has_set_context = False
         has_set_context = False
         try:
         try:
             if context in self._locals_map:
             if context in self._locals_map:
-                if hasattr(g, _LocalsContext.__ctx_g_name):
-                    self._lc_stack.append(getattr(g, _LocalsContext.__ctx_g_name))
-                setattr(g, _LocalsContext.__ctx_g_name, context)
+                if hasattr(get_request_meta(), _LocalsContext.__ctx_g_name):
+                    self._lc_stack.append(getattr(get_request_meta(), _LocalsContext.__ctx_g_name))
+                setattr(get_request_meta(), _LocalsContext.__ctx_g_name, context)
                 has_set_context = True
                 has_set_context = True
             yield
             yield
         finally:
         finally:
-            if has_set_context and hasattr(g, _LocalsContext.__ctx_g_name):
+            if has_set_context and hasattr(get_request_meta(), _LocalsContext.__ctx_g_name):
                 if len(self._lc_stack) > 0:
                 if len(self._lc_stack) > 0:
-                    setattr(g, _LocalsContext.__ctx_g_name, self._lc_stack.pop())
+                    setattr(get_request_meta(), _LocalsContext.__ctx_g_name, self._lc_stack.pop())
                 else:
                 else:
-                    delattr(g, _LocalsContext.__ctx_g_name)
+                    delattr(get_request_meta(), _LocalsContext.__ctx_g_name)
 
 
     def get_locals(self) -> t.Dict[str, t.Any]:
     def get_locals(self) -> t.Dict[str, t.Any]:
         return self.get_default() if (context := self.get_context()) is None else self._locals_map[context]
         return self.get_default() if (context := self.get_context()) is None else self._locals_map[context]
 
 
     def get_context(self) -> t.Optional[str]:
     def get_context(self) -> t.Optional[str]:
-        return getattr(g, _LocalsContext.__ctx_g_name) if hasattr(g, _LocalsContext.__ctx_g_name) else None
+        from ..servers import get_request_meta
+
+        return (
+            getattr(get_request_meta(), _LocalsContext.__ctx_g_name)
+            if hasattr(get_request_meta(), _LocalsContext.__ctx_g_name)
+            else None
+        )
 
 
     def is_default(self) -> bool:
     def is_default(self) -> bool:
         return self.get_default() == self.get_locals()
         return self.get_default() == self.get_locals()

+ 1 - 1
taipy/gui/utils/proxy.py

@@ -68,7 +68,7 @@ class _TaipyReverseProxyResource(Resource):
         )
         )
 
 
     def _get_port(self):
     def _get_port(self):
-        return self._gui._server._port
+        return self._gui._server.get_port()
 
 
     def render(self, request):
     def render(self, request):
         port = self._get_port()
         port = self._get_port()

+ 5 - 5
tests/gui/actions/test_download.py

@@ -29,10 +29,10 @@ def test_download(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_flask()))
+    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_server_instance()))
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         download(gui._Gui__state, "some text", "filename.txt", "on_download_action")  # type: ignore[attr-defined]
         download(gui._Gui__state, "some text", "filename.txt", "on_download_action")  # type: ignore[attr-defined]
 
 
@@ -53,10 +53,10 @@ def test_download_fn(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_flask()))
+    ws_client = gui._server._ws.test_client(t.cast(Flask, gui._server.get_server_instance()))
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         download(gui._Gui__state, "some text", "filename.txt", on_download_action)  # type: ignore[attr-defined]
         download(gui._Gui__state, "some text", "filename.txt", on_download_action)  # type: ignore[attr-defined]
 
 
@@ -66,7 +66,7 @@ def test_download_fn(gui: Gui, helpers):
         "DF",
         "DF",
         {"name": "filename.txt", "context": "test_download"},
         {"name": "filename.txt", "context": "test_download"},
     )
     )
-    assert "onAction" in received_messages[0]["args"] # inner function is treated as lambda
+    assert "onAction" in received_messages[0]["args"]  # inner function is treated as lambda
 
 
 
 
 def test_bad_download(gui: Gui, helpers):
 def test_bad_download(gui: Gui, helpers):

+ 2 - 2
tests/gui/actions/test_get_module_context.py

@@ -26,7 +26,7 @@ def test_get_module_context(gui: Gui, helpers):
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         g.client_id = cid
         g.client_id = cid
         module = get_module_context(gui._Gui__state)  # type: ignore[attr-defined]
         module = get_module_context(gui._Gui__state)  # type: ignore[attr-defined]
         assert module == "test_get_module_context"
         assert module == "test_get_module_context"
@@ -47,7 +47,7 @@ def test_get_module_name_from_state(gui: Gui, helpers):
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         g.client_id = cid
         g.client_id = cid
         module = get_module_name_from_state(gui._Gui__state)  # type: ignore[attr-defined]
         module = get_module_name_from_state(gui._Gui__state)  # type: ignore[attr-defined]
         assert module == "test_get_module_context"
         assert module == "test_get_module_context"

+ 1 - 1
tests/gui/actions/test_get_state_id.py

@@ -26,7 +26,7 @@ def test_get_state_id(gui: Gui, helpers):
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         g.client_id = cid
         g.client_id = cid
         assert cid == get_state_id(gui._Gui__state)  # type: ignore[attr-defined]
         assert cid == get_state_id(gui._Gui__state)  # type: ignore[attr-defined]
 
 

+ 1 - 1
tests/gui/actions/test_get_user_content_url.py

@@ -27,7 +27,7 @@ def test_get_content_url(gui: Gui, helpers):
     # WS client and emit
     # WS client and emit
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         url = get_user_content_url(gui._Gui__state, "path")  # type: ignore[attr-defined]
         url = get_user_content_url(gui._Gui__state, "path")  # type: ignore[attr-defined]
         assert url == "/taipy-user-content/path?client_id=test"
         assert url == "/taipy-user-content/path?client_id=test"

+ 2 - 2
tests/gui/actions/test_hold_control.py

@@ -25,10 +25,10 @@ def test_hold_control(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())  # type: ignore[arg-type]
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         hold_control(gui._Gui__state)  # type: ignore[attr-defined]
         hold_control(gui._Gui__state)  # type: ignore[attr-defined]
 
 

+ 2 - 2
tests/gui/actions/test_invoke_callback.py

@@ -19,7 +19,7 @@ from taipy.gui import Gui, Markdown, State
 
 
 @contextlib.contextmanager
 @contextlib.contextmanager
 def get_state(gui: Gui, state_id: str):
 def get_state(gui: Gui, state_id: str):
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         client_id = gui._bindings()._get_or_create_scope(state_id)[0]
         client_id = gui._bindings()._get_or_create_scope(state_id)[0]
         gui._Gui__set_client_id_in_context(client_id)  # type: ignore[attr-defined]
         gui._Gui__set_client_id_in_context(client_id)  # type: ignore[attr-defined]
         yield gui._Gui__state  # type: ignore[attr-defined]
         yield gui._Gui__state  # type: ignore[attr-defined]
@@ -65,7 +65,7 @@ def test_invoke_callback_sid(gui: Gui, helpers):
 
 
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         g.client_id = base_sid
         g.client_id = base_sid
         gui.invoke_callback(cid, user_callback, [])
         gui.invoke_callback(cid, user_callback, [])
         assert g.client_id == base_sid
         assert g.client_id == base_sid

+ 2 - 2
tests/gui/actions/test_navigate.py

@@ -25,10 +25,10 @@ def test_navigate(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())  # type: ignore[arg-type]
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         navigate(gui._Gui__state, "test")  # type: ignore[attr-defined]
         navigate(gui._Gui__state, "test")  # type: ignore[attr-defined]
 
 

+ 4 - 4
tests/gui/actions/test_notify.py

@@ -25,10 +25,10 @@ def test_notify(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())  # type: ignore[arg-type]
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
         id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
         assert id == "id"
         assert id == "id"
@@ -53,11 +53,11 @@ def test_close_notification(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())  # type: ignore[arg-type]
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
         id = notify(gui._Gui__state, "Info", "Message", id="id")  # type: ignore[attr-defined]
         close_notification(gui._Gui__state, id)  # type: ignore[attr-defined, arg-type]
         close_notification(gui._Gui__state, id)  # type: ignore[attr-defined, arg-type]

+ 2 - 2
tests/gui/actions/test_resume_control.py

@@ -25,10 +25,10 @@ def test_resume_control(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())  # type: ignore[arg-type]
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())  # type: ignore[arg-type]
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         resume_control(gui._Gui__state)  # type: ignore[attr-defined]
         resume_control(gui._Gui__state)  # type: ignore[attr-defined]
 
 

+ 1 - 1
tests/gui/config/test_cli.py

@@ -76,7 +76,7 @@ def test_gui_service_arguments_hierarchy():
     assert not service_config["debug"]
     assert not service_config["debug"]
     assert not service_config["extended_status"]
     assert not service_config["extended_status"]
     assert service_config["favicon"] is None
     assert service_config["favicon"] is None
-    assert not service_config["flask_log"]
+    assert not service_config["server_log"]
     assert service_config["host"] == "127.0.0.1"
     assert service_config["host"] == "127.0.0.1"
     assert service_config["light_theme"] is None
     assert service_config["light_theme"] is None
     assert service_config["margin"] is None
     assert service_config["margin"] is None

+ 34 - 33
tests/gui/data/test_pandas_data_accessor.py

@@ -32,21 +32,24 @@ class MockDataFormat:
     LIST = Mock(value="list")
     LIST = Mock(value="list")
     CSV = Mock(value="csv")
     CSV = Mock(value="csv")
 
 
+
 @pytest.fixture
 @pytest.fixture
 def pandas_accessor():
 def pandas_accessor():
     gui = Mock()
     gui = Mock()
     return _PandasDataAccessor(gui=gui)
     return _PandasDataAccessor(gui=gui)
 
 
+
 @pytest.fixture
 @pytest.fixture
 def sample_df():
 def sample_df():
     data = {
     data = {
         "StringCol": ["Apple", "Banana", "Cherry", "apple"],
         "StringCol": ["Apple", "Banana", "Cherry", "apple"],
         "NumberCol": [10, 20, 30, 40],
         "NumberCol": [10, 20, 30, 40],
         "BoolCol": [True, False, True, False],
         "BoolCol": [True, False, True, False],
-        "DateCol": pandas.to_datetime(["2020-01-01", "2021-06-15", "2022-08-22", "2023-03-05"])
+        "DateCol": pandas.to_datetime(["2020-01-01", "2021-06-15", "2022-08-22", "2023-03-05"]),
     }
     }
     return pandas.DataFrame(data)
     return pandas.DataFrame(data)
 
 
+
 def test_simple_data(gui: Gui, helpers, small_dataframe):
 def test_simple_data(gui: Gui, helpers, small_dataframe):
     accessor = _PandasDataAccessor(gui)
     accessor = _PandasDataAccessor(gui)
     pd = pandas.DataFrame(data=small_dataframe)
     pd = pandas.DataFrame(data=small_dataframe)
@@ -101,7 +104,7 @@ def test_style(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
         value = accessor.get_data("x", pd, {"start": 0, "end": 1, "styles": {"st": "test_style"}}, _DataFormat.JSON)[
         value = accessor.get_data("x", pd, {"start": 0, "end": 1, "styles": {"st": "test_style"}}, _DataFormat.JSON)[
             "value"
             "value"
@@ -120,7 +123,7 @@ def test_tooltip(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         gui._bind_var_val("tt", tt)
         gui._bind_var_val("tt", tt)
         gui._get_locals_bind_from_context(None)["tt"] = tt
         gui._get_locals_bind_from_context(None)["tt"] = tt
         g.client_id = cid
         g.client_id = cid
@@ -139,7 +142,7 @@ def test_format_fn(gui: Gui, helpers, small_dataframe):
     pd = pandas.DataFrame(data=small_dataframe)
     pd = pandas.DataFrame(data=small_dataframe)
     gui.run(run_server=False)
     gui.run(run_server=False)
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         gui._bind_var_val("ff", ff)
         gui._bind_var_val("ff", ff)
         gui._get_locals_bind_from_context(None)["ff"] = ff
         gui._get_locals_bind_from_context(None)["ff"] = ff
         g.client_id = cid
         g.client_id = cid
@@ -279,58 +282,54 @@ def test_filter_by_date(gui: Gui, helpers, small_dataframe):
     value = accessor.get_data("x", pd, query, _DataFormat.JSON)
     value = accessor.get_data("x", pd, query, _DataFormat.JSON)
     assert len(value["value"]["data"]) == 1
     assert len(value["value"]["data"]) == 1
 
 
+
 def test_contains_case_sensitive(pandas_accessor, sample_df):
 def test_contains_case_sensitive(pandas_accessor, sample_df):
-    payload = {
-        "filters": [{"col": "StringCol", "value": "Apple", "action": "contains", "matchCase": True}]
-    }
+    payload = {"filters": [{"col": "StringCol", "value": "Apple", "action": "contains", "matchCase": True}]}
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
-    filtered_data = pandas.DataFrame(result["value"]['data'])
+    filtered_data = pandas.DataFrame(result["value"]["data"])
 
 
     assert len(filtered_data) == 1
     assert len(filtered_data) == 1
-    assert filtered_data.iloc[0]['StringCol'] == 'Apple'
+    assert filtered_data.iloc[0]["StringCol"] == "Apple"
+
 
 
 def test_contains_case_insensitive(pandas_accessor, sample_df):
 def test_contains_case_insensitive(pandas_accessor, sample_df):
-    payload = {
-        "filters": [{"col": "StringCol", "value": "apple", "action": "contains", "matchCase": False}]
-    }
+    payload = {"filters": [{"col": "StringCol", "value": "apple", "action": "contains", "matchCase": False}]}
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
-    filtered_data = pandas.DataFrame(result["value"]['data'])
+    filtered_data = pandas.DataFrame(result["value"]["data"])
 
 
     assert len(filtered_data) == 2
     assert len(filtered_data) == 2
-    assert 'Apple' in filtered_data['StringCol'].values
-    assert 'apple' in filtered_data['StringCol'].values
+    assert "Apple" in filtered_data["StringCol"].values
+    assert "apple" in filtered_data["StringCol"].values
+
 
 
 def test_equals_case_sensitive(pandas_accessor, sample_df):
 def test_equals_case_sensitive(pandas_accessor, sample_df):
-    payload = {
-        "filters": [{"col": "StringCol", "value": "Apple", "action": "==", "matchCase": True}]
-    }
+    payload = {"filters": [{"col": "StringCol", "value": "Apple", "action": "==", "matchCase": True}]}
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
-    filtered_data = pandas.DataFrame(result["value"]['data'])
+    filtered_data = pandas.DataFrame(result["value"]["data"])
 
 
     assert len(filtered_data) == 1
     assert len(filtered_data) == 1
-    assert filtered_data.iloc[0]['StringCol'] == 'Apple'
+    assert filtered_data.iloc[0]["StringCol"] == "Apple"
+
 
 
 def test_equals_case_insensitive(pandas_accessor, sample_df):
 def test_equals_case_insensitive(pandas_accessor, sample_df):
-    payload = {
-        "filters": [{"col": "StringCol", "value": "apple", "action": "==", "matchCase": False}]
-    }
+    payload = {"filters": [{"col": "StringCol", "value": "apple", "action": "==", "matchCase": False}]}
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
-    filtered_data = pandas.DataFrame(result["value"]['data'])
+    filtered_data = pandas.DataFrame(result["value"]["data"])
 
 
     assert len(filtered_data) == 2
     assert len(filtered_data) == 2
-    assert 'Apple' in filtered_data['StringCol'].values
-    assert 'apple' in filtered_data['StringCol'].values
+    assert "Apple" in filtered_data["StringCol"].values
+    assert "apple" in filtered_data["StringCol"].values
+
 
 
 def test_not_equals_case_insensitive(pandas_accessor, sample_df):
 def test_not_equals_case_insensitive(pandas_accessor, sample_df):
-    payload = {
-        "filters": [{"col": "StringCol", "value": "apple", "action": "!=", "matchCase": False}]
-    }
+    payload = {"filters": [{"col": "StringCol", "value": "apple", "action": "!=", "matchCase": False}]}
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
     result = pandas_accessor.get_data("test_var", sample_df, payload, MockDataFormat.LIST)
-    filtered_data = pandas.DataFrame(result["value"]['data'])
+    filtered_data = pandas.DataFrame(result["value"]["data"])
 
 
     assert len(filtered_data) == 2
     assert len(filtered_data) == 2
-    assert 'Banana' in filtered_data['StringCol'].values
-    assert 'Cherry' in filtered_data['StringCol'].values
+    assert "Banana" in filtered_data["StringCol"].values
+    assert "Cherry" in filtered_data["StringCol"].values
+
 
 
 def test_decimator(gui: Gui, helpers, small_dataframe):
 def test_decimator(gui: Gui, helpers, small_dataframe):
     a_decimator = ScatterDecimator(threshold=1)  # noqa: F841
     a_decimator = ScatterDecimator(threshold=1)  # noqa: F841
@@ -348,7 +347,7 @@ def test_decimator(gui: Gui, helpers, small_dataframe):
     cid = helpers.create_scope_and_get_sid(gui)
     cid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         g.client_id = cid
         g.client_id = cid
 
 
         ret_data = accessor.get_data(
         ret_data = accessor.get_data(
@@ -431,6 +430,7 @@ def test_csv(gui, small_dataframe):
     assert path is not None
     assert path is not None
     assert os.path.getsize(path) > 0
     assert os.path.getsize(path) > 0
 
 
+
 def test_multi_index(gui):
 def test_multi_index(gui):
     pandas_accessor = _PandasDataAccessor(gui)
     pandas_accessor = _PandasDataAccessor(gui)
 
 
@@ -443,6 +443,7 @@ def test_multi_index(gui):
         assert result.get("error") is None
         assert result.get("error") is None
         assert result["value"] is not None
         assert result["value"] is not None
 
 
+
 def test_multi_index_columns(gui):
 def test_multi_index_columns(gui):
     pandas_accessor = _PandasDataAccessor(gui)
     pandas_accessor = _PandasDataAccessor(gui)
 
 

+ 5 - 3
tests/gui/gui_specific/test_broadcast.py

@@ -19,11 +19,12 @@ from taipy.gui import Gui
 
 
 @contextlib.contextmanager
 @contextlib.contextmanager
 def get_state(gui: Gui, state_id: str):
 def get_state(gui: Gui, state_id: str):
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         client_id = gui._bindings()._get_or_create_scope(state_id)[0]
         client_id = gui._bindings()._get_or_create_scope(state_id)[0]
         gui._Gui__set_client_id_in_context(client_id)  # type: ignore[attr-defined]
         gui._Gui__set_client_id_in_context(client_id)  # type: ignore[attr-defined]
         yield gui._Gui__state  # type: ignore[attr-defined]
         yield gui._Gui__state  # type: ignore[attr-defined]
 
 
+
 def test_multiple_scopes(gui: Gui):
 def test_multiple_scopes(gui: Gui):
     var = 1  # noqa: F841
     var = 1  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
@@ -77,6 +78,7 @@ def test_broadcast_change(gui: Gui):
         assert state2.v1 == "none"
         assert state2.v1 == "none"
         assert state2.v2 == 2
         assert state2.v2 == 2
 
 
+
 def test_broadcast_changes(gui: Gui):
 def test_broadcast_changes(gui: Gui):
     # Bind test variables
     # Bind test variables
     v1 = "none"  # noqa: F841
     v1 = "none"  # noqa: F841
@@ -87,7 +89,7 @@ def test_broadcast_changes(gui: Gui):
     s1, _ = gui._bindings()._get_or_create_scope("s1")
     s1, _ = gui._bindings()._get_or_create_scope("s1")
     s2, _ = gui._bindings()._get_or_create_scope("s2")
     s2, _ = gui._bindings()._get_or_create_scope("s2")
 
 
-    changes = { "v1": "some", "v2": 2}
+    changes = {"v1": "some", "v2": 2}
     gui.broadcast_changes(changes)
     gui.broadcast_changes(changes)
     with get_state(gui, s1) as state1:
     with get_state(gui, s1) as state1:
         assert state1.v1 == "some"
         assert state1.v1 == "some"
@@ -104,7 +106,7 @@ def test_broadcast_changes(gui: Gui):
         assert state2.v1 == "more"
         assert state2.v1 == "more"
         assert state2.v2 == 3
         assert state2.v2 == 3
 
 
-    gui.broadcast_changes({ "v1": "more yet"}, v2=4)
+    gui.broadcast_changes({"v1": "more yet"}, v2=4)
     with get_state(gui, s1) as state1:
     with get_state(gui, s1) as state1:
         assert state1.v1 == "more yet"
         assert state1.v1 == "more yet"
         assert state1.v2 == 4
         assert state1.v2 == 4

+ 1 - 2
tests/gui/gui_specific/test_favicon.py

@@ -16,14 +16,13 @@ from taipy.gui import Gui, Markdown
 
 
 
 
 def test_favicon(gui: Gui, helpers):
 def test_favicon(gui: Gui, helpers):
-
     with warnings.catch_warnings(record=True):
     with warnings.catch_warnings(record=True):
         gui._set_frame(inspect.currentframe())
         gui._set_frame(inspect.currentframe())
         gui.add_page("test", Markdown("#This is a page"))
         gui.add_page("test", Markdown("#This is a page"))
         gui.run(run_server=False)
         gui.run(run_server=False)
         client = gui._server.test_client()
         client = gui._server.test_client()
         # WS client and emit
         # WS client and emit
-        ws_client = gui._server._ws.test_client(gui._server.get_flask())
+        ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         sid = helpers.create_scope_and_get_sid(gui)
         sid = helpers.create_scope_and_get_sid(gui)
         client.get(f"/taipy-jsx/test/?client_id={sid}")
         client.get(f"/taipy-jsx/test/?client_id={sid}")

+ 13 - 10
tests/gui/gui_specific/test_gui.py

@@ -29,7 +29,7 @@ def test__get_real_var_name(gui: Gui):
     assert res[1] == ""
     assert res[1] == ""
 
 
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         with gui._set_locals_context(_get_module_name_from_frame(frame)) if frame else nullcontext():
         with gui._set_locals_context(_get_module_name_from_frame(frame)) if frame else nullcontext():
             with pytest.raises(NameError):
             with pytest.raises(NameError):
                 res = gui._get_real_var_name(f"{_TaipyContent.get_hash()}_var")
                 res = gui._get_real_var_name(f"{_TaipyContent.get_hash()}_var")
@@ -37,13 +37,13 @@ def test__get_real_var_name(gui: Gui):
 
 
 def test__get_user_instance(gui: Gui):
 def test__get_user_instance(gui: Gui):
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         gui._get_user_instance("", type(None))
         gui._get_user_instance("", type(None))
 
 
 
 
 def test__refresh_expr(gui: Gui):
 def test__refresh_expr(gui: Gui):
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         res = gui._refresh_expr("var", None)
         res = gui._refresh_expr("var", None)
         assert res is None
         assert res is None
 
 
@@ -51,7 +51,7 @@ def test__refresh_expr(gui: Gui):
 def test__tbl_cols(gui: Gui):
 def test__tbl_cols(gui: Gui):
     data = pd.DataFrame({"col1": [0, 1, 2], "col2": [True, True, False]})
     data = pd.DataFrame({"col1": [0, 1, 2], "col2": [True, True, False]})
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         res = gui._tbl_cols(True, None, json.dumps({}), json.dumps({"data": "data"}), data=data)
         res = gui._tbl_cols(True, None, json.dumps({}), json.dumps({"data": "data"}), data=data)
         assert isinstance(res, str)
         assert isinstance(res, str)
 
 
@@ -66,7 +66,7 @@ def test__tbl_cols(gui: Gui):
 def test__chart_conf(gui: Gui):
 def test__chart_conf(gui: Gui):
     data = pd.DataFrame({"col1": [0, 1, 2], "col2": [True, True, False]})
     data = pd.DataFrame({"col1": [0, 1, 2], "col2": [True, True, False]})
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         res = gui._chart_conf(True, None, json.dumps({}), json.dumps({"data": "data"}), data=data)
         res = gui._chart_conf(True, None, json.dumps({}), json.dumps({"data": "data"}), data=data)
         assert isinstance(res, str)
         assert isinstance(res, str)
 
 
@@ -84,20 +84,23 @@ def test__chart_conf(gui: Gui):
 
 
 def test__get_valid_adapter_result(gui: Gui):
 def test__get_valid_adapter_result(gui: Gui):
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         res = gui._get_valid_adapter_result(("id", "label"))
         res = gui._get_valid_adapter_result(("id", "label"))
         assert isinstance(res, tuple)
         assert isinstance(res, tuple)
         assert res[0] == "id"
         assert res[0] == "id"
 
 
-def test_on_action_call(gui:Gui):
+
+def test_on_action_call(gui: Gui):
     an_id = "my_id"
     an_id = "my_id"
 
 
     a_non_action_payload = {"a": "b"}
     a_non_action_payload = {"a": "b"}
+
     def on_action(state, id, payload):
     def on_action(state, id, payload):
         assert id == an_id
         assert id == an_id
         assert payload is a_non_action_payload
         assert payload is a_non_action_payload
 
 
     an_action_payload = {"action": "on_an_action"}
     an_action_payload = {"action": "on_an_action"}
+
     def on_an_action(state, id, payload):
     def on_an_action(state, id, payload):
         assert id == an_id
         assert id == an_id
         assert payload is an_action_payload
         assert payload is an_action_payload
@@ -106,6 +109,6 @@ def test_on_action_call(gui:Gui):
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
 
 
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
-        gui._Gui__on_action(an_id, a_non_action_payload) # type: ignore[attr-defined]
-        gui._Gui__on_action(an_id, an_action_payload) # type: ignore[attr-defined]
+    with gui.get_server_instance().app_context():
+        gui._Gui__on_action(an_id, a_non_action_payload)  # type: ignore[attr-defined]
+        gui._Gui__on_action(an_id, an_action_payload)  # type: ignore[attr-defined]

+ 1 - 1
tests/gui/gui_specific/test_locals_context.py

@@ -19,7 +19,7 @@ from taipy.gui.utils._locals_context import _LocalsContext
 def test_locals_context(gui: Gui):
 def test_locals_context(gui: Gui):
     lc = _LocalsContext()
     lc = _LocalsContext()
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         with pytest.raises(KeyError):
         with pytest.raises(KeyError):
             lc.get_default()
             lc.get_default()
         current_locals = locals()
         current_locals = locals()

+ 2 - 2
tests/gui/gui_specific/test_navigate.py

@@ -25,7 +25,7 @@ def test_navigate(gui: Gui, helpers):
         gui.run(run_server=False)
         gui.run(run_server=False)
         client = gui._server.test_client()
         client = gui._server.test_client()
         # WS client and emit
         # WS client and emit
-        ws_client = gui._server._ws.test_client(gui._server.get_flask())
+        ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         sid = helpers.create_scope_and_get_sid(gui)
         sid = helpers.create_scope_and_get_sid(gui)
         client.get(f"/taipy-jsx/test/?client_id={sid}")
         client.get(f"/taipy-jsx/test/?client_id={sid}")
@@ -44,7 +44,7 @@ def test_navigate_to_no_route(gui: Gui, helpers):
         gui.run(run_server=False)
         gui.run(run_server=False)
         client = gui._server.test_client()
         client = gui._server.test_client()
         # WS client and emit
         # WS client and emit
-        ws_client = gui._server._ws.test_client(gui._server.get_flask())
+        ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         # Get the jsx once so that the page will be evaluated -> variable will be registered
         sid = helpers.create_scope_and_get_sid(gui)
         sid = helpers.create_scope_and_get_sid(gui)
         client.get(f"/taipy-jsx/test/?client_id={sid}")
         client.get(f"/taipy-jsx/test/?client_id={sid}")

+ 2 - 6
tests/gui/gui_specific/test_state.py

@@ -24,7 +24,7 @@ def test_state(gui: Gui):
     gui.add_page("page1", md_page1)
     gui.add_page("page1", md_page1)
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     state = gui._Gui__state  # type: ignore[attr-defined]
     state = gui._Gui__state  # type: ignore[attr-defined]
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         assert state.a == 10
         assert state.a == 10
         assert state["page1"].a == 20
         assert state["page1"].a == 20
         assert state["tests.gui.gui_specific.state_asset.page1"].a == 20
         assert state["tests.gui.gui_specific.state_asset.page1"].a == 20
@@ -51,11 +51,7 @@ def test_state(gui: Gui):
 
 
         assert state._get_placeholder("_taipy_p1") == 10
         assert state._get_placeholder("_taipy_p1") == 10
 
 
-        assert state._get_placeholder_attrs() == (
-            "_taipy_p1",
-            "_current_context",
-            "__state_id"
-        )
+        assert state._get_placeholder_attrs() == ("_taipy_p1", "_current_context", "__state_id")
 
 
         assert get_a(state) == 20
         assert get_a(state) == 20
 
 

+ 1 - 1
tests/gui/gui_specific/test_variable_binding.py

@@ -34,7 +34,7 @@ def test_variable_binding(helpers):
     assert gui._bindings().x == x
     assert gui._bindings().x == x
     assert gui._bindings().y == y
     assert gui._bindings().y == y
     assert gui._bindings().z == z
     assert gui._bindings().z == z
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         assert callable(gui._get_user_function("another_function"))
         assert callable(gui._get_user_function("another_function"))
     helpers.test_cleanup()
     helpers.test_cleanup()
 
 

+ 1 - 1
tests/gui/gui_specific/test_variable_directory.py

@@ -19,7 +19,7 @@ from .state_asset.page2 import md_page2
 
 
 def test_variable_directory_dyanmic_process(gui: Gui):
 def test_variable_directory_dyanmic_process(gui: Gui):
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         locals_context = _LocalsContext()
         locals_context = _LocalsContext()
         variable_directory = _VariableDirectory(locals_context)
         variable_directory = _VariableDirectory(locals_context)
         page1_module = str(md_page1._get_module_name())
         page1_module = str(md_page1._get_module_name())

+ 1 - 1
tests/gui/helpers.py

@@ -154,7 +154,7 @@ class Helpers:
                 client_url=gui._get_config("client_url", "http://localhost:{port}"),
                 client_url=gui._get_config("client_url", "http://localhost:{port}"),
                 debug=False,
                 debug=False,
                 use_reloader=False,
                 use_reloader=False,
-                flask_log=False,
+                server_log=False,
                 run_in_thread=True,
                 run_in_thread=True,
                 allow_unsafe_werkzeug=False,
                 allow_unsafe_werkzeug=False,
                 notebook_proxy=False,
                 notebook_proxy=False,

+ 1 - 1
tests/gui/long_runnig/test_long_running.py

@@ -35,7 +35,7 @@ def test_long_callback(gui: Gui):
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     state = gui._Gui__state  # type: ignore[attr-defined]
     state = gui._Gui__state  # type: ignore[attr-defined]
 
 
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         assert state.status is None
         assert state.status is None
         invoke_long_callback(state, heavy_function)
         invoke_long_callback(state, heavy_function)
         invoke_long_callback(state, heavy_function_with_exception)
         invoke_long_callback(state, heavy_function_with_exception)

+ 4 - 4
tests/gui/server/http/test_file_upload.py

@@ -103,7 +103,7 @@ def test_file_upload_multiple(gui: Gui, helpers):
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         gui._bind_var_val(var_name, None)
         gui._bind_var_val(var_name, None)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = _DataScopes._GLOBAL_ID
     sid = _DataScopes._GLOBAL_ID
@@ -134,13 +134,13 @@ def test_file_upload_folder(gui: Gui, helpers):
 
 
     sid = _DataScopes._GLOBAL_ID
     sid = _DataScopes._GLOBAL_ID
     files = [(io.BytesIO(b"(^~^)"), "cutey.txt"), (io.BytesIO(b"(^~^)"), "cute_nested.txt")]
     files = [(io.BytesIO(b"(^~^)"), "cutey.txt"), (io.BytesIO(b"(^~^)"), "cute_nested.txt")]
-    folders = [ ["folder"], ["folder", "nested"] ]
+    folders = [["folder"], ["folder", "nested"]]
     for file, folder in zip(files, folders):
     for file, folder in zip(files, folders):
         path = os.path.join(*folder, file[1])
         path = os.path.join(*folder, file[1])
         response = flask_client.post(
         response = flask_client.post(
             f"/taipy-uploads?client_id={sid}",
             f"/taipy-uploads?client_id={sid}",
             data={"var_name": "cute_varname", "blob": file, "path": path},
             data={"var_name": "cute_varname", "blob": file, "path": path},
-            content_type="multipart/form-data"
+            content_type="multipart/form-data",
         )
         )
         assert response.status_code == 200
         assert response.status_code == 200
-        assert os.path.isfile( os.path.join( gui._get_config("upload_folder", tempfile.gettempdir()), path) )
+        assert os.path.isfile(os.path.join(gui._get_config("upload_folder", tempfile.gettempdir()), path))

+ 1 - 1
tests/gui/server/ws/test_a.py

@@ -30,7 +30,7 @@ def test_a_button_pressed(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 1 - 1
tests/gui/server/ws/test_broadcast.py

@@ -29,7 +29,7 @@ def test_broadcast(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 1 - 1
tests/gui/server/ws/test_df.py

@@ -29,7 +29,7 @@ def test_download_file(gui: Gui, helpers):
 
 
     gui.run(run_server=False)
     gui.run(run_server=False)
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "do_something"})
     ws_client.emit("message", {"client_id": sid, "type": "A", "name": "my_button", "payload": "do_something"})

+ 1 - 1
tests/gui/server/ws/test_du.py

@@ -32,7 +32,7 @@ def test_du_table_data_fetched(gui: Gui, helpers, csvdata):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 2 - 2
tests/gui/server/ws/test_on_change.py

@@ -29,7 +29,7 @@ def test_default_on_change(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
@@ -57,7 +57,7 @@ def test_specific_on_change(gui: Gui, helpers):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 1 - 1
tests/gui/server/ws/test_ru.py

@@ -29,7 +29,7 @@ def test_ru_selector(gui: Gui, helpers, csvdata):
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 1 - 1
tests/gui/server/ws/test_u.py

@@ -26,7 +26,7 @@ def ws_u_assert_template(gui: Gui, helpers, value_before_update, value_after_upd
     gui.run(run_server=False)
     gui.run(run_server=False)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     sid = helpers.create_scope_and_get_sid(gui)
     sid = helpers.create_scope_and_get_sid(gui)
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")
     flask_client.get(f"/taipy-jsx/test?client_id={sid}")

+ 2 - 2
tests/gui/server/ws/test_with.py

@@ -26,14 +26,14 @@ def test_sending_messages_in_group(gui: Gui, helpers):
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     flask_client = gui._server.test_client()
     flask_client = gui._server.test_client()
     # WS client and emit
     # WS client and emit
-    ws_client = gui._server._ws.test_client(gui._server.get_flask())
+    ws_client = gui._server._ws.test_client(gui._server.get_server_instance())
     cid = _DataScopes._GLOBAL_ID
     cid = _DataScopes._GLOBAL_ID
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     # Get the jsx once so that the page will be evaluated -> variable will be registered
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     flask_client.get(f"/taipy-jsx/test?client_id={cid}")
     assert gui._bindings()._get_all_scopes()[cid].name == "World!"  # type: ignore
     assert gui._bindings()._get_all_scopes()[cid].name == "World!"  # type: ignore
     assert gui._bindings()._get_all_scopes()[cid].btn_id == "button1"  # type: ignore
     assert gui._bindings()._get_all_scopes()[cid].btn_id == "button1"  # type: ignore
 
 
-    with gui.get_flask_app().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
+    with gui.get_server_instance().test_request_context(f"/taipy-jsx/test/?client_id={cid}", data={"client_id": cid}):
         with gui as aGui:
         with gui as aGui:
             aGui._Gui__state.name = "Monde!"
             aGui._Gui__state.name = "Monde!"
             aGui._Gui__state.btn_id = "button2"
             aGui._Gui__state.btn_id = "button2"

+ 6 - 6
tests/gui/utils/test_evaluator.py

@@ -21,7 +21,7 @@ from taipy.gui.utils.types import _TaipyNumber
 def test_unbind_variable_in_expression(gui: Gui, helpers):
 def test_unbind_variable_in_expression(gui: Gui, helpers):
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     with warnings.catch_warnings(record=True) as records:
     with warnings.catch_warnings(record=True) as records:
-        with gui.get_flask_app().app_context():
+        with gui.get_server_instance().app_context():
             gui._evaluate_expr("{x}")
             gui._evaluate_expr("{x}")
             warns = helpers.get_taipy_warnings(records)
             warns = helpers.get_taipy_warnings(records)
             assert len(warns) == 3
             assert len(warns) == 3
@@ -35,7 +35,7 @@ def test_evaluate_same_expression_multiple_times(gui: Gui):
     x = 10  # noqa: F841
     x = 10  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         s1 = gui._evaluate_expr("x + 10 = {x + 10}")
         s1 = gui._evaluate_expr("x + 10 = {x + 10}")
         s2 = gui._evaluate_expr("x + 10 = {x + 10}")
         s2 = gui._evaluate_expr("x + 10 = {x + 10}")
         assert s1 == s2
         assert s1 == s2
@@ -45,7 +45,7 @@ def test_evaluate_expressions_same_variable(gui: Gui):
     x = 10  # noqa: F841
     x = 10  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         s1 = gui._evaluate_expr("x + 10 = {x + 10}")
         s1 = gui._evaluate_expr("x + 10 = {x + 10}")
         s2 = gui._evaluate_expr("x = {x}")
         s2 = gui._evaluate_expr("x = {x}")
         assert "tp_TpExPr_x" in s1 and "tp_TpExPr_x" in s2
         assert "tp_TpExPr_x" in s1 and "tp_TpExPr_x" in s2
@@ -56,7 +56,7 @@ def test_evaluate_holder(gui: Gui):
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
     with warnings.catch_warnings(record=True):
     with warnings.catch_warnings(record=True):
-        with gui.get_flask_app().app_context():
+        with gui.get_server_instance().app_context():
             gui._evaluate_expr("{x + 10}")
             gui._evaluate_expr("{x + 10}")
             hash = gui._evaluate_bind_holder(_TaipyNumber, "TpExPr_x + 10_TPMDL_0")
             hash = gui._evaluate_bind_holder(_TaipyNumber, "TpExPr_x + 10_TPMDL_0")
             assert "_TpN_tp_TpExPr_x_10_TPMDL_0_0" in hash
             assert "_TpN_tp_TpExPr_x_10_TPMDL_0_0" in hash
@@ -70,7 +70,7 @@ def test_evaluate_holder(gui: Gui):
 
 
 def test_evaluate_not_expression_type(gui: Gui):
 def test_evaluate_not_expression_type(gui: Gui):
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         assert "x + 10" == gui._evaluate_expr("x + 10")
         assert "x + 10" == gui._evaluate_expr("x + 10")
 
 
 
 
@@ -79,7 +79,7 @@ def test_evaluate_expression_2_clients(gui: Gui):
     y = 20  # noqa: F841
     y = 20  # noqa: F841
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
     gui.run(run_server=False)
     gui.run(run_server=False)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         gui._bindings()._get_or_create_scope("A")
         gui._bindings()._get_or_create_scope("A")
         gui._bindings()._get_or_create_scope("B")
         gui._bindings()._get_or_create_scope("B")
         g.client_id = "A"
         g.client_id = "A"

+ 1 - 1
tests/gui/utils/test_map_dict.py

@@ -112,7 +112,7 @@ def test_map_dict_set(gui: Gui, test_client):
     gui._set_frame(inspect.currentframe())
     gui._set_frame(inspect.currentframe())
 
 
     gui.run(run_server=False, single_client=True)
     gui.run(run_server=False, single_client=True)
-    with gui.get_flask_app().app_context():
+    with gui.get_server_instance().app_context():
         assert isinstance(gui._Gui__state.d, _MapDict)  # type: ignore[attr-defined]
         assert isinstance(gui._Gui__state.d, _MapDict)  # type: ignore[attr-defined]
         gui._Gui__state.d = {"b": 2}  # type: ignore[attr-defined]
         gui._Gui__state.d = {"b": 2}  # type: ignore[attr-defined]
         assert isinstance(gui._Gui__state.d, _MapDict)  # type: ignore[attr-defined]
         assert isinstance(gui._Gui__state.d, _MapDict)  # type: ignore[attr-defined]