wesktop v0.10.0 /API Reference
On this page

API reference for wesktop's native symbols: run() desktop window, GUI backend detection, desktop entries, dev mode, SDUI primitives, and version metadata.

#API Reference

wesktop exposes 116 public symbols via import wesktop. Most of these are re-exports from fastware -- the ASGI micro-framework that provides routing, responses, SSE, middleware, auth, dependency injection, config, testing, server lifecycle, background tasks, feature flags, audit logging, error logging, and MCP support. For documentation on those symbols, see the fastware API reference.

This page documents the symbols that are native to wesktop -- the desktop shell, entry management, SDUI primitives, GUI backend detection, and dev mode. The library is validated by 131 tests across 8 test modules.

#Desktop Window

The desktop module provides the run() function for launching native OS windows backed by a Granian ASGI server in a daemon thread. The pywebview dependency is late-imported, so headless deployments that only use serve() never load the GUI library.

#src.wesktop.desktop

Native desktop window via pywebview, backed by a detached Granian ASGI server subprocess, with cross-process window refcounting and automatic server lifecycle.

Process-group / coordination story ---------------------------------- wesktop group-manages exactly ONE child: the detached server subprocess spawned by serve_background (which becomes its own process-group leader so stop can signal the whole group and reap granian workers). wesktop does NOT own the renderer child processes -- pywebview spawns and owns those (WebKitGTK/WebView2/ Cocoa) inside webview.start().

Because a single wesktop process can neither see nor count another wesktop process's windows, window lifecycle is coordinated through the filesystem, not in-process state:

  • Window markers (kind="window" in the fastware instance registry): one

marker file per open native window, carrying {pid, window_id}. Written before webview.start() and removed after it returns. The live-marker count (dead PIDs pruned by kill -0) is the true number of open windows across ALL wesktop processes sharing this app's server. The server is stopped only when zero live window markers remain after this process's window closes -- so process A closing its last window never kills the server under process B's still-open window.

  • Focus-request markers (kind="focus-request"): a platform-neutral,

file-based focus signal. With second_open="focus-existing", a second launch drops a focus-request marker and exits; the window-owning process runs a ~1s daemon poll that consumes the request and raises its window. No DBus, no AppleEvents.

  • Registry entries (list_instances): the detached server registers its

own {pid, port, name} descriptor. Together the registry entry and the live window markers are the cross-process enumeration surface (see :func:list_app_instances).

#_wire_runtime_bridge

python
def _wire_runtime_bridge(window: object, url: str) -> None

Best-effort host-side update wiring for a native window.

Captures the window and, where pywebview exposes a focus event, polls /__fastware/version on focus to reload on a changed build id. This is a no-op on pywebview builds without a focus event -- native windows load the same page + client.js, which is the primary (poll-free) update path.

#_app_url

python
def _app_url(host: str, port: int) -> str

Compose the same-origin packaged app URL from host and port.

The single source of truth for the URL an app window loads. In the join path the port comes from the port file; in the new-server path serve_background returns the same http://host:port form.

#_version_url

python
def _version_url(url: str) -> str

The fastware version endpoint for a given app URL.

#_port_from_url

python
def _port_from_url(url: str) -> int

Extract the TCP port from an http://host:port URL.

#_startup_handshake

python
def _startup_handshake(url: str, *, timeout: float=5.0) -> str | None

Fetch /__fastware/version after window creation; loud stderr on failure.

Returns the observed build id, or None if the server is unreachable or the payload is malformed within timeout. The window is NEVER torn down on failure -- the user's window stays -- but the failure is logged loudly to stderr so it is unmissable.

#_inject_runtime_config

python
def _inject_runtime_config(window: object, build_id: str | None, port: int, app_name: str) -> bool

Best-effort: set window.__wesktop = {buildId, port, appName} via JS.

Runtime-config injection is best-effort by nature -- pywebview's evaluate_js timing depends on the page being loaded. Retries ONCE on failure. Returns True if a call succeeded, False otherwise.

#_wire_runtime_config_injection

python
def _wire_runtime_config_injection(window: object, build_id: str | None, port: int, app_name: str) -> None

Wire runtime-config injection to the window's loaded event if present.

Injecting after page load is the reliable moment; when pywebview exposes no loaded event the injection is attempted immediately (best-effort).

#_raise_window

python
def _raise_window(window: object) -> None

Best-effort raise-to-front of window.

Calls restore() (un-minimize) then show(). Raising a window ABOVE other applications' windows is window-manager dependent and not guaranteed on every platform -- this is the documented limitation of the platform- neutral, file-based focus signal.

#_request_focus_existing

python
def _request_focus_existing(pid_path: Path, existing_pid: int) -> None

Drop a focus-request marker for the window-owning process and return.

Platform-neutral: the joining process writes a marker into the instance- registry dir and exits; the owning process's focus poll consumes it and raises its window.

#_install_focus_request_poll

python
def _install_focus_request_poll(window: object, pid_path: Path, stop_event: threading.Event, *, interval: float=1.0) -> threading.Thread

Poll for focus-request markers while the window is open; raise on request.

Runs a lightweight daemon thread that, every interval seconds until stop_event is set, consumes any focus-request markers (deleting them, even those owned by other/dead PIDs) and raises this window.

#AppInstances

A snapshot of an app's live server + windows from the registry.

#list_app_instances

python
def list_app_instances(pid_path: Path) -> AppInstances

Enumerate the app's live server instance(s) and open window markers.

Reads the fastware instance registry for pid_path: servers are the registered server descriptors (RegistryEntry); windows are the live per-window marker payloads (dicts with pid and window_id). Stale entries are pruned by the underlying registry reads.

#_require_webview_gui

python
def _require_webview_gui() -> object

Import pywebview and verify a GUI backend, returning the webview module.

Raises RuntimeError with an actionable message if pywebview is not installed or no GUI backend is available. Only called on paths that actually open a native window (the focus-existing early exit needs neither).

#_run_window

python
def _run_window(webview: object, window: object, url: str, pid_path: Path, port: int, app_name: str, icon: str | None) -> None

Manage a single native window's full lifecycle around webview.start().

Writes a per-window marker (cross-process refcount), runs the startup handshake, wires the runtime bridge + runtime-config injection + focus- request poll, blocks in webview.start(), then removes the marker and stops the server only when zero live window markers remain.

#ensure_gui_backend

python
def ensure_gui_backend() -> bool

Report whether a native pywebview GUI backend is available, truthfully per platform.

On Linux, this additionally makes the system PyGObject importable in isolated venvs: if gi is not importable, common system site-packages locations are searched and the first one found is added to sys.path.

#_has_gui_backend

python
def _has_gui_backend() -> bool

Probe whether pywebview can load a GUI backend.

Non-Linux platforms delegate to ensure_gui_backend(), which reports availability truthfully per platform. On Linux, honours the PYWEBVIEW_GUI env var and probes GTK first (via ensure_gui_backend, which also makes system PyGObject importable in isolated venvs), then Qt.

#_default_pid_path

python
def _default_pid_path(name: str) -> Path

Stable per-app PID file path under the platform runtime/state dir.

A CWD-relative default would defeat single-instance detection when the app is launched from different directories.

#_launch_command_parts

python
def _launch_command_parts() -> list[str]

Reconstruct a runnable command line (as argv parts) for this process.

Handles the python -m pkg case, where sys.argv[0] is the package's __main__.py (a module file, not an executable): rebuilds sys.executable -m pkg instead.

#_auto_register_entry

python
def _auto_register_entry(title: str, icon: str | None) -> None

Create a desktop entry for this app if one doesn't exist.

On Linux/macOS a launcher script is created in ~/.local/bin and the entry points at it; this also self-heals: if an existing entry points to a missing launcher (e.g. the package was reinstalled to a different venv), the broken entry is removed and recreated with the current launcher path. On Windows the Start Menu shortcut points directly at the target -- a POSIX shell script cannot execute there.

#run

python
def run(target: str | Callable, *, title: str='wesktop', width: int=1280, height: int=800, icon: str | None=None, host: str | None=None, port: int | None=None, pid_path: Path | None=None, name: str='WESKTOP', pre_serve: Callable[[], None] | None=None, reload: bool=False, js_api: object | None=None, single_instance: bool=True, second_open: str='new-window') -> None

Start server + open native desktop window. Blocks until window closes.

The server runs as a detached subprocess (see serve_background), so pre_serve and reload cannot work here and are hard errors: pre_serve would run in this process while the server re-imports the target in another, and a file watcher cannot restart the detached server. Use :func:wesktop.serve for both.

second_open selects what happens on a second launch while an instance is already running (single-instance join). It must be chosen explicitly from:

  • "new-window" (default): open an additional native window joined to the

existing server. Windows are refcounted across processes via marker files; the server stops only when the last window (in any process) closes.

  • "focus-existing": do NOT open a new window. Drop a platform-neutral

focus-request marker and exit; the process that owns the window raises it via a ~1s file-based poll. Raising above other apps is WM-dependent.

#wesktop.run(target, *, title, width, height, icon, host, port, pid_path, name, pre_serve, reload, js_api, single_instance)

Start a granian server in a background thread and open a native desktop window via pywebview. Blocks until the user closes the window. This is the primary entry point for desktop applications.

The target parameter is an ASGI import path (e.g., "myapp:app") or a callable. pywebview is late-imported so headless environments that only use serve() never load the GUI dependency. In desktop mode the server binds to a random available port by default (port 0), so multiple instances do not collide.

When single_instance=True (the default) and a pid_path is provided, run() checks for an already-running server. If found, it opens a new window pointing at the existing server instead of starting a second one.

wesktop.run(target, *, title, width, height, icon, host, port, pid_path, name, pre_serve, reload, js_api, single_instance)
ParameterTypeDefaultDescription
targetstr | CallablerequiredASGI module path or callable
titlestr"wesktop"Window title
widthint1280Window width in pixels
heightint800Window height in pixels
iconstr | NoneNonePath to window icon
hoststr | NoneNoneBind address (default: 127.0.0.1)
portint | NoneNoneBind port (default: random)
pid_pathPath | NoneNonePID file for lifecycle management
namestr"WESKTOP"Server name for logging
pre_serveCallable | NoneNoneCallback invoked before starting the server
reloadboolFalseEnable auto-reload on code changes
js_apiobject | NoneNonePython object exposed to JavaScript via window.pywebview.api
single_instanceboolTrueJoin existing instance if one is running

#wesktop.ensure_gui_backend()

Make pywebview's GUI backend importable in isolated virtual environments. If gi (PyGObject) is not importable, searches common system site-packages locations (Linux, macOS Homebrew, macOS Framework) and adds the first match to sys.path. Returns True if a backend is available, False otherwise. Called automatically by run().

#Desktop Entries

Cross-platform desktop shortcut creation and removal for all 3 major operating systems. On Linux, creates freedesktop-compliant .desktop files in ~/.local/share/applications/ with optional icon installation to ~/.local/share/icons/. On macOS, generates .app bundles in ~/Applications/ with Info.plist and launcher scripts. On Windows, creates Start Menu shortcuts via COM automation with a PowerShell fallback.

#src.wesktop.entries

Cross-platform desktop entry creation and removal for Linux .desktop files, macOS .app bundles, and Windows Start Menu shortcuts.

#create_entry

python
def create_entry(name: str, command: str, *, icon: str | Path | None=None, comment: str='', categories: str='Utility;') -> Path

Create a platform-native desktop entry. Returns the path of the created entry.

command is a full, already-quoted command line. On Linux/macOS, quote arguments with shlex.quote. On Windows, double-quote any path or argument containing spaces (see :func:quote_windows_command).

#remove_entry

python
def remove_entry(name: str) -> bool

Remove a desktop entry (and its launcher script, if any).

Returns True if something was removed.

#entry_exists

python
def entry_exists(name: str) -> bool

Check whether a desktop entry already exists for name on this platform.

#launcher_name

python
def launcher_name(name: str) -> str

Derive the launcher script name for an app: slugged name + '-open'.

#launcher_path

python
def launcher_path(name: str) -> Path

Path of the launcher script for name (POSIX platforms).

#create_launcher

python
def create_launcher(name: str, command: str) -> Path

Create an executable launcher script for name that execs command.

command must be a fully shell-quoted POSIX command line. Only supported on Linux and macOS -- a POSIX shell script cannot execute on Windows, so Windows shortcuts must point directly at their target instead.

#remove_launcher

python
def remove_launcher(name: str) -> bool

Remove the launcher script for name. Returns True if it existed.

#_split_windows_command

python
def _split_windows_command(command: str) -> tuple[str, str]

Split a Windows command line into (target, arguments).

Quoting contract: a target path containing spaces MUST be double-quoted, e.g. '"C:\Program Files\app.exe" --arg'. Unquoted commands split at the first whitespace. An unquoted absolute-path target whose first token has no file extension is almost certainly a spaces-in-path target truncated at the first space -- that is a hard error instead of silently producing a shortcut to a nonexistent target.

#quote_windows_command

python
def quote_windows_command(parts: Sequence[str]) -> str

Join command parts into a Windows command line.

Follows the quoting contract of :func:_split_windows_command: any part containing whitespace is double-quoted.

#_windows_com_available

python
def _windows_com_available() -> bool

Whether the pywin32 COM backend is importable.

#wesktop.create_entry(name, command, *, icon, comment, categories)

Create a platform-native desktop entry so users can launch a wesktop application from their OS application launcher. On Linux, this writes a freedesktop-compliant .desktop file with optional icon installation; on macOS, it creates an .app bundle with an Info.plist and launcher shell script; on Windows, it creates a Start Menu shortcut using COM automation with a PowerShell fallback:

wesktop.create_entry(name, command, *, icon, comment, categories)
PlatformWhat it creates
Linux.desktop file in ~/.local/share/applications/ with optional icon copy to ~/.local/share/icons/
macOS.app bundle in ~/Applications/ with Info.plist and launcher script
WindowsStart Menu shortcut via COM (win32com) or PowerShell fallback

Returns the Path of the created entry.

wesktop.create_entry(name, command, *, icon, comment, categories)
ParameterTypeDefaultDescription
namestrrequiredApplication name
commandstrrequiredShell command to execute
iconstr | Path | NoneNonePath to icon file or theme icon name
commentstr""Application description
categoriesstr"Utility;"Desktop entry categories (Linux only)

#wesktop.remove_entry(name)

Remove a previously created desktop entry by its registered name. Searches the platform-specific location (Linux ~/.local/share/applications/, macOS ~/Applications/, Windows Start Menu folder) and deletes both the entry and any installed icon. Returns True if the entry was found and removed, False if no entry with that name existed.

#Development Mode

#wesktop.dev(target, *, vite_command, vite_port, host, port, pid_path, name, pre_serve)

Development mode with Vite frontend hot-reload. Starts a Vite dev server as a subprocess alongside the granian ASGI backend, proxying unmatched frontend requests through ViteDevProxy middleware. Polls the Vite port for readiness (up to 15 seconds) and terminates the Vite process automatically when the server shuts down.

wesktop.dev(target, *, vite_command, vite_port, host, port, pid_path, name, pre_serve)
ParameterTypeDefaultDescription
targetstr | CallablerequiredASGI module path or callable
vite_commandstr"npm run dev"Command to start Vite
vite_portint5173Port Vite listens on
hoststr | NoneNoneBackend bind address
portint | NoneNoneBackend bind port
pid_pathPath | NoneNonePID file path
namestr"WESKTOP"Server name for logging
pre_serveCallable | NoneNoneCallback invoked before starting the server

#SDUI Primitives

The SDUI system provides 39 Pydantic-validated node types organized into 6 categories (layout, display, data, input, feedback, overlay) for building dynamic dashboards entirely from the server without shipping custom frontend code.

#SDUINode

Base class for all SDUI nodes.

Every node serialises to {"type": ..., "props": ..., "children": [...]} with an optional "if" key for conditional rendering.

wesktop includes 39 server-driven UI node types for building dynamic dashboards without shipping frontend code. Each model serializes to the {"type", "props", "children"} dict shape expected by the SDUI renderer.

For the full list of SDUI primitives (layout, display, data, input, feedback, overlay), see the auto-generated SDUI reference.

#Grouping

Grouping
CategoryCountNodes
Layout9Stack, ZStack, Spacer, Divider, Grid, Card, Tabs, Breadcrumb, Empty
Display10Heading, Text, Code, Status, Badge, ProgressBar, Spinner, Timeline, Diff, Markdown
Data6Table, DataGrid, List, KeyValue, JsonView, Tree
Input8Button, Input, TextArea, Select, Checkbox, Switch, Radio, Slider
Feedback3Alert, Toast, Logs
Overlay4Modal, Drawer, Popover, Confirm

#Quick example

python
from wesktop.sdui import Stack, Button, Heading, node

# Using model classes
layout = Stack(children=[
    Heading(text="Dashboard", level=1).to_node(),
    Button(label="Deploy", variant="primary", command="deploy").to_node(),
])

# Using the node() helper
tree = node("stack", [node("heading", text="Hello", level=2)])

#Fastware Re-exports

The following 15 modules are re-exported from fastware, providing the full ASGI framework stack (routing, responses, middleware, auth, DI, testing, server lifecycle, and more) without requiring a separate import fastware statement. See the fastware API docs for full documentation.

Fastware Re-exports
wesktop modulefastware sourceProvides
wesktop.asgifastware.routing, fastware.request, fastware.responses, fastware.app, fastware.types, fastware.websocketRouter, Request, response types, create_app, WebSocket
wesktop.ssefastware.sseBroadcaster, sse_route
wesktop.serverfastware.serverserve, serve_background, stop, status, ServerStatus
wesktop.middlewarefastware.middlewareCORSMiddleware, RequestIDMiddleware, RequestTimingMiddleware, TrustedHostMiddleware, ViteDevProxy
wesktop.authfastware.authcreate_token, verify_token, hash_password, verify_password, JSONFileUserStore, CSRFMiddleware, rate_limit
wesktop.difastware.diDependencyResolver
wesktop.configfastware.configload_config
wesktop.testingfastware.testingAsyncTestClient, TestClient
wesktop.featuresfastware.featuresFeatureFlags
wesktop.auditfastware.auditAuditLog
wesktop.tasksfastware.tasksBackgroundTask, TaskRegistry
wesktop.error_logfastware.error_logErrorLog
wesktop.loggingfastware.loggingconfigure_logging, get_logger, init_sentry
wesktop.mcpfastware.mcpcreate_mcp_server, register_tools_for_role
wesktop.devfastware.devdev mode internals

#Metadata

#__version__

Package version string, read from importlib.metadata at import time. Follows semantic versioning (currently 0.x.x, pre-stable). Available via import wesktop; wesktop.__version__ in Python code and wesktop --version from the command line. The version is set in pyproject.toml and bumped automatically by rlsbl during releases.