PUI is a reactive/declarative UI framework with two-way data binding. PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declarative flavor with virtual DOM and aims to maintain interoperability.
PUI requires Python 3.9 or newer. Backend packages have their own Python and platform requirements; the versions available for your interpreter may differ. The shared framework and PySide6 backend retain Python 3.9 support. Shared changes must preserve that baseline even when an optional backend requires newer Python.
Choose a backend extra:
pip install 'QPUIQ[pyside6]'
pip install 'QPUIQ[wx]'
pip install 'QPUIQ[flet]'
pip install 'QPUIQ[textual]'
pip install 'QPUIQ[web]'The web extra installs aiohttp, msgpack and zstandard with their dependencies.
Browser JavaScript, CSS and zstd WASM are bundled in the package; no separate npm
install is needed to run the backend.
Install the development/cookbook dependencies with:
pip install 'QPUIQ[cookbook]' # Pygments, Matplotlib and Shapely
pip install 'QPUIQ[web,cookbook]' # Web backend plus cookbook dependenciesThe cookbook extra can be combined with any backend extra. Pip selects dependency
versions compatible with the current Python interpreter, including Python 3.9.
The flet extra installs the current Flet 1.x desktop/web runtime (minimum tested version: 1.0.4, requiring Python 3.10 or newer). Table support uses its built-in DataTable, including Dropdown choices and dialog editing; no table extension is required.
For tkinter, install QPUIQ and use a Python build with Tk support:
pip install QPUIQ
python -c "import tkinter"The optional QPUIQ[tkinter] spelling installs the same packages; its extra is
empty because Tk is supplied by Python or the operating system.
Some Linux distributions provide Tk support separately as
python3-tk; an existing virtual environment uses its base Python's native
support. wxPython can also require platform-native GTK/build dependencies.
For the framework alone, install QPUIQ. Optional development tools:
pip install 'QPUIQ[reload]' # Hot reload via jurigged
pip install 'QPUIQ[landlock]' # Optional Linux filesystem allowlists; see doc/landlock.md
pip install textual-dev # Textual development toolsWhen working from this checkout, use the project environment:
python3 -m venv env
env/bin/python -m pip install -e '.[pyside6,flet,textual,cookbook]'
# Add .[wx] when wxPython is available for the platform.
# Add reload to enable hot reload, e.g. -e '.[web,cookbook,reload]'.Install an optional agent SDK alongside your backend (Python 3.10+):
pip install 'QPUIQ[pyside6,codex]' # or QPUIQ[web,claude]
python -m PUIThe launcher checks the selected provider's CLI login, asks for a backend and
Python class name, creates an empty application, prints its command and runs it.
Existing applications can use app.start(codex={}) / claude={} for CLI login,
or pass {"key": "API_KEY"}. Optional model and effort entries select the provider settings. Web gets a browser sidebar; PySide6, wx, tkinter and
Flet get an AI window supervising a restartable application child.
See AI development for permissions, State preservation and
make pyside6_codex / pyside6_claude and corresponding wx/tkinter/Flet/Web targets.
The shared and Qt regression suites pass on Python 3.9.24 with PySide6 6.7.2 on Linux. This covers state/bindings, lifecycle, signatures and legacy decorators, plus Qt windows, controls, tables, trees, scrolling, Tabs, and Views. It does not validate integration with an application's embedded interpreter or other platforms. PySide6 6.7.2 supports Python 3.9–3.12. To use this tested combination on Python 3.9:
python3.9 -m pip install 'QPUIQ[pyside6]' 'PySide6==6.7.2'To reproduce the compatibility check from a checkout, create a separate runtime
under env/ and install only the packages needed for this baseline:
python3.9 -m venv env/python39
env/python39/bin/python -m pip install -e . -r tests/requirements-python39.txt
make check-python39
# Or use an existing environment: make check-python39 PYTHON39=/path/to/python3.9The test runtime is pinned to PySide6==6.7.2 in
tests/requirements-python39.txt, and
make check-python39 rejects a different PySide6 version. The check runs the
shared and Qt regression suites; the Flet-specific signature test is skipped in
this environment.
The normal backend extra continues to allow newer versions. Run this check when changing
shared code or the Qt backend. Keep Python 3.10-only syntax and APIs, including
standard-library ParamSpec, out of code used by the Python 3.9 baseline.
Define applications by subclassing the backend's Application, and reusable
views by subclassing its PUIView. Put UI declarations in content(), initialize
local state in setup(), and use member functions as event handlers. Classes
are the recommended API; @PUI and @PUIApp are deprecated and retained for
compatibility. See the migration reference.
Every backend accepts the keyword-only Application(title="My app"), defaulting
to title=''. Web uses it as the page title; Textual sets its app title and sends
the terminal title when mounted. PySide6/wx set the native application display
name; PySide6, wx, Tkinter and Flet also use it for windows without an explicit
Window.title. See Application for title precedence
and terminal behavior.
# example/hello_world.py
import PUI.PySide6 as UI
class Example(UI.Application):
def content(self):
with UI.Window(title="test", size=(320,240)):
UI.Label("Hello world")
root = Example()
root.run()# example/generic_textfield.py
from PUI.PySide6 import *
data = State()
class Example(Application):
def __init__(self):
super().__init__()
data.var = 0
def content(self):
with Window(title="blah"):
with VBox():
with HBox():
Button("-").click(self.on_minus)
Label(f"{data.var}")
Button("+").click(self.on_plus)
TextField(data("var")) # binding
def on_minus(self, event):
data.var -= 1
def on_plus(self, event):
data.var += 1
root = Example()
root.run()# example/bleak_list.py
....
class DeviceView(PUIView):
def __init__(self, device, advertising_data):
super().__init__()
self.device = device
self.advertising_data = advertising_data
def content(self):
Label(f"{self.device.address} {self.device.name} {self.advertising_data.rssi}")
class GUI(Application):
def __init__(self, state):
super().__init__()
self.state = state
def content(self):
with Window(title="BLE List"):
with VBox():
Label(f"Found {len(self.state.scanned_devices)} devices")
for device, advertising_data in self.state.scanned_devices:
DeviceView(device, advertising_data)
....PUI handles normal widget sizing automatically. Each component supplies a
preferred size and expansion policy appropriate to the widget and its position
in an HBox or VBox; for example, a TextField uses the available horizontal
space while retaining its native height, and a Spacer consumes flexible space.
Application code normally does not need to assign control dimensions just to
make a layout fit.
Use .layout(width=..., height=..., weight=..., padding=..., margin=...) when a
design needs an explicit size, a particular share of flexible space, or custom
spacing. PUI translates these semantic rules to each backend's native layout
system, so exact pixel sizes can still vary with the toolkit, platform, font,
and theme.
Web resolves flexible space on the parent container's main axis: width in HBox,
height in VBox. Positive weights take priority over automatic expansion;
weight=0 preserves the widget's preferred size. Setting only Canvas width or
height leaves its other axis flexible. See the layout reference
for the priority rules and the Web progress for specification
differences still needing clarification.
# example/pyside6_feedparser.py
...
with VBox():
Label(title).qt(StyleSheet={"font-weight":"bold"}) # QT-specific
with HBox():
with Scroll():
with VBox():
for i,e in enumerate(entries):
Label(e.title).click(self.entry_selected, i)
Spacer()
with Scroll().layout(weight=1): # Generic Layout Parameter
if 0 <= selected and selected < len(entries):
(Text(entries[selected].description)
.layout(padding=10) # Generic Layout Parameter
.qt(StyleSheet={"background-color":"white", "color":"black"})) # QT-specific
...Standalone examples are also included in the package and can run from any directory:
python -m PUI.examples.hello_world
python -m PUI.examples.generic_list PySide6
python -m PUI.examples.generic_dict tkinterInstall the selected backend separately. The integration examples additionally use
bleak (bleak_list), feedparser (pyside6_feedparser), psutil
(generic_memtest), or sv-ttk (tkinter_sun_valley); install the relevant
package before running that example. These are not included in the cookbook extra.
The cookbook is included in the package. Install a backend and its example dependencies, then run it from any directory:
python -m pip install 'QPUIQ[pyside6,cookbook]'
python -m PUI.cookbook PySide6The cookbook extra includes Pygments for the code viewer, Matplotlib for the
MatplotlibCanvas example, and Shapely 2.x or newer for DrawShapely.
cookbook/requirements.txt remains an equivalent
dependency list. Choose a different backend extra as needed; for Tkinter,
use 'QPUIQ[cookbook]' with a Tk-enabled Python. Backend support still determines
which examples are available. Hot reload and Textual development tools are optional.
The code viewer uses a fixed dark Monokai palette. Backends without HTML support show plain text with the same dark background and light foreground.
See Canvas for drawing examples and HeavyCanvas for a large interactive chart.
Use make pyodide to run the cookbook with Python in the browser, loaded from a
fixed official CDN version. See the Pyodide reference
for the GitHub Pages entry, build and deployment instructions.
python -m PUI.cookbook textual
Use make textual to run the Textual cookbook. For development mode, install
textual-dev and use make textual_dev.
python -m PUI.cookbook tkinter
The Tkinter cookbook tries Sun Valley light when the external sv-ttk package is installed;
otherwise it uses classic. Theme loading lives in the cookbook's setup().
pip install 'QPUIQ[reload]'Then PUI will take care of view update (code)
The progress documents below track backend alignment, remaining work and checked runtime versions.
- PySide6 (reference implementation) — Python 3.9 compatibility
- wxPython — Progress
- Tkinter — Progress; external Sun Valley theme.
Install
sv-ttk, then runpython -m PUI.examples.tkinter_sun_valleyfor a dark theme example. - Flet — Progress
- Textual (terminal UI; no Canvas) — Progress
- Web (Python runs on the server) — Progress.
make webbuilds the browser bundle and serves the cookbook at http://localhost:8866/pui/. From a Git checkout, first install the browser build dependencies;make buildbuilds the bundle without starting the server. - Pyodide (Python runs in the browser) — Progress.
make pyodide-buildbuilds the static site indist/pyodide;make pyodidebuilds and serves the cookbook at http://localhost:8868/cookbook/cookbook.html.
- Dump with layout parameters and add test cases
- [ISSUE] flet layout sizing (cookbook scroll example)
- nested state trigger
- set state in PUIView init
- set state in setup() ?
- Tabs(
tabposition) - Lazy List
- UI Flow
- Navigation Stack
- View Router
- Model Window/Dialog
- Layout
- SwiftUI style overlay ??
- Canvas
- Arc
- ...
- State with Pydantic support?








