flet-web 0.25.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flet-web might be problematic. Click here for more details.

Files changed (48) hide show
  1. flet_web/__init__.py +9 -0
  2. flet_web/fastapi/README.md +146 -0
  3. flet_web/fastapi/__init__.py +6 -0
  4. flet_web/fastapi/app.py +121 -0
  5. flet_web/fastapi/flet_app.py +430 -0
  6. flet_web/fastapi/flet_app_manager.py +167 -0
  7. flet_web/fastapi/flet_fastapi.py +128 -0
  8. flet_web/fastapi/flet_oauth.py +66 -0
  9. flet_web/fastapi/flet_static_files.py +188 -0
  10. flet_web/fastapi/flet_upload.py +95 -0
  11. flet_web/fastapi/oauth_state.py +11 -0
  12. flet_web/fastapi/serve_fastapi_web_app.py +94 -0
  13. flet_web/patch_index.py +112 -0
  14. flet_web/uploads.py +54 -0
  15. flet_web/version.py +1 -0
  16. flet_web/web/.last_build_id +1 -0
  17. flet_web/web/assets/AssetManifest.bin +1 -0
  18. flet_web/web/assets/AssetManifest.bin.json +1 -0
  19. flet_web/web/assets/AssetManifest.json +1 -0
  20. flet_web/web/assets/FontManifest.json +1 -0
  21. flet_web/web/assets/NOTICES +37378 -0
  22. flet_web/web/assets/fonts/MaterialIcons-Regular.otf +0 -0
  23. flet_web/web/assets/packages/cupertino_icons/assets/CupertinoIcons.ttf +0 -0
  24. flet_web/web/assets/packages/flutter_map/lib/assets/flutter_map_logo.png +0 -0
  25. flet_web/web/assets/packages/media_kit/assets/web/hls1.4.10.js +2 -0
  26. flet_web/web/assets/packages/record_web/assets/js/record.fixwebmduration.js +507 -0
  27. flet_web/web/assets/packages/record_web/assets/js/record.worklet.js +400 -0
  28. flet_web/web/assets/packages/wakelock_plus/assets/no_sleep.js +230 -0
  29. flet_web/web/assets/shaders/ink_sparkle.frag +126 -0
  30. flet_web/web/favicon.png +0 -0
  31. flet_web/web/flutter.js +4 -0
  32. flet_web/web/flutter_bootstrap.js +31 -0
  33. flet_web/web/flutter_service_worker.js +214 -0
  34. flet_web/web/icons/apple-touch-icon-192.png +0 -0
  35. flet_web/web/icons/icon-192.png +0 -0
  36. flet_web/web/icons/icon-512.png +0 -0
  37. flet_web/web/icons/icon-maskable-192.png +0 -0
  38. flet_web/web/icons/icon-maskable-512.png +0 -0
  39. flet_web/web/icons/loading-animation.png +0 -0
  40. flet_web/web/index.html +99 -0
  41. flet_web/web/main.dart.js +233348 -0
  42. flet_web/web/manifest.json +35 -0
  43. flet_web/web/python-worker.js +47 -0
  44. flet_web/web/python.js +28 -0
  45. flet_web/web/version.json +1 -0
  46. flet_web-0.25.0.dist-info/METADATA +27 -0
  47. flet_web-0.25.0.dist-info/RECORD +48 -0
  48. flet_web-0.25.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,128 @@
1
+ import asyncio
2
+ from contextlib import asynccontextmanager
3
+ from typing import (
4
+ Any,
5
+ Awaitable,
6
+ Callable,
7
+ Coroutine,
8
+ Dict,
9
+ List,
10
+ Optional,
11
+ Sequence,
12
+ Type,
13
+ Union,
14
+ )
15
+
16
+ import fastapi
17
+ import flet_web.fastapi
18
+ from fastapi.datastructures import Default
19
+ from fastapi.params import Depends
20
+ from fastapi.utils import generate_unique_id
21
+ from starlette.middleware import Middleware
22
+ from starlette.requests import Request
23
+ from starlette.responses import JSONResponse, Response
24
+ from starlette.routing import BaseRoute
25
+
26
+
27
+ class FastAPI(fastapi.FastAPI):
28
+ def __init__(
29
+ self,
30
+ *,
31
+ debug: bool = False,
32
+ routes: Optional[List[BaseRoute]] = None,
33
+ title: str = "FastAPI",
34
+ summary: Optional[str] = None,
35
+ description: str = "",
36
+ version: str = "0.1.0",
37
+ openapi_url: Optional[str] = "/openapi.json",
38
+ openapi_tags: Optional[List[Dict[str, Any]]] = None,
39
+ servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
40
+ dependencies: Optional[Sequence[Depends]] = None,
41
+ default_response_class: Type[Response] = Default(JSONResponse),
42
+ redirect_slashes: bool = True,
43
+ docs_url: Optional[str] = "/docs",
44
+ redoc_url: Optional[str] = "/redoc",
45
+ swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
46
+ swagger_ui_init_oauth: Optional[Dict[str, Any]] = None,
47
+ middleware: Optional[Sequence[Middleware]] = None,
48
+ exception_handlers: Optional[
49
+ Dict[
50
+ Union[int, Type[Exception]],
51
+ Callable[[Request, Any], Coroutine[Any, Any, Response]],
52
+ ]
53
+ ] = None,
54
+ on_startup: Optional[Sequence[Callable[[], Optional[Awaitable]]]] = None,
55
+ on_shutdown: Optional[Sequence[Callable[[], Optional[Awaitable]]]] = None,
56
+ terms_of_service: Optional[str] = None,
57
+ contact: Optional[Dict[str, Union[str, Any]]] = None,
58
+ license_info: Optional[Dict[str, Union[str, Any]]] = None,
59
+ openapi_prefix: str = "",
60
+ root_path: str = "",
61
+ root_path_in_servers: bool = True,
62
+ responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
63
+ callbacks: Optional[List[BaseRoute]] = None,
64
+ webhooks: Optional[fastapi.routing.APIRouter] = None,
65
+ deprecated: Optional[bool] = None,
66
+ include_in_schema: bool = True,
67
+ swagger_ui_parameters: Optional[Dict[str, Any]] = None,
68
+ generate_unique_id_function: Callable[
69
+ [fastapi.routing.APIRoute], str
70
+ ] = Default(generate_unique_id),
71
+ **extra: Any,
72
+ ) -> None:
73
+ @asynccontextmanager
74
+ async def lifespan(app: fastapi.FastAPI):
75
+ await flet_web.fastapi.app_manager.start()
76
+ if on_startup:
77
+ for h in on_startup:
78
+ if asyncio.iscoroutinefunction(h):
79
+ await h()
80
+ else:
81
+ h()
82
+
83
+ yield
84
+ if on_shutdown:
85
+ for h in on_shutdown:
86
+ if asyncio.iscoroutinefunction(h):
87
+ await h()
88
+ else:
89
+ h()
90
+ await flet_web.fastapi.app_manager.shutdown()
91
+
92
+ super().__init__(
93
+ debug=debug,
94
+ routes=routes,
95
+ title=title,
96
+ summary=summary,
97
+ description=description,
98
+ version=version,
99
+ openapi_url=openapi_url,
100
+ openapi_tags=openapi_tags,
101
+ servers=servers,
102
+ dependencies=dependencies,
103
+ default_response_class=default_response_class,
104
+ redirect_slashes=redirect_slashes,
105
+ docs_url=docs_url,
106
+ redoc_url=redoc_url,
107
+ swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url,
108
+ swagger_ui_init_oauth=swagger_ui_init_oauth,
109
+ middleware=middleware,
110
+ exception_handlers=exception_handlers,
111
+ # on_startup=on_startup,
112
+ # on_shutdown=on_shutdown,
113
+ lifespan=lifespan,
114
+ terms_of_service=terms_of_service,
115
+ contact=contact,
116
+ license_info=license_info,
117
+ openapi_prefix=openapi_prefix,
118
+ root_path=root_path,
119
+ root_path_in_servers=root_path_in_servers,
120
+ responses=responses,
121
+ callbacks=callbacks,
122
+ webhooks=webhooks,
123
+ deprecated=deprecated,
124
+ include_in_schema=include_in_schema,
125
+ swagger_ui_parameters=swagger_ui_parameters,
126
+ generate_unique_id_function=generate_unique_id_function,
127
+ **extra,
128
+ )
@@ -0,0 +1,66 @@
1
+ from fastapi import HTTPException, Request
2
+ from fastapi.responses import HTMLResponse, RedirectResponse
3
+ from flet_web.fastapi.flet_app_manager import app_manager
4
+
5
+
6
+ class FletOAuth:
7
+ """
8
+ HTTP handler for OAuth callback.
9
+ """
10
+
11
+ def __init__(self) -> None:
12
+ pass
13
+
14
+ async def handle(self, request: Request):
15
+ """
16
+ Handle OAuth callback request.
17
+
18
+ Request must contain minimum `code` and `state` query parameters.
19
+
20
+ Returns either redirect to a Flet page or a HTML page with further instructions.
21
+ """
22
+ state_id = request.query_params.get("state")
23
+
24
+ if not state_id:
25
+ raise HTTPException(status_code=400, detail="Invalid state")
26
+
27
+ state = app_manager.retrieve_state(state_id)
28
+
29
+ if not state:
30
+ raise HTTPException(status_code=400, detail="Invalid state")
31
+
32
+ session = await app_manager.get_session(state.session_id)
33
+ if not session:
34
+ raise HTTPException(status_code=500, detail="Session not found")
35
+
36
+ await session._authorize_callback_async(
37
+ {
38
+ "state": state_id,
39
+ "code": request.query_params.get("code"),
40
+ "error": request.query_params.get("error"),
41
+ "error_description": request.query_params.get("error_description"),
42
+ }
43
+ )
44
+
45
+ if state.complete_page_url:
46
+ return RedirectResponse(state.complete_page_url)
47
+ else:
48
+ html_content = (
49
+ state.complete_page_html
50
+ if state.complete_page_html
51
+ else f"""
52
+ <!DOCTYPE html>
53
+ <html>
54
+ <head>
55
+ <title>Signed in successfully</title>
56
+ </head>
57
+ <body>
58
+ <script type="text/javascript">
59
+ window.close();
60
+ </script>
61
+ <p>You've been successfully signed in! You can close this tab or window now.</p>
62
+ </body>
63
+ </html>
64
+ """
65
+ )
66
+ return HTMLResponse(content=html_content, status_code=200)
@@ -0,0 +1,188 @@
1
+ import logging
2
+ import os
3
+ import shutil
4
+ import tempfile
5
+ from pathlib import Path
6
+ from typing import Optional, Tuple
7
+
8
+ import flet_web.fastapi as flet_fastapi
9
+ from fastapi.staticfiles import StaticFiles
10
+ from flet.core.types import WebRenderer
11
+ from flet.utils import Once, get_bool_env_var
12
+ from flet_web import get_package_web_dir, patch_index_html, patch_manifest_json
13
+ from flet_web.fastapi.flet_app_manager import app_manager
14
+ from starlette.types import Receive, Scope, Send
15
+
16
+ logger = logging.getLogger(flet_fastapi.__name__)
17
+
18
+
19
+ class FletStaticFiles(StaticFiles):
20
+ """
21
+ Serve Flet app static files.
22
+
23
+ Parameters:
24
+
25
+ * `app_mount_path` (str) - absolute URL of Flet app. Default is `/`.
26
+ * `assets_dir` (str, optional) - an absolute path to app's assets directory.
27
+ * `app_name` (str, optional) - PWA application name.
28
+ * `app_short_name` (str, optional) - PWA application short name.
29
+ * `app_description` (str, optional) - PWA application description.
30
+ * `web_renderer` (WebRenderer) - web renderer defaulting to `WebRenderer.CANVAS_KIT`.
31
+ * `use_color_emoji` (bool) - whether to load a font with color emoji. Default is `False`.
32
+ * `route_url_strategy` (str) - routing URL strategy: `path` (default) or `hash`.
33
+ * `websocket_endpoint_path` (str, optional) - absolute URL of Flet app WebSocket handler. Default is `{app_mount_path}/ws`.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ proxy_path: Optional[str] = None,
39
+ assets_dir: Optional[str] = None,
40
+ app_name: Optional[str] = None,
41
+ app_short_name: Optional[str] = None,
42
+ app_description: Optional[str] = None,
43
+ web_renderer: WebRenderer = WebRenderer.CANVAS_KIT,
44
+ use_color_emoji: bool = False,
45
+ route_url_strategy: str = "path",
46
+ websocket_endpoint_path: Optional[str] = None,
47
+ ) -> None:
48
+ self.index = "index.html"
49
+ self.manifest_json = "manifest.json"
50
+ self.__proxy_path = proxy_path
51
+ self.__assets_dir = assets_dir
52
+ self.__app_name = app_name
53
+ self.__app_short_name = app_short_name
54
+ self.__app_description = app_description
55
+ self.__web_renderer = web_renderer
56
+ self.__use_color_emoji = use_color_emoji
57
+ self.__route_url_strategy = route_url_strategy
58
+ self.__websocket_endpoint_path = websocket_endpoint_path
59
+ self.__once = Once()
60
+
61
+ env_web_renderer = os.getenv("FLET_WEB_RENDERER")
62
+ if env_web_renderer:
63
+ self.__web_renderer = WebRenderer(env_web_renderer)
64
+
65
+ env_use_color_emoji = get_bool_env_var("FLET_WEB_USE_COLOR_EMOJI")
66
+ if env_use_color_emoji is not None:
67
+ self.__use_color_emoji = env_use_color_emoji
68
+
69
+ env_route_url_strategy = os.getenv("FLET_WEB_ROUTE_URL_STRATEGY")
70
+ if env_route_url_strategy:
71
+ self.__route_url_strategy = env_route_url_strategy
72
+
73
+ logger.info(f"Web renderer configured: {self.__web_renderer}")
74
+ logger.info(f"Use color emoji: {self.__use_color_emoji}")
75
+ logger.info(f"Route URL strategy configured: {self.__route_url_strategy}")
76
+
77
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
78
+ await self.__once.do(self.__config, scope["root_path"])
79
+ await super().__call__(scope, receive, send)
80
+
81
+ def lookup_path(self, path: str) -> Tuple[str, Optional[os.stat_result]]:
82
+ """Returns the index file when no match is found.
83
+
84
+ Args:
85
+ path (str): Resource path.
86
+
87
+ Returns:
88
+ [tuple[str, os.stat_result]]: Always retuens a full path and stat result.
89
+ """
90
+ logger.debug(f"StaticFiles.lookup_path: {self.__app_mount_path} {path}")
91
+ full_path, stat_result = super().lookup_path(path)
92
+
93
+ # if a file cannot be found
94
+ if stat_result is None:
95
+ return super().lookup_path(self.index)
96
+
97
+ return full_path, stat_result
98
+
99
+ async def __config(self, root_path: str):
100
+ if self.__proxy_path:
101
+ self.__app_mount_path = self.__proxy_path + root_path
102
+ else:
103
+ self.__app_mount_path = root_path
104
+
105
+ # where modified index.html is stored
106
+ temp_dir = tempfile.mkdtemp()
107
+ app_manager.add_temp_dir(temp_dir)
108
+ logger.info(f"Temp dir created for patched index and manifest: {temp_dir}")
109
+
110
+ # "standard" web files
111
+ web_dir = get_package_web_dir()
112
+ logger.info(f"Web root: {web_dir}")
113
+
114
+ if not os.path.exists(web_dir):
115
+ raise Exception(f"Web root path not found: {web_dir}")
116
+
117
+ # user-defined assets
118
+ if self.__assets_dir:
119
+ if not Path(self.__assets_dir).is_absolute():
120
+ logger.warning("assets_dir must be absolute path.")
121
+ self.__assets_dir = None
122
+ elif not os.path.exists(self.__assets_dir):
123
+ logger.info(f"assets_dir does not exist: {self.__assets_dir}")
124
+ self.__assets_dir = None
125
+
126
+ logger.info(f"Assets dir: {self.__assets_dir}")
127
+
128
+ # copy index.html from assets_dir or web_dir
129
+ if self.__assets_dir and os.path.exists(
130
+ os.path.join(self.__assets_dir, self.index)
131
+ ):
132
+ shutil.copyfile(
133
+ os.path.join(self.__assets_dir, self.index),
134
+ os.path.join(temp_dir, self.index),
135
+ )
136
+ else:
137
+ shutil.copyfile(
138
+ os.path.join(web_dir, self.index),
139
+ os.path.join(temp_dir, self.index),
140
+ )
141
+
142
+ # copy manifest.json from assets_dir or web_dir
143
+ if self.__assets_dir and os.path.exists(
144
+ os.path.join(self.__assets_dir, self.manifest_json)
145
+ ):
146
+ shutil.copyfile(
147
+ os.path.join(self.__assets_dir, self.manifest_json),
148
+ os.path.join(temp_dir, self.manifest_json),
149
+ )
150
+ else:
151
+ shutil.copyfile(
152
+ os.path.join(web_dir, self.manifest_json),
153
+ os.path.join(temp_dir, self.manifest_json),
154
+ )
155
+
156
+ ws_path = self.__websocket_endpoint_path
157
+ if not ws_path:
158
+ ws_path = self.__app_mount_path.strip("/")
159
+ ws_path = f"{'' if ws_path == '' else '/'}{ws_path}/ws"
160
+
161
+ # replace variables in index.html and manifest.json
162
+ patch_index_html(
163
+ index_path=os.path.join(temp_dir, self.index),
164
+ base_href=self.__app_mount_path,
165
+ websocket_endpoint_path=ws_path,
166
+ app_name=self.__app_name,
167
+ app_description=self.__app_description,
168
+ web_renderer=WebRenderer(self.__web_renderer),
169
+ use_color_emoji=self.__use_color_emoji,
170
+ route_url_strategy=self.__route_url_strategy,
171
+ )
172
+
173
+ patch_manifest_json(
174
+ manifest_path=os.path.join(temp_dir, self.manifest_json),
175
+ app_name=self.__app_name,
176
+ app_short_name=self.__app_short_name,
177
+ app_description=self.__app_description,
178
+ )
179
+
180
+ # set html=True to resolve the index even when no
181
+ # the base path is passed in
182
+ super().__init__(directory=temp_dir, packages=None, html=True, check_dir=True)
183
+
184
+ # add the rest of dirs
185
+ if self.__assets_dir:
186
+ self.all_directories.append(self.__assets_dir)
187
+
188
+ self.all_directories.append(web_dir)
@@ -0,0 +1,95 @@
1
+ import logging
2
+ import os
3
+ from datetime import datetime, timezone
4
+ from typing import Optional
5
+
6
+ import flet_web.fastapi as flet_fastapi
7
+ from anyio import open_file
8
+ from fastapi import Request
9
+ from flet_web.uploads import build_upload_query_string, get_upload_signature
10
+
11
+ logger = logging.getLogger(flet_fastapi.__name__)
12
+
13
+
14
+ class FletUpload:
15
+ """
16
+ Flet app uploads handler.
17
+
18
+ Parameters:
19
+
20
+ * `upload_dir` (str) - an absolute path to a directory with uploaded files.
21
+ * `max_upload_size` (str, int) - maximum size of a single upload, bytes. Unlimited if `None`.
22
+ * `secret_key` (str, optional) - secret key to sign and verify upload requests.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ upload_dir: str,
28
+ max_upload_size: Optional[int] = None,
29
+ secret_key: Optional[str] = None,
30
+ ) -> None:
31
+ self.__upload_dir = os.path.realpath(upload_dir)
32
+ self.__max_upload_size = max_upload_size
33
+
34
+ env_max_upload_size = os.getenv("FLET_MAX_UPLOAD_SIZE")
35
+ if env_max_upload_size:
36
+ self.__max_upload_size = int(env_max_upload_size)
37
+
38
+ self.__secret_key = secret_key
39
+
40
+ env_upload_secret_key = os.getenv("FLET_SECRET_KEY")
41
+ if env_upload_secret_key:
42
+ self.__secret_key = env_upload_secret_key
43
+
44
+ logger.info(f"Upload path configured: {self.__upload_dir}")
45
+
46
+ """
47
+ Handle file upload.
48
+
49
+ Upload must be an non-encoded (raw) file in the requst body.
50
+ """
51
+
52
+ async def handle(self, request: Request):
53
+ file_name = request.query_params["f"]
54
+ expire_str = request.query_params["e"]
55
+ signature = request.query_params["s"]
56
+
57
+ if not file_name or not expire_str or not signature:
58
+ raise Exception("Invalid request")
59
+
60
+ expire_date = datetime.fromisoformat(expire_str)
61
+
62
+ # verify signature
63
+ query_string = build_upload_query_string(file_name, expire_date)
64
+ if (
65
+ get_upload_signature(
66
+ request.url.path, query_string, expire_date, self.__secret_key
67
+ )
68
+ != signature
69
+ ):
70
+ raise Exception("Invalid request")
71
+
72
+ # check expiration date
73
+ if datetime.now(timezone.utc) >= expire_date:
74
+ raise Exception("Invalid request")
75
+
76
+ # build/validate dest path
77
+ joined_path = os.path.join(self.__upload_dir, file_name)
78
+ full_path = os.path.realpath(joined_path)
79
+ if os.path.commonpath([full_path, self.__upload_dir]) != self.__upload_dir:
80
+ raise Exception("Invalid request")
81
+
82
+ # create directory if not exists
83
+ dest_dir = os.path.dirname(full_path)
84
+ os.makedirs(dest_dir, exist_ok=True)
85
+
86
+ # upload file
87
+ size = 0
88
+ async with await open_file(full_path, "wb") as f:
89
+ async for chunk in request.stream():
90
+ size += len(chunk)
91
+ if self.__max_upload_size and size > self.__max_upload_size:
92
+ raise Exception(
93
+ f"Max upload size reached: {self.__max_upload_size}"
94
+ )
95
+ await f.write(chunk)
@@ -0,0 +1,11 @@
1
+ import dataclasses
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+
6
+ @dataclasses.dataclass
7
+ class OAuthState:
8
+ session_id: str
9
+ expires_at: datetime
10
+ complete_page_url: Optional[str] = dataclasses.field(default=None)
11
+ complete_page_html: Optional[str] = dataclasses.field(default=None)
@@ -0,0 +1,94 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Optional
4
+
5
+ import uvicorn
6
+ from flet.core.types import WebRenderer
7
+
8
+ import flet_web.fastapi
9
+ import flet_web.fastapi as flet_fastapi
10
+
11
+ logger = logging.getLogger(flet_fastapi.__name__)
12
+
13
+
14
+ class WebServerHandle:
15
+ def __init__(self, page_url: str, server: uvicorn.Server) -> None:
16
+ self.page_url = page_url
17
+ self.server = server
18
+
19
+ async def close(self):
20
+ logger.info("Closing Flet web server...")
21
+ await self.server.shutdown()
22
+
23
+
24
+ def get_fastapi_web_app(
25
+ session_handler,
26
+ page_name: str,
27
+ assets_dir,
28
+ upload_dir,
29
+ web_renderer: Optional[WebRenderer],
30
+ use_color_emoji,
31
+ route_url_strategy,
32
+ ):
33
+ web_path = f"/{page_name.strip('/')}"
34
+ app = flet_web.fastapi.FastAPI()
35
+ app.mount(
36
+ web_path,
37
+ flet_web.fastapi.app(
38
+ session_handler,
39
+ upload_dir=upload_dir,
40
+ assets_dir=assets_dir,
41
+ web_renderer=web_renderer if web_renderer else WebRenderer.AUTO,
42
+ use_color_emoji=use_color_emoji,
43
+ route_url_strategy=route_url_strategy,
44
+ ),
45
+ )
46
+
47
+ return app
48
+
49
+
50
+ async def serve_fastapi_web_app(
51
+ session_handler,
52
+ host,
53
+ url_host,
54
+ port,
55
+ page_name: str,
56
+ assets_dir,
57
+ upload_dir,
58
+ web_renderer: Optional[WebRenderer],
59
+ use_color_emoji,
60
+ route_url_strategy,
61
+ blocking,
62
+ on_startup,
63
+ log_level,
64
+ ):
65
+
66
+ web_path = f"/{page_name.strip('/')}"
67
+ page_url = f"http://{url_host}:{port}{web_path if web_path != '/' else ''}"
68
+
69
+ def startup():
70
+ if on_startup:
71
+ on_startup(page_url)
72
+
73
+ app = flet_web.fastapi.FastAPI(on_startup=[startup])
74
+
75
+ app.mount(
76
+ web_path,
77
+ flet_web.fastapi.app(
78
+ session_handler,
79
+ upload_dir=upload_dir,
80
+ assets_dir=assets_dir,
81
+ web_renderer=web_renderer if web_renderer else WebRenderer.AUTO,
82
+ use_color_emoji=use_color_emoji,
83
+ route_url_strategy=route_url_strategy,
84
+ ),
85
+ )
86
+ config = uvicorn.Config(app, host=host, port=port, log_level=log_level)
87
+ server = uvicorn.Server(config)
88
+
89
+ if blocking:
90
+ await server.serve()
91
+ else:
92
+ asyncio.create_task(server.serve())
93
+
94
+ return WebServerHandle(page_url=page_url, server=server)
@@ -0,0 +1,112 @@
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from flet.core.types import WebRenderer
7
+
8
+
9
+ def patch_index_html(
10
+ index_path: str,
11
+ base_href: str,
12
+ websocket_endpoint_path: Optional[str] = None,
13
+ app_name: Optional[str] = None,
14
+ app_description: Optional[str] = None,
15
+ pyodide: bool = False,
16
+ pyodide_pre: bool = False,
17
+ pyodide_script_path: str = "",
18
+ web_renderer: WebRenderer = WebRenderer.AUTO,
19
+ use_color_emoji: bool = False,
20
+ route_url_strategy: str = "path",
21
+ ):
22
+ with open(index_path, "r") as f:
23
+ index = f.read()
24
+
25
+ if pyodide and pyodide_script_path:
26
+ module_name = Path(pyodide_script_path).stem
27
+ pyodideCode = f"""
28
+ <script>
29
+ var micropipIncludePre = {str(pyodide_pre).lower()};
30
+ var pythonModuleName = "{module_name}";
31
+ </script>
32
+ <script src="python.js"></script>
33
+ """
34
+ index = index.replace("<!-- pyodideCode -->", pyodideCode)
35
+ index = index.replace("%FLET_WEB_PYODIDE%", str(pyodide).lower())
36
+ index = index.replace(
37
+ "<!-- webRenderer -->",
38
+ f'<script>webRenderer="{web_renderer.value}";</script>',
39
+ )
40
+ index = index.replace(
41
+ "<!-- useColorEmoji -->",
42
+ f"<script>useColorEmoji={str(use_color_emoji).lower()};</script>",
43
+ )
44
+ index = index.replace("%FLET_ROUTE_URL_STRATEGY%", route_url_strategy)
45
+
46
+ if base_href:
47
+ base_url = base_href.strip("/").strip()
48
+ index = index.replace(
49
+ '<base href="/">',
50
+ '<base href="{}">'.format(
51
+ "/" if base_url == "" else "/{}/".format(base_url)
52
+ ),
53
+ )
54
+ if websocket_endpoint_path:
55
+ index = re.sub(
56
+ r"\<meta name=\"flet-websocket-endpoint-path\" content=\"(.+)\">",
57
+ r'<meta name="flet-websocket-endpoint-path" content="{}">'.format(
58
+ websocket_endpoint_path
59
+ ),
60
+ index,
61
+ )
62
+ if app_name:
63
+ index = re.sub(
64
+ r"\<meta name=\"apple-mobile-web-app-title\" content=\"(.+)\">",
65
+ r'<meta name="apple-mobile-web-app-title" content="{}">'.format(app_name),
66
+ index,
67
+ )
68
+ index = re.sub(
69
+ r"\<title>(.+)</title>",
70
+ r"<title>{}</title>".format(app_name),
71
+ index,
72
+ )
73
+ if app_description:
74
+ index = re.sub(
75
+ r"\<meta name=\"description\" content=\"(.+)\">",
76
+ r'<meta name="description" content="{}">'.format(app_description),
77
+ index,
78
+ )
79
+
80
+ with open(index_path, "w") as f:
81
+ f.write(index)
82
+
83
+
84
+ def patch_manifest_json(
85
+ manifest_path: str,
86
+ app_name: Optional[str] = None,
87
+ app_short_name: Optional[str] = None,
88
+ app_description: Optional[str] = None,
89
+ background_color: Optional[str] = None,
90
+ theme_color: Optional[str] = None,
91
+ ):
92
+ with open(manifest_path, "r") as f:
93
+ manifest = json.loads(f.read())
94
+
95
+ if app_name:
96
+ manifest["name"] = app_name
97
+ manifest["short_name"] = app_name
98
+
99
+ if app_short_name:
100
+ manifest["short_name"] = app_short_name
101
+
102
+ if app_description:
103
+ manifest["description"] = app_description
104
+
105
+ if background_color:
106
+ manifest["background_color"] = background_color
107
+
108
+ if theme_color:
109
+ manifest["theme_color"] = theme_color
110
+
111
+ with open(manifest_path, "w") as f:
112
+ f.write(json.dumps(manifest, indent=2))