This release is a pre-release and may not be stable for production use.
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.
Vibe Coding — this project is built by a human and an AI together. The author sets the requirements, the architecture decisions and the acceptance criteria, and does the real-machine verification; an AI agent writes most of the code, tests and documentation. Nothing here is "generated and shipped unseen": every non-obvious claim was measured on a real machine and the measurements were kept. See How this was built.
desktop_flask is a standalone extension library — it does not modify Flask's
source. You keep from flask import Flask and add one line:
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:
desktop_flask.run_desktop(app, title="My tool")
From the command line:
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.dev2— 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 theDESKTOP_*keys may still shift before0.1.0. Desktop-specific optimisations are still ahead. Breaking changes will be listed in CHANGELOG.md.
Install
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.1does not resolve, because pip excludes pre-releases when the requirement carries a specifier. Plainpip install desktop_flaskandpip install desktop_flask==0.1.0.dev2both work. Once0.1.0is 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
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
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:
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 | documentation index (English); Chinese version under docs/zh/ |
| docs/configuration.md | every DESKTOP_* key, init_app()/run_desktop()/CLI option, and the reasoning behind the defaults |
| docs/troubleshooting.md | the failures that actually happen, with the fix that worked |
| docs/packaging.md | Nuitka packaging, why not PyInstaller, the frozen-path traps, distribution checklist |
| 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. Usedesktop_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.cookiestays empty — that is expected.localStorage/IndexedDBwork normally. - No Service Worker (Blink requires http/https).
- Windows only for now; the interface is designed to be extended.
How this was built (Vibe Coding)
This project is a human–AI collaboration, and it is explicit about which part is whose.
| Who | Did what |
|---|---|
| Human author | the goal and the requirements; the architecture decisions (a custom df:// scheme instead of a local port, bridge-managed cookies, no WebSocket, Nuitka instead of PyInstaller); the acceptance criteria; every real-machine check; the release |
| AI agent | most of the implementation, the test suite, the documentation in both languages, and the release scripts |
Why it can still be trusted:
- Nothing was accepted because it looked right. Every non-obvious decision names
its evidence — a measurement, an official document quote, or a real-machine run
that failed and forced a change. The WebView2 quirks that cost the most time are
recorded rather than hidden:
--include-data-dirsilently dropping.dll; Nuitka not settingsys.frozen; pythonnet convertingbyteselement-by-element at ~1000× the cost of oneMarshal.Copy. - The gates are real. 942 tests in the development tree, 450 in an environment
with the unmodified official Flask,
ruff+mypy --strict, and a packaged build that passes the built-in self check 15 passed / 0 failed / 4 info on a real Windows desktop. - The limits are stated, not papered over. Windows only, no WebSocket/SSE, no streaming responses, cookies owned by the bridge — all listed under Boundaries.
What to expect because of it:
- The comments and docs are unusually dense; an AI writes more explanation than a human usually bothers to. That is deliberate here, but it is a style you may not like.
- The English documentation is a translation: Chinese is the first-class language of this project and the fuller version.
- If you find a mistake that looks AI-shaped — confidently wrong, or copied from a similar-looking case — an issue is the fastest fix. That is exactly the kind of report this mode of development needs.
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
(BSD-3-Clause) and
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
Metadata
Release files for desktop_flask 0.1.0.dev2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| desktop_flask-0.1.0.dev2.tar.gz | 716.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| desktop_flask-0.1.0.dev2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / desktop_flask-0.1.0.dev2.tar.gz
| Download URL | desktop_flask-0.1.0.dev2.tar.gz |
|---|---|
| Size | 716.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34e87f9a06a90fc2710709275dc377bb55ca2e22f93a6d25bf3e59889c00db02
|
|
BLAKE2b-256 checksum How to use checksums |
025aa3f735ffefbee13938663cee082eb0878e75d7de4b080da5fa99e128d5db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / desktop_flask-0.1.0.dev2-py3-none-any.whl
| Download URL | desktop_flask-0.1.0.dev2-py3-none-any.whl |
|---|---|
| Size | 1.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f12f4933fff6c1f8577b580bd19780733c4681eb096918f7d030224ea58e7889
|
|
BLAKE2b-256 checksum How to use checksums |
b0cf27d514a76a0f160fc9d13fbda2db272f42bf7d83bf4c9a0aec1ab0fdac9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|