dinhlongviolin1 1 місяць тому
батько
коміт
651b1349ac

+ 4 - 4
taipy/gui/gui.py

@@ -2772,7 +2772,7 @@ class Gui:
             self._server = create_server(
                 self,  # type: ignore[arg-type]
                 path_mapping=self._path_mapping,
-                flask=self._server_instance,
+                server=self._server_instance,
                 async_mode=app_config.get("async_mode"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
@@ -2784,7 +2784,7 @@ class Gui:
             self._server = create_server(
                 self,  # type: ignore[arg-type]
                 path_mapping=self._path_mapping,
-                flask=self._server_instance,
+                server=self._server_instance,
                 async_mode=app_config.get("async_mode"),
                 allow_upgrades=not app_config.get("notebook_proxy"),
                 server_config=app_config.get("server_config"),
@@ -3109,7 +3109,7 @@ class Gui:
         `(Gui.)run^` method was set to True, or you are running in an IPython notebook
         context.
         """
-        if hasattr(self, "_server") and hasattr(self._server, "_thread") and self._server._is_running:
+        if hasattr(self, "_server") and hasattr(self._server, "_thread") and self._server.is_running():
             self._server.stop_thread()
             self.run(**self.__run_kwargs, _reload=True)
             _TaipyLogger._get_logger().info("Gui server has been reloaded.")
@@ -3122,7 +3122,7 @@ class Gui:
         `(Gui.)run()^` method was set to True, or you are running in an IPython notebook
         context.
         """
-        if hasattr(self, "_server") and hasattr(self._server, "_thread") and self._server._is_running:
+        if hasattr(self, "_server") and hasattr(self._server, "_thread") and self._server.is_running():
             self._server.stop_thread()
             _TaipyLogger._get_logger().info("Gui server has been stopped.")
 

+ 4 - 3
taipy/gui/servers/fastapi/request.py

@@ -10,9 +10,10 @@
 # specific language governing permissions and limitations under the License.
 
 from contextvars import ContextVar
+from typing import Optional
 
-from flask import Request
+from fastapi import Request
 from flask.ctx import _AppCtxGlobals
 
-request: ContextVar[Request] = ContextVar("request")
-request_meta: ContextVar[_AppCtxGlobals] = ContextVar("request_meta")
+request: ContextVar[Optional[Request]] = ContextVar("request")
+request_meta: ContextVar[Optional[_AppCtxGlobals]] = ContextVar("request_meta")

+ 248 - 7
taipy/gui/servers/fastapi/server.py

@@ -9,21 +9,239 @@
 # 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 asyncio
+import os
+import pathlib
+import typing as t
+import webbrowser
+from contextlib import contextmanager
+
+import socketio
+import uvicorn
+from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import HTMLResponse, JSONResponse
+from fastapi.templating import Jinja2Templates
+from flask.ctx import _AppCtxGlobals
+from starlette.middleware.base import BaseHTTPMiddleware
+
+import __main__
+from taipy.common.logger._taipy_logger import _TaipyLogger
+
+from ..._renderers.json import _TaipyJsonAdapter
+from ...config import ServerConfig
+from ...custom._page import _ExternalResourceHandlerManager
 from ..server import _Server
+from .request import request as request_context
+from .request import request_meta
+from .utils import send_from_directory
+
+if t.TYPE_CHECKING:
+    from ...gui import Gui
+
+
+# this is for fastapi routes
+def get_request_meta(_: Request):
+    new_request_meta = _AppCtxGlobals()
+    request_meta.set(new_request_meta)
+    return new_request_meta
+
+
+# this is for ws calls as contextmanager
+@contextmanager
+def get_request_meta_sync():
+    new_request_meta = _AppCtxGlobals()
+    request_meta.set(new_request_meta)
+    yield new_request_meta
+    request_meta.set(None)
+
+
+class CleanupMiddleware(BaseHTTPMiddleware):
+    async def dispatch(self, request: Request, call_next):
+        response = await call_next(request)
+        request_context.set(None)
+        request_meta.set(None)
+        return response
 
 
 class FastAPIServer(_Server):
+    def __init__(
+        self,
+        gui: "Gui",
+        server: t.Optional[FastAPI] = None,
+        path_mapping: t.Optional[dict] = None,
+        # async mode is not used in FastAPI but it is here to comply with the api for now
+        async_mode: t.Optional[str] = None,
+        allow_upgrades: bool = True,
+        server_config: t.Optional[ServerConfig] = None,
+    ):
+        self._gui = gui
+        server_config = server_config or {}
+        self._path_mapping = path_mapping or {}
+        self._allow_upgrades = allow_upgrades
+        self._is_running = False
+        self._port: t.Optional[int] = None
+
+        # server setup
+        self._server = server or FastAPI()
+        self._ws = socketio.AsyncServer()
+        self._server.mount("/", socketio.ASGIApp(self._ws, other_asgi_app=self._server))
+
+        # registering middleware
+        self._server.add_middleware(CleanupMiddleware)
+
+        self._server.add_middleware(
+            CORSMiddleware,
+            allow_origins=["*"],  # Change this to your frontend domain for security
+            allow_credentials=True,
+            allow_methods=["*"],
+            allow_headers=["*"],
+        )
+
+        # Define your event handlers and routes
+        @self._ws.event
+        def connect(sid, environ):
+            with get_request_meta_sync():
+                gui._handle_connect()
+
+        @self._ws.on("message")  # type: ignore
+        def handle_message(sid, message):
+            with get_request_meta_sync():
+                if "status" in message:
+                    _TaipyLogger._get_logger().info(message["status"])
+                elif "type" in message:
+                    gui._manage_ws_message(message["type"], message)
+
+        @self._ws.event
+        def disconnect(sid):
+            with get_request_meta_sync():
+                gui._handle_disconnect()
+
+    def _get_default_router(
+        self,
+        static_folder: str,
+        template_folder: str,
+        title: str,
+        favicon: str,
+        root_margin: str,
+        scripts: t.List[str],
+        styles: t.List[str],
+        version: str,
+        client_config: t.Dict[str, t.Any],
+        watermark: t.Optional[str],
+        css_vars: str,
+        base_url: str,
+    ) -> APIRouter:
+        templates = Jinja2Templates(directory=template_folder)
+
+        taipy_router = APIRouter()
+
+        @taipy_router.get("/", response_class=HTMLResponse)
+        @taipy_router.get("/{path:path}", response_class=HTMLResponse)
+        def my_index(request: Request, path: str = "", request_meta: _AppCtxGlobals = Depends(get_request_meta)):  # noqa: B008
+            resource_handler_id = request.cookies.get("_RESOURCE_HANDLER_ARG", None)
+            if resource_handler_id is not None:
+                resource_handler = _ExternalResourceHandlerManager().get(resource_handler_id)
+                if resource_handler is None:
+                    reload_html = """
+                        <html>
+                            <head><style>body {background-color: black; margin: 0;}</style></head>
+                            <body><script>location.reload();</script></body>
+                        </html>
+                    """
+                    response = HTMLResponse(content=reload_html, status_code=400)
+                    response.set_cookie(
+                        "_RESOURCE_HANDLER_ARG",
+                        "",
+                        secure=request.url.scheme == "https",
+                        httponly=True,
+                        expires=0,
+                        path="/",
+                    )
+                    return response
+                try:
+                    return resource_handler.get_resources(path, static_folder, base_url)
+                except Exception as e:
+                    raise HTTPException(
+                        status_code=500, detail="Can't get resources from custom resource handler"
+                    ) from e
+
+            if not path or path == "index.html" or "." not in path:
+                try:
+                    return templates.TemplateResponse(
+                        "index.html",
+                        {
+                            "title": title,
+                            "favicon": f"{favicon}?version={version}",
+                            "root_margin": root_margin,
+                            "watermark": watermark,
+                            "config": client_config,
+                            "scripts": scripts,
+                            "styles": styles,
+                            "version": version,
+                            "css_vars": css_vars,
+                            "base_url": base_url,
+                        },
+                    )
+                except Exception:
+                    raise HTTPException(  # noqa: B904
+                        status_code=500,
+                        detail="Something is wrong with the taipy-gui front-end installation. Check that the js bundle has been properly built.",  # noqa: E501
+                    )
+
+            if path == "taipy.status.json":
+                return JSONResponse(content=self._gui._serve_status(pathlib.Path(template_folder) / path))
+
+            if (file_path := str(os.path.normpath((base_path := static_folder + os.path.sep) + path))).startswith(
+                base_path
+            ) and os.path.isfile(file_path):
+                return send_from_directory(base_path, path)
+            # use the path mapping to detect and find resources
+            for k, v in self._path_mapping.items():
+                if (
+                    path.startswith(f"{k}/")
+                    and (
+                        file_path := str(os.path.normpath((base_path := v + os.path.sep) + path[len(k) + 1 :]))
+                    ).startswith(base_path)
+                    and os.path.isfile(file_path)
+                ):
+                    return send_from_directory(base_path, path[len(k) + 1 :])
+            if (
+                hasattr(__main__, "__file__")
+                and (
+                    file_path := str(
+                        os.path.normpath((base_path := os.path.dirname(__main__.__file__) + os.path.sep) + path)
+                    )
+                ).startswith(base_path)
+                and os.path.isfile(file_path)
+                and not self._is_ignored(file_path)
+            ):
+                return send_from_directory(base_path, path)
+            if (
+                (
+                    file_path := str(os.path.normpath((base_path := self._gui._root_dir + os.path.sep) + path))  # type: ignore[attr-defined]
+                ).startswith(base_path)
+                and os.path.isfile(file_path)
+                and not self._is_ignored(file_path)
+            ):
+                return send_from_directory(base_path, path)
+
+            # Default error return for unmatched paths
+            raise HTTPException(status_code=404, detail="")
+
+        return taipy_router
+
+    def direct_render_json(self, data):
+        return _TaipyJsonAdapter().parse(data)
+
     def get_server_instance(self):
-        raise NotImplementedError
+        return self._server
 
     def get_port(self) -> int:
-        raise NotImplementedError
+        return self._port or -1
 
     def send_ws_message(self, *args, **kwargs):
-        raise NotImplementedError
-
-    def direct_render_json(self, data):
-        raise NotImplementedError
+        asyncio.run(self._ws.emit("message", *args, **kwargs))
 
     def run(
         self,
@@ -38,7 +256,30 @@ class FastAPIServer(_Server):
         notebook_proxy,
         port_auto_ranges,
     ):
-        raise NotImplementedError
+        from ..utils import is_running_from_reloader
+
+        host_value = host if host != "0.0.0.0" else "localhost"
+        self._host = host
+        if port == "auto":
+            port = self._get_random_port(port_auto_ranges)
+        server_url = f"http://{host_value}:{port}"
+        self._port = port
+        if not is_running_from_reloader() and self._gui._get_config("run_browser", False):  # type: ignore[attr-defined]
+            webbrowser.open(client_url or server_url, new=2)
+        self._is_running = True
+        run_config = {
+            "app": self._server,
+            "host": host,
+            "port": port,
+            "reload": use_reloader,
+        }
+        try:
+            uvicorn.run(**run_config)
+        except KeyboardInterrupt:
+            pass
+
+    def is_running(self):
+        return self._is_running
 
     def stop_thread(self):
         raise NotImplementedError

+ 36 - 0
taipy/gui/servers/fastapi/utils.py

@@ -0,0 +1,36 @@
+# 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 typing as t
+
+from fastapi import Response
+from fastapi.responses import FileResponse
+
+
+def send_file(
+    path_or_file: os.PathLike[str] | str,
+    **kwargs: t.Any,
+) -> Response:
+    return FileResponse(path_or_file, **kwargs)
+
+
+def send_from_directory(
+    directory: os.PathLike[str] | str,
+    path: os.PathLike[str] | str,
+    **kwargs: t.Any,
+) -> Response:
+    path = os.path.normpath(path)
+    directory = os.path.normpath(directory)
+    joined_path = os.path.join(directory, path)
+    if not os.path.exists(joined_path) or not os.path.isfile(joined_path):
+        return Response("File not found", status_code=404)
+    return FileResponse(joined_path, **kwargs)

+ 19 - 47
taipy/gui/servers/flask/server.py

@@ -20,7 +20,6 @@ import time
 import typing as t
 import webbrowser
 from importlib import util
-from random import choices, randint
 
 from flask import (
     Blueprint,
@@ -35,7 +34,6 @@ from flask import (
 )
 from flask_cors import CORS
 from flask_socketio import SocketIO
-from gitignore_parser import parse_gitignore
 from kthread import KThread
 from werkzeug.serving import is_running_from_reloader
 
@@ -56,7 +54,7 @@ class FlaskServer(_Server):
     def __init__(
         self,
         gui: Gui,
-        flask: t.Optional[Flask] = None,
+        server: t.Optional[Flask] = None,
         path_mapping: t.Optional[dict] = None,
         async_mode: t.Optional[str] = None,
         allow_upgrades: bool = True,
@@ -64,14 +62,14 @@ class FlaskServer(_Server):
     ):
         self._gui = gui
         server_config = server_config or {}
-        self._flask = flask
-        if self._flask is None:
+        self._server = server
+        if self._server is None:
             flask_config: t.Dict[str, t.Any] = {"import_name": "Taipy"}
             if "flask" in server_config and isinstance(server_config["flask"], dict):
                 flask_config.update(server_config["flask"])
-            self._flask = Flask(**flask_config)
-        if "SECRET_KEY" not in self._flask.config or not self._flask.config["SECRET_KEY"]:
-            self._flask.config["SECRET_KEY"] = "TaIpY"
+            self._server = Flask(**flask_config)
+        if "SECRET_KEY" not in self._server.config or not self._server.config["SECRET_KEY"]:
+            self._server.config["SECRET_KEY"] = "TaIpY"
 
         # setup cors
         if "cors" not in server_config or (
@@ -80,7 +78,7 @@ class FlaskServer(_Server):
             cors_config = (
                 server_config["cors"] if "cors" in server_config and isinstance(server_config["cors"], dict) else {}
             )
-            CORS(self._flask, **cors_config)
+            CORS(self._server, **cors_config)
 
         # setup socketio
         socketio_config: t.Dict[str, t.Any] = {
@@ -93,13 +91,13 @@ class FlaskServer(_Server):
         }
         if "socketio" in server_config and isinstance(server_config["socketio"], dict):
             socketio_config.update(server_config["socketio"])
-        self._ws = SocketIO(self._flask, **socketio_config)
+        self._ws = SocketIO(self._server, **socketio_config)
 
         self._apply_patch()
 
         # set json encoder (for Taipy specific types)
-        self._flask.json_provider_class = _TaipyJsonProvider
-        self._flask.json = self._flask.json_provider_class(self._flask)
+        self._server.json_provider_class = _TaipyJsonProvider
+        self._server.json = self._server.json_provider_class(self._server)
 
         self.__path_mapping = path_mapping or {}
         self.__ssl_context = server_config.get("ssl_context", None)
@@ -122,22 +120,6 @@ class FlaskServer(_Server):
         def handle_disconnect():
             gui._handle_disconnect()  # type: ignore[attr-defined]
 
-    def __is_ignored(self, file_path: str) -> bool:
-        if not hasattr(self, "_ignore_matches"):
-            __IGNORE_FILE = ".taipyignore"
-            ignore_file = (
-                (pathlib.Path(__main__.__file__).parent / __IGNORE_FILE) if hasattr(__main__, "__file__") else None
-            )
-            if not ignore_file or not ignore_file.is_file():
-                ignore_file = pathlib.Path(self._gui._root_dir) / __IGNORE_FILE  # type: ignore[attr-defined]
-            self._ignore_matches = (
-                parse_gitignore(ignore_file) if ignore_file.is_file() and os.access(ignore_file, os.R_OK) else None
-            )
-
-        if callable(self._ignore_matches):
-            return self._ignore_matches(file_path)
-        return False
-
     def _get_default_blueprint(
         self,
         static_folder: str,
@@ -217,7 +199,7 @@ class FlaskServer(_Server):
                     )
                 ).startswith(base_path)
                 and os.path.isfile(file_path)
-                and not self.__is_ignored(file_path)
+                and not self._is_ignored(file_path)
             ):
                 return send_from_directory(base_path, path)
             if (
@@ -225,7 +207,7 @@ class FlaskServer(_Server):
                     file_path := str(os.path.normpath((base_path := self._gui._root_dir + os.path.sep) + path))  # type: ignore[attr-defined]
                 ).startswith(base_path)
                 and os.path.isfile(file_path)
-                and not self.__is_ignored(file_path)
+                and not self._is_ignored(file_path)
             ):
                 return send_from_directory(base_path, path)
             return ("", 404)
@@ -236,17 +218,17 @@ class FlaskServer(_Server):
         return jsonify(data)
 
     def get_server_instance(self):
-        return self._flask
+        return self._server
 
     def get_port(self):
         return self._port
 
     def test_client(self):
-        return t.cast(Flask, self._flask).test_client()
+        return t.cast(Flask, self._server).test_client()
 
     def _run_notebook(self):
         self._is_running = True
-        self._ws.run(self._flask, host=self._host, port=self._port, debug=False, use_reloader=False)
+        self._ws.run(self._server, host=self._host, port=self._port, debug=False, use_reloader=False)
 
     def _get_async_mode(self) -> str:
         return self._ws.async_mode  # type: ignore[attr-defined]
@@ -264,19 +246,6 @@ class FlaskServer(_Server):
             if not patcher.is_monkey_patched("time"):
                 monkey_patch(time=True)
 
-    def _get_random_port(
-        self, port_auto_ranges: t.Optional[t.List[t.Union[int, t.Tuple[int, int]]]] = None
-    ):  # pragma: no cover
-        port_auto_ranges = port_auto_ranges or [(49152, 65535)]
-        random_weights = [1 if isinstance(r, int) else abs(r[1] - r[0]) + 1 for r in port_auto_ranges]
-        while True:
-            random_choices = [
-                r if isinstance(r, int) else randint(min(r[0], r[1]), max(r[0], r[1])) for r in port_auto_ranges
-            ]
-            port = choices(random_choices, weights=random_weights)[0]
-            if port not in _RuntimeManager().get_used_port() and not _is_port_open(self._host, port):
-                return port
-
     def send_ws_message(self, *args, **kwargs):
         self._ws.emit("message", *args, **kwargs)
 
@@ -331,7 +300,7 @@ class FlaskServer(_Server):
             return
         self._is_running = True
         run_config = {
-            "app": self._flask,
+            "app": self._server,
             "host": host,
             "port": port,
             "debug": debug,
@@ -347,6 +316,9 @@ class FlaskServer(_Server):
         except KeyboardInterrupt:
             pass
 
+    def is_running(self):
+        return self._is_running
+
     def stop_thread(self):
         if hasattr(self, "_thread") and self._thread.is_alive() and self._is_running:
             self._is_running = False

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

@@ -0,0 +1,10 @@
+# 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.

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

@@ -10,11 +10,18 @@
 # specific language governing permissions and limitations under the License.
 
 import os
+import pathlib
 import re
 import typing as t
 from abc import ABC, abstractmethod
 from contextvars import ContextVar
+from random import choices, randint
 
+from gitignore_parser import parse_gitignore
+
+import __main__
+
+from ..utils import _is_port_open, _RuntimeManager
 from ..utils._css import get_style
 
 
@@ -25,6 +32,37 @@ class _Server(ABC):
     _CLOSING_CURLY = r"&#x7D;\2"
     _RESOURCE_HANDLER_ARG = "tprh"
 
+    def _is_ignored(self, file_path: str) -> bool:
+        if not hasattr(self, "_ignore_matches"):
+            __IGNORE_FILE = ".taipyignore"
+            ignore_file = (
+                (pathlib.Path(__main__.__file__).parent / __IGNORE_FILE) if hasattr(__main__, "__file__") else None
+            )
+            if not ignore_file or not ignore_file.is_file():
+                ignore_file = pathlib.Path(self._gui._root_dir) / __IGNORE_FILE  # type: ignore[attr-defined]
+            self._ignore_matches = (
+                parse_gitignore(ignore_file) if ignore_file.is_file() and os.access(ignore_file, os.R_OK) else None
+            )
+
+        if callable(self._ignore_matches):
+            return self._ignore_matches(file_path)
+        return False
+
+    def _get_random_port(
+        self, port_auto_ranges: t.Optional[t.List[t.Union[int, t.Tuple[int, int]]]] = None
+    ):  # pragma: no cover
+        if not hasattr(self, "_host"):
+            raise RuntimeError("Server host is not set")
+        port_auto_ranges = port_auto_ranges or [(49152, 65535)]
+        random_weights = [1 if isinstance(r, int) else abs(r[1] - r[0]) + 1 for r in port_auto_ranges]
+        while True:
+            random_choices = [
+                r if isinstance(r, int) else randint(min(r[0], r[1]), max(r[0], r[1])) for r in port_auto_ranges
+            ]
+            port = choices(random_choices, weights=random_weights)[0]
+            if port not in _RuntimeManager().get_used_port() and not _is_port_open(self._host, port):  # type: ignore
+                return port
+
     @abstractmethod
     def get_server_instance(self):
         raise NotImplementedError
@@ -73,6 +111,10 @@ class _Server(ABC):
     ):
         raise NotImplementedError
 
+    @abstractmethod
+    def is_running(self):
+        raise NotImplementedError
+
     @abstractmethod
     def stop_thread(self):
         raise NotImplementedError

+ 7 - 5
taipy/gui/servers/utils.py

@@ -22,6 +22,8 @@ from werkzeug.serving import is_running_from_reloader as flask_is_running_from_r
 from .fastapi import FastAPIServer
 from .fastapi.request import request as fastapi_request
 from .fastapi.request import request_meta as fastapi_meta
+from .fastapi.utils import send_file as fastapi_send_file
+from .fastapi.utils import send_from_directory as fastapi_send_from_directory
 from .flask import FlaskServer
 from .server import ServerFrameworks, _Server, server, server_type
 
@@ -50,14 +52,14 @@ 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")
+        return fastapi_send_file(*args, **kwargs)
 
 
 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")
+        return fastapi_send_from_directory(*args, **kwargs)
 
 
 def get_request():
@@ -80,7 +82,7 @@ def has_server_context():
     if server_type.get() == "flask":
         return has_app_context()
     elif server_type.get() == "fastapi":
-        return True
+        return fastapi_request.get() is not None
     return False
 
 
@@ -88,7 +90,7 @@ def has_request_context():
     if server_type.get() == "flask":
         return flask_has_request_context()
     elif server_type.get() == "fastapi":
-        return True
+        return fastapi_request.get() is not None
     return False
 
 
@@ -96,5 +98,5 @@ def is_running_from_reloader():
     if server_type.get() == "flask":
         flask_is_running_from_reloader()
     elif server_type.get() == "fastapi":
-        return False
+        return server.get().get_server_instance().state.is_reloader
     return False

+ 1 - 1
tests/gui/e2e/renderers/test_html_rendering.py

@@ -102,7 +102,7 @@ def test_html_render_path_mapping(page: "Page", gui: Gui, helpers, e2e_base_url,
     gui._server = _Server(
         gui,
         path_mapping={"style": f"{Path(Path(__file__).parent.resolve())}{os.path.sep}test-assets{os.path.sep}style"},
-        flask=gui._server_instance,
+        server=gui._server_instance,
         async_mode="gevent",
     )
     gui.add_page("page1", Html(f"{Path(Path(__file__).parent.resolve())}{os.path.sep}page1.html"))