Skip to main content

guile

A lightweight Python framework for building desktop apps.


Philosophy

Guile started as a personal tool for building lab and research apps — the kind of quick internal dashboards, data explorers, and parameter tools that are too specific to justify a full web stack, but too interactive for a script. It does two things and tries to do them well. First, it layers interactivity on top of the Python you already have: your functions stay ordinary Python, and guile just wires them to widgets. Second, it follows a single reactive rule — change a state value and the interface updates, patching only the parts that actually changed, so text stays in inputs and focus is never lost. The app is one Python process that opens a native window: no server, no ports, no browser tab.


Install

pip install guile

Requires pywebview. On Windows, WebView2 ships with Windows 10/11 — nothing extra to install.


Quick start

import guile as gui

count = gui.state(0)

@gui.app("Counter", width=400, height=300)
def ui():
    with gui.col(align="center", justify="center", style="height:100vh"):
        with gui.card(gap=14):
            gui.title("Counter")
            with gui.row(gap=16, align="center", justify="center"):
                gui.button("−", variant="secondary",
                           on_click=lambda: count.update(lambda x: x - 1))
                gui.text(count.value, size="2xl", bold=True,
                         style="min-width:64px;text-align:center")
                gui.button("+",
                           on_click=lambda: count.update(lambda x: x + 1))

gui.run()

@gui.app() defines the app; gui.run() opens the window and blocks until it is closed. Code written after gui.run() executes once the window closes — a natural place to save the session or continue a processing pipeline (see examples/field_notes.py).

While building a UI, use gui.run(dev=True): guile watches your script and reloads the app inside the open window every time you save the file. Errors show in the window without killing the session; each reload resets state to its initial values.


How it works

  • gui.state(value) — a reactive value; setting it re-renders the UI automatically. Read it through .value, always
  • with gui.card(): / with gui.col(): / with gui.row(): — layout containers; everything indented goes inside
  • gui.button(), gui.slider(), gui.input(), gui.table() — widgets that take on_click= or return their current value
  • gui.figure(fig) — embed a matplotlib figure inline
  • gui.leaflet(center, markers=..., layers=...) — embed an interactive map; drape a georeferenced image (gui.ImageOverlay), a pre-tiled drone mosaic (gui.TileOverlay), or vector features (gui.GeoJSON) over it
  • gui.task(fn, on_done=...) — run slow work on a background thread; the window stays responsive
  • gui.run() — opens the window; gui.run(dev=True) adds hot reload while you build
  • gui.package("my_app.py") — build a shareable executable in one call

Examples

File What it shows
counter.py State, buttons, badges
todo.py Lists, dynamic rendering, checkboxes
settings.py Sliders, selects, form layout
field_notes.py Save on exit — code after gui.run()
mesonet_map.py Leaflet map with markers
mesonet_interactive.py Live mesonet station data
weather_explorer.py Table, date picker, file picker
soils_lab.py Lab data entry form
soil_water_retention.py Sliders driving a live chart
upload_weather_data.py File picker, DataFrame, table
canopeo.py Image analysis
map_draw.py Leaflet with draw tools
map_overlays.py Image overlay + GeoJSON on a map

Using AI assistants with guile

If you build guile apps with an AI tool (Claude Code, Copilot, ChatGPT, …), point it at the machine-readable docs instead of the HTML pages:

  • llms-full.txt — the complete API, the golden rules, and verified examples in one self-contained file. Everything an assistant needs to write correct guile apps.
  • llms.txt — the short index, following the llms.txt convention.

Tools working inside a cloned repo can also read the guile source directly — every public function carries a full docstring.


Dependencies

Package Purpose
pywebview The window (installed with guile)
matplotlib gui.figure() (installed with guile)
numpy, pandas Optional — pip install guile[science]

Everything else is Python standard library.


Files

File Role
state.py Reactive value class
ui.py Render engine + all widgets
_app.py Window lifecycle, pywebview bridge
_template.py Embedded HTML/CSS/JS
_package.py gui.package() — PyInstaller wrapper
_dev.py gui.run(dev=True) — hot reload
__init__.py Public API (gui.*)

Changelog

v0.8.2

  • Map fix: a keyed map no longer goes blank when an unkeyed ancestor is replaced (e.g. a sidebar element appearing or disappearing shifts auto-numbered ids). The registry now detects the orphaned Leaflet instance, disposes it, and rebuilds the map in place, keeping the user's current pan/zoom.

v0.8.1

  • Map overlay layers: gui.leaflet(layers=[...]) accepts gui.ImageOverlay(png, bounds=...) to drape a georeferenced PNG/JPG, gui.TileOverlay(url) for a pre-tiled pyramid (the practical route for large drone mosaics — tile with gdal2tiles, serve with python -m http.server), and gui.GeoJSON(data, popup=, on_click=) for vector features with a per-feature click callback. See examples/map_overlays.py.

v0.8.0

  • gui.package() now defaults to package_mode="onedir" (a folder holding the executable and its libraries) instead of a single file. Onedir draws fewer antivirus/SmartScreen false positives and starts faster — zip it or wrap it in an installer to share. Pass package_mode="onefile" for the old single-executable behavior. This replaces the onefile= argument.

v0.7.0

  • @gui.app now only defines the app; add gui.run() at the end of your script to open the window. Code after gui.run() executes when the window closes — a natural place to save the session or continue a pipeline (see examples/field_notes.py).
  • Hot reload: gui.run(dev=True) watches your script and reloads the app inside the open window on every save. Errors show in the window without killing the session; each reload resets state to initial values.
  • State is now fully explicit: read and compare through .value (if count.value > 0:). Comparing or operating on the State object itself raises a clear TypeError — this fixes silent misbehavior with numpy arrays and DataFrames.
  • New gui.task(fn, on_done=, on_error=, busy=) — run slow work on a background thread; clicks and renders keep flowing, and state set inside the task drives live progress bars.
  • Errors raised in callbacks now show a danger toast in the window (previously invisible in packaged, windowed apps).
  • DOM patcher fix: programmatic updates to inputs, checkboxes, textareas, and selects now render correctly after the user has interacted with them; the caret no longer jumps in live=True inputs.
  • gui.number_input() keeps .value current per keystroke but re-renders on commit (Enter, focus leave, spinner); an empty or invalid commit keeps the current value.
  • Maps rendered conditionally (e.g. inside a tab) now load Leaflet lazily instead of staying blank.
  • Duplicate key= values print a one-time warning; gui.progress(value, max=0) no longer raises.
  • New example field_notes.py (load → run → save-on-exit); test suite expanded to 14 tests.

v0.6.0 — Improved map tile presets. Added gui.package() for one-call PyInstaller builds.

v0.5.0 — Added center=True to app window. Improved code structure in how-to page.

v0.4.0 — Added tabs. Fixed datetime-local input to display in 24-hour format.

v0.3.0 — Added notify and modal widgets.

v0.2.0 — Added max_height to gui.scroll(). Fixed multiselect change event.

v0.1.0 — First release. 27 widgets.


MIT License

Metadata

Release files for guile 0.8.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for guile 0.8.2
File Size Uploaded Provenance Actions
guile-0.8.2.tar.gz 61.6 kB Without provenance Details for guile-0.8.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for guile 0.8.2
File Interpreter ABI Platform Actions
guile-0.8.2-py3-none-any.whl Python 3 none any Details for guile-0.8.2-py3-none-any.whl

Total release size: 122.6 kB

Release files / guile-0.8.2.tar.gz

Download URL guile-0.8.2.tar.gz
Size 61.6 kB
Tags Source
SHA-256 checksum
How to use checksums
12fab2f2b4b517e854ff79a5ec0e6db68a77aa8f3474e97f083f902538faaace
BLAKE2b-256 checksum
How to use checksums
9388df56c0002dcb9732233c4455f198ac770f68552849b06fd2f05a48f31107
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / guile-0.8.2-py3-none-any.whl

Download URL guile-0.8.2-py3-none-any.whl
Size 61.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2a3fd9f1a0ab95d6e83b087eacd6811c3c0888466e9df57ae8232225ee13f4fa
BLAKE2b-256 checksum
How to use checksums
a940f9601c12af3894e44082d8211a1b99d6585904a860203b38544b892fe32e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Security for guile 0.8.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page