Metadata-Version: 2.4
Name: desktop_flask
Version: 0.1.0.dev1
Summary: Run Flask apps in a desktop window: WebView2 renders, an in-process WSGI bridge serves, no TCP port is opened.
Author-email: mzy <453778839@qq.com>
License-Expression: BSD-3-Clause
Project-URL: Homepage, https://gitee.com/chwu666/desktop_flask
Project-URL: Repository, https://gitee.com/chwu666/desktop_flask
Project-URL: Issues, https://gitee.com/chwu666/desktop_flask/issues
Project-URL: Documentation, https://gitee.com/chwu666/desktop_flask/blob/main/docs/README.md
Project-URL: Changelog, https://gitee.com/chwu666/desktop_flask/blob/main/CHANGELOG.md
Keywords: flask,desktop,webview2,gui,wsgi,desktop-application,nuitka
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Win32 (MS Windows)
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: User Interfaces
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
License-File: NOTICE.md
Requires-Dist: flask>=3.1.0
Requires-Dist: pythonnet>=3.0; sys_platform == "win32"
Provides-Extra: desktop
Requires-Dist: pythonnet>=3.0; sys_platform == "win32" and extra == "desktop"
Provides-Extra: build
Requires-Dist: nuitka>=2.5; extra == "build"
Dynamic: license-file

[English](https://gitee.com/chwu666/desktop_flask/blob/main/README.md) · [简体中文](https://gitee.com/chwu666/desktop_flask/blob/main/README.zh-CN.md)

# desktop_flask

Run a Flask app in a desktop window: **WebView2 renders the UI, an in-process WSGI
bridge serves it, and no TCP port is ever opened.**

`desktop_flask` is a standalone extension library — it does **not** modify Flask's
source. You keep `from flask import Flask` and add one line:

```python
from flask import Flask
import desktop_flask

app = Flask(__name__)
desktop_flask.init_app(app)          # ← the only wiring step

@app.get("/")
def get_index():
    return render_template("index.html")

if __name__ == "__main__":
    app.run_desktop(title="My tool")
```

Or without any injection:

```python
desktop_flask.run_desktop(app, title="My tool")
```

From the command line:

```console
flask --app myapp desktop                      # via the flask.commands entry point
flask --app myapp desktop --self-check         # built-in WebView2 self check (S1–S19)

desktop-flask myapp:app --title "My tool"      # launcher, no Flask CLI needed
python -m desktop_flask myapp:create_app()     # the same thing
```

> **Status: `0.1.0.dev1` — developer preview.** The bridge core, the Flask
> integration, the WebView2 host and the packaging story are done and tested on a
> real machine, but the API and the `DESKTOP_*` keys may still shift before
> `0.1.0`. Desktop-specific optimisations are still ahead. Breaking changes will be
> listed in [CHANGELOG.md](https://gitee.com/chwu666/desktop_flask/blob/main/CHANGELOG.md).

## Install

```console
pip install desktop_flask
```

That is enough on Windows: `pythonnet` is a base dependency (`sys_platform ==
'win32'`), so `run_desktop()` works right after installing. `desktop_flask[desktop]`
is still accepted as an alias for older instructions.

* The WebView2 **Runtime** is required at run time. Windows 11 has it; older
  Windows 10 may not. `run_desktop(install_runtime=True)` can offer to install it.
* The WebView2 **managed DLLs and native loader are bundled in the package**, so
  there is no separate download step.
* **Windows only** for now. There is no macOS/Linux backend — `run_desktop()` says
  so instead of silently opening a port.

> ⚠️ Do not put a version **range** in your requirements for this pre-release:
> `desktop_flask>=0.1` does not resolve, because pip excludes pre-releases when the
> requirement carries a specifier. Plain `pip install desktop_flask` and
> `pip install desktop_flask==0.1.0.dev1` both work. Once `0.1.0` is published,
> ranges start working and upgrades move to the final release automatically.

## What it changes

| | Ordinary `app.run()` | `desktop_flask` |
|---|---|---|
| Transport | TCP socket on 127.0.0.1 | direct `app.wsgi_app(environ, start_response)` call |
| Reachable from other processes | yes | **no** — requests never leave the process |
| Port conflicts / firewall prompts | possible | impossible |
| Page origin | `http://127.0.0.1:5000` | `df://app` (registered custom scheme, tuple origin) |
| Cookies | Chromium | the bridge's own cookie jar (Chromium cookies do not work on custom schemes) |
| WebSocket / SSE | yes | **not supported** — realtime uses `postMessage` (`desktop_flask.emit`) |
| Backend code | — | **unchanged** |

## Packaging your own app into an exe

```console
pip install "desktop_flask[desktop,build]"
desktop-flask-build app.py
```

You get `build/app.dist/` — ship that whole directory. **Nuitka also needs a C
compiler** (MSVC or MinGW64); `pip` cannot install it, and the tool's preflight will
tell you if it is missing before starting a ten-minute compile. See
[docs/packaging.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/packaging.md)
for the two traps that make a build succeed and still not run, and for the
distribution checklist.

## Reading the docs offline

The documentation ships **inside the package**, so it is readable without any
network access:

```console
desktop-flask-docs                  # list the pages (Chinese, the fuller set)
desktop-flask-docs --en             # English pages
desktop-flask-docs packaging --cat  # print one page
```

## What's inside

| | |
|---|---|
| **Transport** | `WSGIBridge` — builds a WSGI `environ` from a WebView2 request, calls `app.wsgi_app`, captures the response. No socket, no port. |
| **Worker pool** | `DESKTOP_WORKERS` (4 by default) — request handling does not occupy the UI thread. The reason is throughput, not "freezing": WebView2 renders in its own process. |
| **Static asset cache** | in-memory LRU (32 MiB default) with strict HTTP semantics and ETag/304 revalidation |
| **Realtime channel** | `postMessage` message bus (`desktop_flask.emit()` / `desktopFlask.invoke()`) — the WebSocket replacement |
| **Single instance** | `DESKTOP_SINGLE_INSTANCE` — a named kernel mutex keyed on the `userDataFolder`, so "the second copy is silently broken" becomes a readable message |
| **Runtime detection + guided install** | registry-based detection, actionable instructions, and an opt-in (`DESKTOP_INSTALL_RUNTIME`) download + silent install of Microsoft's Evergreen bootstrapper |
| **Crash reports** | `sys.excepthook` + `threading.excepthook` + `sys.unraisablehook` + `faulthandler`, each writing an editable report with an environment block |
| **`userDataFolder` management** | `%LOCALAPPDATA%\<app-id>\WebView2`, overridable, with `DESKTOP_APP_ID` as the identity knob |
| **Packaging** | `desktop-flask-build` — Nuitka standalone/onefile builds with the vendored DLLs, the icon, and the flags that would otherwise fail *silently* |
| **Icon** | drawn in pure Python by `scripts/make_icon.py`; used for both the exe and the window |
| **Built-in self check** | `--self-check` — S1–S19 in a real WebView2 window, on your machine |

## Documentation

| Document | Contents |
|---|---|
| [docs/README.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/README.md) | documentation index (English); Chinese version under [docs/zh/](https://gitee.com/chwu666/desktop_flask/blob/main/docs/zh/README.md) |
| [docs/configuration.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/configuration.md) | every `DESKTOP_*` key, `init_app()`/`run_desktop()`/CLI option, and the reasoning behind the defaults |
| [docs/troubleshooting.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/troubleshooting.md) | the failures that actually happen, with the fix that worked |
| [docs/packaging.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/packaging.md) | Nuitka packaging, why not PyInstaller, the frozen-path traps, distribution checklist |
| [docs/zh/python-3.14-free-threading.md](https://gitee.com/chwu666/desktop_flask/blob/main/docs/zh/python-3.14-free-threading.md) | R17: what free-threading (PEP 703/779) would and would not buy, and the prep needed to test it — **Chinese only** |

Developer-facing design notes (architecture, measured findings, ADRs) live in the
**carrier repository** this library was developed in: `DESKTOP_MODE_DESIGN.md` and
the `m0_spike/`, `m1_bridge/`, `m3_host/`, `m4_engineering/` conclusion documents.
They are not part of this package.

## Boundaries (by design, not bugs)

* **No WebSocket.** The constructor itself refuses a non-`http(s)`/`ws(s)` URL on a
  custom scheme. Use `desktop_flask.emit()` / `desktopFlask.invoke()`.
* **No SSE / streaming responses.** WebView2 requires the complete body before the
  deferral completes. Push progress through the message bridge instead.
* **Cookies are the bridge's, not Chromium's.** `document.cookie` stays empty —
  that is expected. `localStorage` / `IndexedDB` work normally.
* **No Service Worker** (Blink requires http/https).
* **Windows only** for now; the interface is designed to be extended.

## Not part of Flask

`desktop_flask` is an independent extension library: it is **not** part of Flask and
is **not** affiliated with, or endorsed by, the Pallets organisation. It redistributes
Microsoft's WebView2 assemblies verbatim under their own license. See
[LICENSE.txt](https://gitee.com/chwu666/desktop_flask/blob/main/LICENSE.txt)
(BSD-3-Clause) and
[NOTICE.md](https://gitee.com/chwu666/desktop_flask/blob/main/NOTICE.md) for the full
attribution list.

## Layout

This directory **is** the library's repository root — `src/`, `tests/`, `scripts/`,
`docs/` and `examples/` are siblings here, so the whole directory can be pushed as a
standalone repository.

```
src/desktop_flask/          the library
├── bridge.py               WSGI bridge: environ, WSGI call, response capture
├── adapter.py              makes url_for(_external=True) produce df:// URLs
├── cookies.py              bridge-managed cookie jar
├── bodystore.py            memory vs. temp file for request/response bodies
├── cache.py                static asset LRU + HTTP conditional requests
├── pipeline.py             cache + bridge, host-independent
├── workers.py              request thread pool + deferral lifetime
├── runtime.py              single instance, crash reports, Runtime detection/install
├── _frozen.py              "is this a frozen build?" (Nuitka does not set sys.frozen)
├── origin.py               origin strategies (df://app, http://df.localhost)
├── compat.py               legacy-origin forwarding, body rewriting, navigation policy
├── messages.py             postMessage message bus (the WebSocket replacement)
├── selfcheck.py            built-in S1–S19 self check page
├── server.py               DebugHttpServer (development only; opens a port)
├── integration.py          init_app(): runtime wiring, no Flask patch
├── packaging.py            desktop-flask-build (Nuitka)
├── docs.py                 desktop-flask-docs (the bundled documentation)
├── cli.py                  `flask desktop`
├── run.py / __main__.py    `desktop-flask` launcher
├── assets/                 icon (generated by scripts/make_icon.py)
├── _vendor/webview2/       WebView2 managed DLLs + native loader (vendored)
└── platforms/windows.py    WebView2 host (pythonnet + WinForms)
docs/                       documentation (English)
docs/zh/                    documentation (Simplified Chinese)
scripts/                    fetch_webview2_dlls.py, make_icon.py, build_nuitka.py, frozen_probe.py
tests/                      the library's test suite
examples/desktop_demo/      a demo app showing cache / workers / message bridge
examples/tutorial/          the official Flask tutorial, run unchanged (acceptance)
pyproject.toml              distribution metadata
LICENSE.txt / NOTICE.md     license and third-party attribution
CHANGELOG.md                what changed, and the version policy
```
