flet-web 1.0.4__tar.gz → 1.1.0.dev1__tar.gz

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.
Files changed (28) hide show
  1. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/PKG-INFO +4 -3
  2. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/pyproject.toml +7 -5
  3. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/app.py +15 -14
  4. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_app.py +20 -3
  5. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_fastapi.py +4 -3
  6. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_static_files.py +28 -8
  7. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/patch_index.py +1 -1
  8. flet_web-1.1.0.dev1/src/flet_web/version.py +1 -0
  9. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web.egg-info/PKG-INFO +4 -3
  10. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web.egg-info/SOURCES.txt +1 -0
  11. flet_web-1.1.0.dev1/src/flet_web.egg-info/requires.txt +10 -0
  12. flet_web-1.1.0.dev1/tests/test_fastapi.py +34 -0
  13. flet_web-1.0.4/src/flet_web/version.py +0 -1
  14. flet_web-1.0.4/src/flet_web.egg-info/requires.txt +0 -7
  15. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/README.md +0 -0
  16. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/setup.cfg +0 -0
  17. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/__init__.py +0 -0
  18. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/__init__.py +0 -0
  19. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_app_manager.py +0 -0
  20. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_oauth.py +0 -0
  21. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/flet_upload.py +0 -0
  22. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/oauth_state.py +0 -0
  23. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/fastapi/serve_fastapi_web_app.py +0 -0
  24. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web/uploads.py +0 -0
  25. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web.egg-info/dependency_links.txt +0 -0
  26. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/src/flet_web.egg-info/top_level.txt +0 -0
  27. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/tests/test_loading_logo.py +0 -0
  28. {flet_web-1.0.4 → flet_web-1.1.0.dev1}/tests/test_patch_index.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-web
3
- Version: 1.0.4
3
+ Version: 1.1.0.dev1
4
4
  Summary: Flet web client in Flutter.
5
5
  Author-email: "Appveyor Systems Inc." <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -9,11 +9,12 @@ Project-URL: Repository, https://github.com/flet-dev/flet
9
9
  Project-URL: Documentation, https://flet.dev/docs
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: flet==1.0.4
12
+ Requires-Dist: flet==1.1.0.dev1
13
13
  Requires-Dist: fastapi>=0.115.12
14
14
  Requires-Dist: websockets>=13.0
15
15
  Requires-Dist: uvicorn>=0.35.0
16
- Requires-Dist: uvicorn[standard]>=0.35.0; sys_platform != "win32" or platform_machine != "ARM64"
16
+ Requires-Dist: uvicorn[standard]>=0.35.0; sys_platform != "win32" or platform_machine != "ARM64" or python_version >= "3.12"
17
+ Requires-Dist: httptools>=0.9.0; sys_platform == "win32" and platform_machine == "ARM64" and python_version >= "3.12"
17
18
 
18
19
  # Flet Web client in Flutter
19
20
 
@@ -1,19 +1,21 @@
1
1
  [project]
2
2
  name = "flet-web"
3
- version = "1.0.4"
3
+ version = "1.1.0.dev1"
4
4
  description = "Flet web client in Flutter."
5
5
  authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }]
6
6
  license = "Apache-2.0"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = [
10
- "flet==1.0.4",
10
+ "flet==1.1.0.dev1",
11
11
  "fastapi >=0.115.12",
12
12
  "websockets >=13.0",
13
13
  "uvicorn >=0.35.0",
14
- # httptools, one of uvicorn's standard extras, has no wheels for Windows on ARM,
15
- # where installing it needs a C++ compiler. uvicorn falls back to h11 without it.
16
- "uvicorn[standard] >=0.35.0; sys_platform != 'win32' or platform_machine != 'ARM64'"
14
+ # On Windows ARM, uvicorn's standard extras install from wheels only on Python 3.12
15
+ # or later (PyYAML has none for 3.10 or 3.11) and with httptools 0.9.0 or later;
16
+ # otherwise they need a C++ compiler. uvicorn falls back to h11 without them.
17
+ "uvicorn[standard] >=0.35.0; sys_platform != 'win32' or platform_machine != 'ARM64' or python_version >= '3.12'",
18
+ "httptools >=0.9.0; sys_platform == 'win32' and platform_machine == 'ARM64' and python_version >= '3.12'"
17
19
  ]
18
20
 
19
21
  [project.urls]
@@ -92,6 +92,18 @@ def app(
92
92
 
93
93
  fastapi_app = FastAPI()
94
94
 
95
+ static_files = FletStaticFiles(
96
+ proxy_path=proxy_path,
97
+ assets_dir=assets_dir,
98
+ app_name=app_name,
99
+ app_short_name=app_short_name,
100
+ app_description=app_description,
101
+ web_renderer=web_renderer,
102
+ route_url_strategy=route_url_strategy,
103
+ websocket_endpoint_path=websocket_endpoint,
104
+ no_cdn=no_cdn,
105
+ )
106
+
95
107
  @fastapi_app.websocket(f"/{websocket_endpoint}")
96
108
  async def app_handler(websocket: WebSocket):
97
109
  """
@@ -109,6 +121,8 @@ def app(
109
121
  oauth_state_timeout_seconds=oauth_state_timeout_seconds,
110
122
  upload_endpoint_path=upload_endpoint_path,
111
123
  secret_key=secret_key,
124
+ route_url_strategy=static_files.route_url_strategy,
125
+ proxy_path=proxy_path,
112
126
  ).handle(websocket)
113
127
 
114
128
  if upload_dir:
@@ -133,20 +147,7 @@ def app(
133
147
  """
134
148
  return await FletOAuth().handle(request)
135
149
 
136
- fastapi_app.mount(
137
- path="/",
138
- app=FletStaticFiles(
139
- proxy_path=proxy_path,
140
- assets_dir=assets_dir,
141
- app_name=app_name,
142
- app_short_name=app_short_name,
143
- app_description=app_description,
144
- web_renderer=web_renderer,
145
- route_url_strategy=route_url_strategy,
146
- websocket_endpoint_path=websocket_endpoint,
147
- no_cdn=no_cdn,
148
- ),
149
- )
150
+ fastapi_app.mount(path="/", app=static_files)
150
151
 
151
152
  # Add middleware for custom headers
152
153
  class CustomHeadersMiddleware(BaseHTTPMiddleware):
@@ -16,6 +16,7 @@ from flet.app import AppCallable
16
16
  from flet.controls.base_control import BaseControl
17
17
  from flet.controls.context import _context_page, context
18
18
  from flet.controls.exceptions import FletPageDisconnectedException
19
+ from flet.controls.types import RouteUrlStrategy
19
20
  from flet.messaging.connection import Connection
20
21
  from flet.messaging.protocol import (
21
22
  ControlEventBody,
@@ -59,6 +60,9 @@ class FletApp(Connection):
59
60
  and redirecting to OAuth callback URL.
60
61
  upload_endpoint_path: Absolute URL of upload endpoint, e.g. `/upload`.
61
62
  secret_key: Secret key to sign upload requests.
63
+ route_url_strategy: Route URL strategy of the served web client.
64
+ proxy_path: URL prefix that a proxy in front of the app strips, as given
65
+ to `flet_web.fastapi.app()`; part of the page's base path.
62
66
  """
63
67
 
64
68
  def __init__(
@@ -71,8 +75,12 @@ class FletApp(Connection):
71
75
  oauth_state_timeout_seconds: int = DEFAULT_FLET_OAUTH_STATE_TIMEOUT,
72
76
  upload_endpoint_path: Optional[str] = None,
73
77
  secret_key: Optional[str] = None,
78
+ route_url_strategy: RouteUrlStrategy = RouteUrlStrategy.PATH,
79
+ proxy_path: Optional[str] = None,
74
80
  ):
75
81
  super().__init__()
82
+ self.route_url_strategy = route_url_strategy
83
+ self.__proxy_path = proxy_path
76
84
  self.__id = random_string(8)
77
85
  logger.info(f"New FletApp: {self.__id}")
78
86
 
@@ -124,6 +132,10 @@ class FletApp(Connection):
124
132
  self.pubsubhub = app_manager.get_pubsubhub(self.__main, loop=self.loop)
125
133
  self.page_url = str(websocket.url).rsplit("/", 1)[0]
126
134
  self.page_name = websocket.url.path.rsplit("/", 1)[0].lstrip("/")
135
+ mount_path = (
136
+ (self.__proxy_path or "") + websocket.scope.get("root_path", "")
137
+ ).strip("/")
138
+ self.page_base_path = f"/{mount_path}/" if mount_path else "/"
127
139
 
128
140
  if not self.__upload_endpoint_path:
129
141
  self.__upload_endpoint_path = (
@@ -236,9 +248,14 @@ class FletApp(Connection):
236
248
  continue
237
249
  ptype = data[0]
238
250
  if ptype == 0x00:
239
- await self.__on_message(
240
- msgpack.unpackb(data[1:], ext_hook=decode_ext_from_msgpack)
241
- )
251
+ # One bad message must not end the loop: nothing would be
252
+ # received after it and the app would freeze.
253
+ try:
254
+ await self.__on_message(
255
+ msgpack.unpackb(data[1:], ext_hook=decode_ext_from_msgpack)
256
+ )
257
+ except Exception:
258
+ logger.exception("Error handling a message from the client.")
242
259
  elif ptype == 0x01:
243
260
  if len(data) < 5:
244
261
  logger.debug("Dropping malformed data-channel frame.")
@@ -15,6 +15,8 @@ from starlette.routing import BaseRoute
15
15
 
16
16
  import flet_web.fastapi
17
17
 
18
+ _DEFAULT_RESPONSE_CLASS = Default(JSONResponse)
19
+
18
20
 
19
21
  class FastAPI(fastapi.FastAPI):
20
22
  """
@@ -38,7 +40,7 @@ class FastAPI(fastapi.FastAPI):
38
40
  openapi_tags: Optional[list[dict[str, Any]]] = None,
39
41
  servers: Optional[list[dict[str, Union[str, Any]]]] = None,
40
42
  dependencies: Optional[Sequence[Depends]] = None,
41
- default_response_class: type[Response] = Default(JSONResponse),
43
+ default_response_class: type[Response] = _DEFAULT_RESPONSE_CLASS,
42
44
  redirect_slashes: bool = True,
43
45
  docs_url: Optional[str] = "/docs",
44
46
  redoc_url: Optional[str] = "/redoc",
@@ -139,8 +141,7 @@ class FastAPI(fastapi.FastAPI):
139
141
  terms_of_service=terms_of_service,
140
142
  contact=contact,
141
143
  license_info=license_info,
142
- openapi_prefix=openapi_prefix,
143
- root_path=root_path,
144
+ root_path=root_path or openapi_prefix,
144
145
  root_path_in_servers=root_path_in_servers,
145
146
  responses=responses,
146
147
  callbacks=callbacks,
@@ -69,8 +69,10 @@ class FletStaticFiles(StaticFiles):
69
69
  app_name: PWA application name.
70
70
  app_short_name: PWA application short name.
71
71
  app_description: PWA application description.
72
- web_renderer: Type of web renderer.
73
- route_url_strategy: Routing URL strategy.
72
+ web_renderer: Type of web renderer, as a `WebRenderer` or its value, e.g.
73
+ `"canvaskit"`.
74
+ route_url_strategy: Routing URL strategy, as a `RouteUrlStrategy` or its
75
+ value, e.g. `"hash"`.
74
76
  no_cdn: Whether not load CanvasKit, Pyodide, and fonts from CDN.
75
77
  websocket_endpoint_path: Path of Flet app WebSocket handler. A path
76
78
  without a leading `/` is resolved against the app mount path.
@@ -97,8 +99,8 @@ class FletStaticFiles(StaticFiles):
97
99
  self.__app_name = app_name
98
100
  self.__app_short_name = app_short_name
99
101
  self.__app_description = app_description
100
- self.__web_renderer = web_renderer
101
- self.__route_url_strategy = route_url_strategy
102
+ self.__web_renderer = WebRenderer(web_renderer)
103
+ self.__route_url_strategy = RouteUrlStrategy(route_url_strategy)
102
104
  self.__no_cdn = no_cdn
103
105
  self.__websocket_endpoint_path = websocket_endpoint_path
104
106
  self.__once = Once()
@@ -119,6 +121,14 @@ class FletStaticFiles(StaticFiles):
119
121
  logger.info(f"Route URL strategy configured: {self.__route_url_strategy}")
120
122
  logger.info(f"No CDN configured: {self.__no_cdn}")
121
123
 
124
+ @property
125
+ def route_url_strategy(self) -> RouteUrlStrategy:
126
+ """
127
+ The route URL strategy written into the served `index.html`: the
128
+ `route_url_strategy` argument, or `FLET_WEB_ROUTE_URL_STRATEGY` if set.
129
+ """
130
+ return self.__route_url_strategy
131
+
122
132
  async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
123
133
  await self.__once.do(self.__config, scope["root_path"])
124
134
  await super().__call__(scope, receive, send)
@@ -126,9 +136,16 @@ class FletStaticFiles(StaticFiles):
126
136
  def lookup_path(self, path: str) -> tuple[str, Optional[os.stat_result]]:
127
137
  """Resolve a static file path, with SPA fallback for client-side routes.
128
138
 
129
- Route-like paths (no extension, or `.html`) that don't match a file
130
- fall back to `index.html` so the Flutter client can handle routing.
131
- Asset-like paths (any other extension) resolve to a real `404`.
139
+ With the `path` route URL strategy, route-like paths (no extension, or
140
+ `.html`) that don't match a file fall back to `index.html` so the
141
+ Flutter client can handle routing. Asset-like paths (any other
142
+ extension) resolve to a real `404`.
143
+
144
+ With the `hash` strategy, routes live in the URL fragment and never
145
+ reach the server, so there is no fallback: any path that doesn't match
146
+ a file resolves to a real `404` (with a `404.html` from the assets
147
+ directory as its body, if there is one), while the mount root still
148
+ serves `index.html`.
132
149
 
133
150
  Args:
134
151
  path: Requested path, relative to the mounted static root
@@ -141,7 +158,10 @@ class FletStaticFiles(StaticFiles):
141
158
  logger.debug(f"StaticFiles.lookup_path: {self.__app_mount_path} {path}")
142
159
  full_path, stat_result = super().lookup_path(path)
143
160
 
144
- if stat_result is not None:
161
+ if (
162
+ stat_result is not None
163
+ or self.__route_url_strategy == RouteUrlStrategy.HASH
164
+ ):
145
165
  return full_path, stat_result
146
166
 
147
167
  # Not found: SPA fallback only for route-like paths.
@@ -85,7 +85,7 @@ def patch_index_html(
85
85
  # loads it via dynamic `import()`. We can no longer use `.js` because
86
86
  # Pyodide >= 0.29 throws "Classic web workers are not supported" inside
87
87
  # any worker that has `importScripts` available — only classic workers
88
- # have it. All supported Pyodide versions (0.27.7 / 0.29.4 / 314.0.6)
88
+ # have it. All supported Pyodide versions (0.27.8 / 0.29.5 / 314.0.7)
89
89
  # ship `pyodide.mjs`.
90
90
  if pyodide_version:
91
91
  if no_cdn:
@@ -0,0 +1 @@
1
+ version = "1.1.0.dev1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-web
3
- Version: 1.0.4
3
+ Version: 1.1.0.dev1
4
4
  Summary: Flet web client in Flutter.
5
5
  Author-email: "Appveyor Systems Inc." <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -9,11 +9,12 @@ Project-URL: Repository, https://github.com/flet-dev/flet
9
9
  Project-URL: Documentation, https://flet.dev/docs
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: flet==1.0.4
12
+ Requires-Dist: flet==1.1.0.dev1
13
13
  Requires-Dist: fastapi>=0.115.12
14
14
  Requires-Dist: websockets>=13.0
15
15
  Requires-Dist: uvicorn>=0.35.0
16
- Requires-Dist: uvicorn[standard]>=0.35.0; sys_platform != "win32" or platform_machine != "ARM64"
16
+ Requires-Dist: uvicorn[standard]>=0.35.0; sys_platform != "win32" or platform_machine != "ARM64" or python_version >= "3.12"
17
+ Requires-Dist: httptools>=0.9.0; sys_platform == "win32" and platform_machine == "ARM64" and python_version >= "3.12"
17
18
 
18
19
  # Flet Web client in Flutter
19
20
 
@@ -19,5 +19,6 @@ src/flet_web/fastapi/flet_static_files.py
19
19
  src/flet_web/fastapi/flet_upload.py
20
20
  src/flet_web/fastapi/oauth_state.py
21
21
  src/flet_web/fastapi/serve_fastapi_web_app.py
22
+ tests/test_fastapi.py
22
23
  tests/test_loading_logo.py
23
24
  tests/test_patch_index.py
@@ -0,0 +1,10 @@
1
+ flet==1.1.0.dev1
2
+ fastapi>=0.115.12
3
+ websockets>=13.0
4
+ uvicorn>=0.35.0
5
+
6
+ [:sys_platform != "win32" or platform_machine != "ARM64" or python_version >= "3.12"]
7
+ uvicorn[standard]>=0.35.0
8
+
9
+ [:sys_platform == "win32" and platform_machine == "ARM64" and python_version >= "3.12"]
10
+ httptools>=0.9.0
@@ -0,0 +1,34 @@
1
+ import pytest
2
+ from fastapi.testclient import TestClient
3
+
4
+ from flet_web.fastapi import FastAPI
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ ("kwargs", "expected_root_path"),
9
+ [
10
+ ({}, ""),
11
+ ({"root_path": "/app"}, "/app"),
12
+ ({"openapi_prefix": "/legacy"}, "/legacy"),
13
+ ({"openapi_prefix": "/legacy", "root_path": "/app"}, "/app"),
14
+ ],
15
+ )
16
+ def test_fastapi_root_path_and_lifecycle(kwargs, expected_root_path):
17
+ events = []
18
+ app = FastAPI(
19
+ **kwargs,
20
+ on_startup=[lambda: events.append("startup")],
21
+ on_shutdown=[lambda: events.append("shutdown")],
22
+ )
23
+
24
+ @app.get("/path")
25
+ def get_root_path():
26
+ return {"root_path": app.root_path}
27
+
28
+ with TestClient(app) as client:
29
+ assert events == ["startup"]
30
+ response = client.get("/path")
31
+ assert response.status_code == 200
32
+ assert response.json() == {"root_path": expected_root_path}
33
+
34
+ assert events == ["startup", "shutdown"]
@@ -1 +0,0 @@
1
- version = "1.0.4"
@@ -1,7 +0,0 @@
1
- flet==1.0.4
2
- fastapi>=0.115.12
3
- websockets>=13.0
4
- uvicorn>=0.35.0
5
-
6
- [:sys_platform != "win32" or platform_machine != "ARM64"]
7
- uvicorn[standard]>=0.35.0
File without changes
File without changes