Skip to content

Commit af2c9ec

Browse files
committed
Add Flet version resolution and fallback mechanism in examples_gallery.py
1 parent b89018c commit af2c9ec

2 files changed

Lines changed: 155 additions & 31 deletions

File tree

.github/workflows/docs.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ jobs:
1616
uses: astral-sh/setup-uv@v6
1717

1818
- name: Build docs
19-
working-directory: sdk/python
19+
working-directory: sdk/python/packages/flet
2020
run: |
21-
uv sync --group docs -v
22-
uv pip install --no-deps --force-reinstall flet-web>=0.70.0.dev0
23-
cd packages/flet
24-
uv run mkdocs build -v
25-
# run: |
26-
# cd sdk/python/packages/flet
27-
# uv sync --group docs -v
28-
# uv pip install -v --no-deps --force-reinstall flet-web>=0.70.0.dev0
29-
# uv run mkdocs build -v
21+
uv run --group docs mkdocs build -v

sdk/python/packages/flet/src/flet/docs_plugins/examples_gallery.py

Lines changed: 153 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
from mkdocs.config import config_options
1313
from mkdocs.config.defaults import MkDocsConfig
1414
from mkdocs.plugins import BasePlugin
15+
from packaging.requirements import Requirement
16+
17+
try: # Python 3.11+
18+
import tomllib
19+
except ModuleNotFoundError: # pragma: no cover - fallback for 3.10
20+
import tomli as tomllib # type: ignore[no-redef]
1521

1622
logger = logging.getLogger("flet.docs.examples_gallery")
1723

@@ -46,10 +52,118 @@ def _ensure_pip_available() -> None:
4652

4753

4854
def _web_path_ready(path: Path) -> bool:
49-
"""Return True when ``path`` points to a usable web client."""
55+
"""Return True when `path` points to a usable web client."""
5056
return path.is_dir() and path.joinpath("index.html").is_file()
5157

5258

59+
def _find_pyproject(start: Path, stop: Optional[Path] = None) -> Optional[Path]:
60+
"""Walk up from start looking for pyproject.toml, stopping at `stop` if provided."""
61+
current = start.resolve()
62+
stop = stop.resolve() if stop else None
63+
while True:
64+
candidate = current / "pyproject.toml"
65+
if candidate.is_file():
66+
return candidate
67+
if current == current.parent or (stop and current == stop):
68+
return None
69+
current = current.parent
70+
71+
72+
def _version_from_specifiers(specifiers) -> Optional[str]:
73+
"""Extract a deterministic version from a SpecifierSet.
74+
75+
Prioritises exact pins to avoid drift; otherwise falls back to the lowest
76+
acceptable bound (>=, >, ~=) to stay compatible with the source tree.
77+
"""
78+
for operator in ("==", "==="):
79+
for spec in specifiers:
80+
if spec.operator == operator:
81+
return spec.version
82+
83+
for operator in ("~=", ">=", ">"):
84+
for spec in specifiers:
85+
if spec.operator == operator:
86+
return spec.version
87+
88+
return None
89+
90+
91+
def _version_from_pyproject(pyproject: Path) -> Optional[str]:
92+
"""
93+
Read the flet dependency from pyproject.toml and return a pinned/lowest version.
94+
"""
95+
try:
96+
data = tomllib.loads(pyproject.read_text())
97+
except Exception as exc: # noqa: BLE001
98+
logger.debug("Unable to parse %s: %s", pyproject, exc)
99+
return None
100+
101+
deps = data.get("project", {}).get("dependencies") or []
102+
for raw in deps:
103+
try:
104+
req = Requirement(raw)
105+
except Exception: # noqa: BLE001
106+
continue
107+
if req.name != "flet":
108+
continue
109+
candidate = _version_from_specifiers(req.specifier)
110+
if candidate:
111+
return candidate
112+
return None
113+
114+
115+
def _resolve_flet_version(
116+
env: Mapping[str, str], src_root: Path, docs_dir: Path
117+
) -> str:
118+
"""Resolve the Flet version similar to `flet publish`.
119+
120+
Order:
121+
- Explicit override: FLET_WEB_VERSION.
122+
- Version pinned in the gallery's pyproject.toml (walk up from src).
123+
- The version exposed by the local flet package
124+
(patched in releases or derived from git tags).
125+
- DEFAULT_VERSION from flet.version.
126+
127+
This keeps docs builds reproducible for historical commits and avoids
128+
pulling newer incompatible clients.
129+
"""
130+
default_version: Optional[str] = None
131+
try:
132+
from flet import version as flet_version
133+
134+
default_version = getattr(flet_version, "DEFAULT_VERSION", None)
135+
except Exception as exc: # noqa: BLE001
136+
logger.debug("Unable to preload flet version defaults: %s", exc)
137+
138+
if env.get("FLET_WEB_VERSION"):
139+
return str(env["FLET_WEB_VERSION"])
140+
141+
pyproject = _find_pyproject(src_root, stop=docs_dir.parent)
142+
pinned = _version_from_pyproject(pyproject) if pyproject else None
143+
if pinned:
144+
return pinned
145+
146+
try:
147+
from flet import version as flet_version
148+
149+
resolved = flet_version.version or getattr(
150+
flet_version, "DEFAULT_VERSION", None
151+
)
152+
resolved = resolved or default_version
153+
if resolved:
154+
return str(resolved)
155+
except Exception as exc: # noqa: BLE001
156+
logger.debug("Unable to resolve flet version from package: %s", exc)
157+
158+
if default_version:
159+
return str(default_version)
160+
161+
raise RuntimeError(
162+
"FLET_WEB_PATH is not set and Flet version could not be determined. "
163+
"Set FLET_WEB_VERSION or provide FLET_WEB_PATH pointing to a built client."
164+
)
165+
166+
53167
def _locate_existing_web_client(env: Mapping[str, str]) -> Optional[Path]:
54168
"""Check configured env or installed packages for a usable web client."""
55169
configured = env.get("FLET_WEB_PATH")
@@ -77,7 +191,10 @@ def _locate_existing_web_client(env: Mapping[str, str]) -> Optional[Path]:
77191

78192

79193
def _download_packaged_web_client(version: str) -> Optional[Path]:
80-
"""Download the pre-built flet-web wheel and expose its web folder."""
194+
"""Download the pre-built flet-web wheel and expose its web folder.
195+
196+
Uses a temp/versioned cache to avoid repeated downloads during local dev.
197+
"""
81198
cache_root = Path(tempfile.gettempdir()) / "flet-web"
82199
target_root = cache_root / version
83200
web_dir = target_root / "flet_web" / "web"
@@ -110,42 +227,56 @@ def _download_packaged_web_client(version: str) -> Optional[Path]:
110227
)
111228
except subprocess.CalledProcessError as exc:
112229
logger.warning("Unable to download flet-web==%s: %s", version, exc)
113-
return None
230+
logger.info("Falling back to latest available pre-release flet-web wheel.")
231+
try:
232+
subprocess.run(
233+
[
234+
sys.executable,
235+
"-m",
236+
"pip",
237+
"install",
238+
"--no-deps",
239+
"--only-binary",
240+
":all:",
241+
"--pre",
242+
"flet-web",
243+
"--target",
244+
str(target_root),
245+
],
246+
check=True,
247+
)
248+
except subprocess.CalledProcessError as exc2:
249+
logger.warning("Unable to download a fallback flet-web wheel: %s", exc2)
250+
return None
114251

115252
return web_dir if _web_path_ready(web_dir) else None
116253

117254

118-
def _ensure_web_client(env: dict[str, str]) -> Path:
119-
"""Ensure a web client exists and return the path to use for publishing."""
255+
def _ensure_web_client(env: dict[str, str], src_root: Path, docs_dir: Path) -> Path:
256+
"""Ensure a web client exists and return the path to use for publishing.
257+
258+
Prefers an already-available client (env or packaged). If none exists,
259+
resolves a version and downloads the wheel into a temp cache.
260+
"""
120261
existing = _locate_existing_web_client(env)
121262
if existing:
122263
return existing
123264

124-
try:
125-
from flet import version as flet_version
126-
except Exception as exc: # noqa: BLE001
127-
raise RuntimeError(
128-
"FLET_WEB_PATH is not set and the flet version could not be determined to "
129-
"download a packaged web client. Set FLET_WEB_PATH manually to a built "
130-
"client (e.g. client/build/web)."
131-
) from exc
132-
133-
target_version = flet_version.version or getattr(
134-
flet_version, "DEFAULT_VERSION", ""
135-
)
265+
target_version = _resolve_flet_version(env, src_root, docs_dir)
136266
downloaded = _download_packaged_web_client(target_version)
137267
if downloaded:
138268
return downloaded
139269

140270
raise RuntimeError(
141271
"FLET_WEB_PATH is not set and no packaged web client could be downloaded "
142-
f"(tried flet-web=={target_version}). Provide FLET_WEB_PATH pointing to a "
143-
"built client or ensure network access to download the flet-web wheel."
272+
f"(tried flet-web=={target_version} and latest pre-release). Provide "
273+
"FLET_WEB_PATH pointing to a built client or ensure network access to "
274+
"download the flet-web wheel."
144275
)
145276

146277

147278
def _latest_mtime(root: Path, ignored: Optional[Iterable[Path]] = None) -> float:
148-
"""Return the newest mtime under ``root`` ignoring any ``ignored`` directories."""
279+
"""Return the newest mtime under `root` ignoring any `ignored` directories."""
149280
root = root.resolve()
150281
ignore_roots = tuple(p.resolve() for p in ignored or ())
151282
latest = 0.0
@@ -177,6 +308,7 @@ class ExamplesGalleryPlugin(BasePlugin):
177308
- FLET_SKIP_EXAMPLES_GALLERY=1 — skip building (useful for fast local docs iteration).
178309
- FLET_WEB_PATH — optional path to a built Flet web client; if unset the plugin
179310
downloads the matching flet-web wheel into a temp cache.
311+
- FLET_WEB_VERSION — optional version override for the wheel download.
180312
181313
Config options (mkdocs.yml):
182314
- enabled: bool (default True)
@@ -254,7 +386,7 @@ def on_pre_build(self, config: MkDocsConfig) -> None:
254386
extra_env = self.config.get("env") or {}
255387
env.update({k: str(v) for k, v in extra_env.items()})
256388

257-
web_client_path = _ensure_web_client(env)
389+
web_client_path = _ensure_web_client(env, src_root, docs_dir)
258390
env["FLET_WEB_PATH"] = str(web_client_path)
259391

260392
_ensure_pip_available()

0 commit comments

Comments
 (0)