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.

Supported by

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