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.
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.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 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.dev1both 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.
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.dev1
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.dev1.tar.gz | 711.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| desktop_flask-0.1.0.dev1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / desktop_flask-0.1.0.dev1.tar.gz
| Download URL | desktop_flask-0.1.0.dev1.tar.gz |
|---|---|
| Size | 711.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d6ad0e4f14d7fc21c8a2099ca781c21258c1f04140e343c2e36f0a6c4ca777bf
|
|
BLAKE2b-256 checksum How to use checksums |
7ebe6fd46408007f27a085b9e42b0f8a03171c50955faf1a99faae554043d6c0
|
| 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.dev1-py3-none-any.whl
| Download URL | desktop_flask-0.1.0.dev1-py3-none-any.whl |
|---|---|
| Size | 1.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0c634709bd989bee3db260ae3d5c9e8bace96a5719f5441d0ad01dd552e842e5
|
|
BLAKE2b-256 checksum How to use checksums |
8b6e7d473e4ceb33f48d11bc7fe3d2aa9f8cb22e365097cdaa615cdcb3beab27
|
| 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}
|