Desktop Applications (lila desktop)

A Lila app is a normal ASGI application, so it can also run inside a native window: the server starts on a local port and the OS webview (WebView2/WebKit) renders it. Measured footprint for the Python side: import lila ≈ 19 MB RSS and a full App ≈ 42 MB; the window itself uses the webview already installed in the operating system instead of bundling a browser.

1. Running in Desktop Mode

lila desktop dev loads .env, finds a free port, starts Uvicorn in a daemon thread and opens a native window. It binds only to 127.0.0.1, allows a single instance and gates the session with a per-run token that is exchanged for an HttpOnly cookie on the first navigation:

# Starts Uvicorn in a daemon thread and opens a native window
lila desktop dev

# Custom options:
lila desktop dev --title "My Desktop App" --width 1280 --height 850
💡 Dual Window Engine:
  • Preferred (pywebview): Uses Microsoft WebView2 on Windows, WebKit on macOS, and WebKitGTK on Linux. Install with: pip install "lila-framework[desktop]"
  • Fallback: if pywebview is not installed, Lila detects Edge, Chrome or Brave and launches an isolated standalone --app window; if none is found it opens the default browser.

2. Compiling Standalone Executables (.exe / Binary)

lila desktop build writes a temporary bootstrap that imports main statically (so PyInstaller can see your code), then bundles main, app and Lila's data files (--collect-all lila), plus public/ and resources/ when they exist. Output goes to dist/<name>.

# Install packaging dependencies:
pip install pyinstaller pywebview
# or: pip install "lila-framework[desktop]"

# Build a single-file executable:
lila desktop build --name "InvoiceManager" --onefile --windowed

# Onedir build (faster startup, easier to inspect):
lila desktop build --name "InvoiceManager" --onedir

# The compiled binary is ready in dist/InvoiceManager (InvoiceManager.exe on Windows).

3. Packaging Assets and PyInstaller sys._MEIPASS

Lila's lila.core.config resolves read-only assets through get_base_path() (honoring sys._MEIPASS when frozen) and writable files (SQLite database, cache, logs) through get_data_path(). In a frozen build the writable location is the OS application-data directory (%LOCALAPPDATA%\\<title> on Windows, ~/Library/Application Support/<title> on macOS, $XDG_DATA_HOME/<title> on Linux), not the read-only executable folder. On a frozen build, Lila looks for .env in that application-data directory first (then the current working directory) and keeps the SQLite database, cache and logs there. Keep DB_NAME relative (DB_NAME="lila") so it resolves under that directory.