Skip to main content

tkipw

License: MIT PyPI - Python Version GitHub Release PyPI Version Downloads Status: Alpha CI

Run ipywidgets / anywidget on the desktop — no Jupyter Notebook, no browser tab.

tkipw is a small runtime that hosts ipywidgets and anywidget inside a real system WebView embedded in a Tkinter window, powered by tkwry.

Python → ipywidgets API → Comm (tkipw) → tkwry IPC → JS Widget Manager → DOM

Alpha — APIs and behavior may change. Not recommended for production yet.


📖 Overview

Jupyter widgets normally need a notebook kernel and a browser. tkipw drops both: your widgets run in the same process as your Python code and render in a native WebView that lives inside a Tk Frame (via tkwry's child-window embedding).

  • No notebook — plain python your_script.py
  • Real widgets — the official @jupyter-widgets/html-manager runs the same controls you use in Jupyter
  • anywidget — bundled front end (e.g. Plotly FigureWidget)
  • ipyleaflet — bundled live Leaflet widget module (Python ↔ map trait updates)
  • ipycanvas — bundled Canvas widget module (Python drawing commands ↔ WebView)
  • bqplot — bundled interactive plotting widgets (Python ↔ SVG figure updates)
  • ipympl — bundled interactive Matplotlib canvas (opt in with import ipympl)
  • Notebook-like displaydisplay(), clear_output(), Output, plt.show(), tracebacks, and logging all show up in an output area
  • One event loop — everything runs on Tk's mainloop

🔧 Requirements

  • Python 3.10+
  • tkwry 0.1.4+ (system WebView: WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux — see tkwry's platform notes)
  • ipywidgets 8.x

The widget core (runtime.js) is hosted over loopback; leaflet / ipycanvas / bqplot / ipympl packs are fetched the first time a matching widget loads. The Playground's Monaco editor and standalone Altair / Bokeh documents load their JavaScript libraries from a CDN.

Bundled front-end dependencies

The widget front end is prebuilt from js/ with esbuild into src/tkipw/html/runtime.{js,css} plus lazy pack-*.{js,css} files and shipped inside the wheel (built in CI, not committed to the repo). It embeds:

  • Jupyter Widgets (@jupyter-widgets/*) and Lumino — BSD-3-Clause
  • anywidget, jupyter-leaflet, jQuery, Backbone.js — MIT
  • ipycanvas (and Rough.js) — BSD-3-Clause / MIT
  • bqplot / bqscales (and D3) — Apache-2.0 / ISC
  • ipympl / jupyter-matplotlib — BSD-3-Clause
  • Leaflet and its map plugins — BSD / MIT / ISC / Beerware
  • Font Awesome Free icon styles (pulled in by Jupyter Widgets; font binaries are stripped at build time) — MIT / CC BY 4.0 / SIL OFL 1.1

All are permissively licensed and redistributable; attributions are collected in NOTICE. Python runtime dependencies (tkwry, ipywidgets, comm, traitlets, markdown) are installed by pip as normal and are not vendored.


📦 Installation

From PyPI (prebuilt front end included — no npm / Node required):

pip install tkipw
pip install "tkipw[demo]"   # plotting, data, image, and 3D demos

From a source checkout (editable):

pip install -e .
pip install -e ".[demo]"

Rebuild the front end only when you change js/ (or after a fresh clone before the first editable run, if runtime.js is not present yet):

cd js && npm install && npm run build

🚀 Usage

from tkipw import App, display
import matplotlib.pyplot as plt

app = App()
plt.plot([1, 2, 3], [1, 4, 9])
plt.show()          # routed into the output area (viewer mode — default)
app.run()

Pop-up windows (%matplotlib tk style for figures, and for any display()):

from tkipw import App, display

app = App(title="host", display_mode="window")
display(some_chart)   # opens a Tk pop-up (host root stays hidden)
app.run()

Interactive widgets work as usual:

from tkipw import App
import ipywidgets as widgets

app = App()
slider = widgets.IntSlider(description="n", value=10)
app.display(slider)
app.run()

Embed in an existing Tk layout with WidgetFrame (a tk.Frame that you pack yourself):

import tkinter as tk
import ipywidgets as widgets
from tkipw import WidgetFrame

root = tk.Tk()
view = WidgetFrame(root)
view.pack(fill="both", expand=True)
view.display(widgets.IntSlider())
root.mainloop()

import ipywidgets / import anywidget work unchanged.

  • app.display(...) / view.display(...) — mount widgets in this host's WebView
  • display / clear_output / Output — notebook-style output under the cell
  • display(..., display_id=True) — returns a handle; handle.update(...) replaces that output
  • register_mime_renderer(mime, fn) — extra _repr_mimebundle_ types → HTML
  • register_widget_module(name, path) — load a classic AMD/nbextension JS module from a local file or directory (not bundled, not CDN). App() also discovers modules already installed under Jupyter nbextensions
  • App(display_mode="viewer"|"window") — output pane vs one Tk pop-up per display() (window mode hides the host root so only the pop-ups are visible). "inline" is a deprecated alias for "viewer"
  • plt.show() — follows the active App (PNG in the viewer pane, or native TkAgg windows)

Import order: from tkipw import App before you create widgets, so they bind to tkipw's Comm backend instead of a DummyComm.

🔄 Multiple Apps & cleanup

Several Apps can be alive at once. The most recently used one (the one you last called display() / activate() on) receives newly created widget comms. destroy() cleans up that App, and when the last App closes, tkipw restores the process-wide patches it installed (Comm backend registry, IPython display bridge, logging handler, sys.excepthook).

a = App(title="A")
b = App(title="B")

a.display(widgets.Button(description="in A"))   # activates A → renders in A
b.display(widgets.Button(description="in B"))   # activates B → renders in B

with a.activate():
    widgets.IntSlider()   # new comms go to A
# B is active again

a.destroy()
b.destroy()   # last one out tears down global patches

The monkey-patches are also individually reversible: uninstall_comm_backend(), uninstall_jupyter_support().


📁 Examples

pip install "tkipw[demo]"        # or: pip install -e ".[demo]"
python examples/playground.py    # viewer: Monaco editor + stacked output
python examples/plotly_demo.py   # window: Plotly FigureWidget pop-up
python examples/ipyleaflet_demo.py # window: live ipyleaflet map pop-up
python examples/ipycanvas_demo.py  # window: live ipycanvas Canvas pop-up
python examples/bqplot_demo.py     # window: live bqplot Figure pop-up
python examples/ipympl_demo.py     # window: interactive Matplotlib (ipympl)
python examples/bokeh_demo.py    # window: Bokeh ``show(plot)`` pop-up
python examples/altair_demo.py   # window: Altair ``display(chart)`` pop-up
python examples/pillow_demo.py   # window: Pillow ``Image.show()`` pop-up
Script Mode Description
examples/playground.py viewer Monaco multi-tab editor + stacked live output
examples/plotly_demo.py window Plotly FigureWidget in a Tk pop-up
examples/ipyleaflet_demo.py window Live ipyleaflet widget map in a Tk pop-up
examples/ipycanvas_demo.py window Live ipycanvas Canvas in a Tk pop-up
examples/bqplot_demo.py window Live bqplot Figure in a Tk pop-up
examples/ipympl_demo.py window Interactive Matplotlib (ipympl) in a Tk pop-up
examples/bokeh_demo.py window Bokeh show(plot) in a Tk pop-up
examples/altair_demo.py window Altair display(chart) in a Tk pop-up
examples/pillow_demo.py window Pillow Image.show() in a Tk pop-up

🖥️ Playground

A viewer-mode IDE-like playground with a Monaco multi-tab editor on the left and stacked notebook-style output on the right:

python examples/playground.py

Samples (README.md / matplotlib / ipympl / pyvista / pandas / Folium / ipyleaflet / ipycanvas / bqplot / …) open as tabs. Running a .md or .markdown tab renders the file directly in the themed output pane; Python code can render the same content with IPython.display.Markdown. Run the active tab with the Run button or ⌘/Ctrl+Enter. While Python is running, the green play button becomes a red stop button; stopping cooperative Python execution reports the interruption in the output pane. The menu bar has New/Open/Save, Undo/Redo, Find/Replace, Minimap, Word Wrap, editor theme, and a View → Display Mode → Viewer / Window selector. Viewer results are stacked in the toggleable output pane; Window mode opens each display() in a separate Tk pop-up. Monaco loads from a CDN on first run.


🧩 Jupyter extensions

IPython.display.display(), tkipw.display() and App.display() all go through one transform gateway, so library-specific display fixes live in extensions:

from tkipw import register_extension

class MyExtension:
    name = "my-library"

    def setup(self):
        ...                 # initialise as a notebook front end

    def transform(self, obj):
        return obj          # adapt for the WebView if needed

register_extension(MyExtension())

Extra _repr_mimebundle_ keys (not a whole library) go through register_mime_renderer(mime, fn) instead. Classic AMD widget JS (for example ipydatagrid) is picked up from Jupyter nbextensions when you create an App, or loaded with register_widget_module(name, path) — not from a CDN.

Built-ins:

  • Matplotlib — follows the active App's display_mode by default: viewer → PNG in the output area; window → native TkAgg figure windows (%matplotlib tk style). import matplotlib alone keeps that path. import ipympl switches to interactive WebView canvases (%matplotlib widget); App display_mode still chooses the viewer pane vs pop-up. The Playground resets the backend from each tab's source so a matplotlib-only tab does not stay stuck on ipympl after an earlier run. Shortcuts: matplotlib_inline() / matplotlib_window() / matplotlib_widget().
  • Folium — pixel Map(width=…, height=…) becomes a fixed-size hosted map (preferred in window mode). Percentage sizes keep the notebook HTML.
  • ipyleaflet — bundled jupyter-leaflet module renders live widget maps; map/layer trait changes continue to flow over the tkipw Comm bridge.
  • ipycanvas — bundled ipycanvas module renders live Canvas widgets; drawing commands and pointer events flow over the Comm bridge.
  • bqplot — bundled bqplot + bqscales modules render interactive SVG figures; marks/scales/axes sync over the Comm bridge. Toolbar Save (data: / blob:) opens a native file dialog; HTTP(S) files use tkwry on_download.
  • ipympl — bundled jupyter-matplotlib module for interactive Matplotlib zoom/pan toolbars in the WebView (activated by import ipympl).
  • PillowImage.show() → PNG via display() (viewer pane or pop-up)
  • Altair — standalone Vega-Lite HTML hosted in a responsive iframe
  • Bokehshow() / displayed models → standalone HTML hosted in an iframe
  • PyVistahandle_plotter → show_trame → IPython.display. On macOS the trame / server backends are remapped to client, because native VTK OpenGL + WKWebView crash (SIGTRAP). Large offline-html srcdoc iframes are served over a loopback LocalHTMLHost for WebView compatibility.

🏗️ Architecture

  • Pythoncomm.create_commTkwryComm; official ipywidgets messages sent as JSON (+base64 buffers)
  • JS@jupyter-widgets/html-manager + window.ipc in runtime.js; leaflet, ipycanvas, bqplot/bqscales, and jupyter-matplotlib load as packs on first use
  • Bridge — a stack of active Apps; the top receives new comm traffic
  • Navigation — the widget shell stays on loopback; other loopback ports (PyVista trame) stay in the WebView; public http(s) links open in the system browser (tkwry on_navigation / open_external)

🧪 Tests

python -m tkipw doctor     # packages, WebView engine, bundled JS runtime
pytest -m "not e2e"        # fast, display-free unit tests
TKIPW_E2E=1 pytest -m e2e  # real WebView: boot, comm, and extension DOM regression

CI runs the unit tests on Windows / macOS / Linux, plus the WebView E2E suite on Linux (Xvfb) and macOS (runtime/comm and extension display paths, split to avoid WebKitGTK hangs). See .github/workflows/ci.yml.


⚠️ Known limitations

  • Alpha — APIs may change
  • Widget coverage — standard ipywidgets controls + anywidget + ipyleaflet
    • ipycanvas + bqplot + ipympl are bundled. Other classic AMD widgets are discovered from Jupyter nbextensions (or register_widget_module). JupyterLab Module Federation is out of scope.
  • PyVista on macOS — client-side rendering only (see extensions above)
  • External links — http(s) outside loopback open in the system browser, not inside the widget WebView
  • Platform behavior — inherits tkwry's platform notes (macOS embedding, import order, Linux source build)

📝 License

MIT. The bundled JavaScript embeds third-party libraries (Jupyter Widgets and Lumino under BSD-3-Clause; anywidget, jQuery, and Backbone under MIT; Font Awesome Free icon styles under MIT / CC BY 4.0 / OFL 1.1) — all permissive and redistributable. See NOTICE for full attributions.

Built on tkwry.

See CHANGELOG.md for release history.


👨‍💻 Author

mashu3

Contributors

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tkipw-0.0.4.tar.gz (1.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tkipw-0.0.4-py3-none-any.whl (1.4 MB view details)

Uploaded Python 3

File details

Details for the file tkipw-0.0.4.tar.gz.

File metadata

  • Download URL: tkipw-0.0.4.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tkipw-0.0.4.tar.gz
Algorithm Hash digest
SHA256 54c607727f2d8ebfcabce81cf67945d0db61d7c859c722ba6678aca3955d6733
MD5 5208b718b90526f4577acaf1f3e4a256
BLAKE2b-256 c4faa0018a16502a4d411ac91af2efb9f1c1cc588a81a9013f0f289a5a515768

See more details on using hashes here.

Provenance

The following attestation bundles were made for tkipw-0.0.4.tar.gz:

Publisher: release.yml on mashu3/tkipw

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tkipw-0.0.4-py3-none-any.whl.

File metadata

  • Download URL: tkipw-0.0.4-py3-none-any.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tkipw-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 d01c72b84ae771c1687cdc321731dfdef614c34c309297c5547db45ee2cfd484
MD5 85ad46bb917fa859684a043e3c4daf07
BLAKE2b-256 7292177f966b9ff95b76eec0c63ca6fb3057553301fbfcf2bbec03bf32fe5c1a

See more details on using hashes here.

Provenance

The following attestation bundles were made for tkipw-0.0.4-py3-none-any.whl:

Publisher: release.yml on mashu3/tkipw

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.0.4 This release

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page