What is PUI
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.
CPPUI: Experimental C++ Version
Features
Installation
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]'
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 jurigged # Hot reload
pip install pygments # Cookbook syntax highlighting
pip install textual-dev # Textual development tools
When working from this checkout, use the project environment:
python3 -m venv env
env/bin/python -m pip install -e '.[pyside6,flet,textual]'
# Add .[wx] when wxPython is available for the platform.
Backend alignment and checked runtime versions are tracked in wx progress, flet progress, tkinter progress, and Textual plan.
Python 3.9 compatibility
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.9
The 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.
Get Started
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.
Hello World
# example/hello_world.py
from PUI.PySide6 import *
class Example(Application):
def content(self):
with Window(title="test", size=(320,240)):
Label("Hello world")
root = Example()
root.run()
State & Data Binding
# 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()
View Component
# 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)
....
Layout & Styling
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.
# 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
...
Canvas
# example/generic_canvas.py
from PUI.PySide6 import *
data = State()
class Example(Application):
def __init__(self):
super().__init__()
data.var = 50
def content(self):
with Window(title="blah", size=(640,480)):
with VBox():
Canvas(self.painter, data.var)
with HBox():
Button("-").click(self.on_minus)
Label(f"{data.var}").layout(weight=1)
Button("+").click(self.on_plus)
@staticmethod
def painter(canvas, var):
# Canvas coordinates are logical pixels. Use pixel_density when
# preparing raster images for HiDPI displays.
raster_scale = canvas.pixel_density
canvas.drawText(var, var/2, f"blah {var}")
canvas.drawLine(var, var, var*2, var*3, color=0xFFFF00)
def on_minus(self, event):
data.var -= 1
def on_plus(self, event):
data.var += 1
root = Example()
root.run()
canvas.pixel_density is the number of physical display pixels per logical
canvas pixel. It is updated before each paint where the backend exposes the
display scale, so moving a window between displays is handled automatically.
Canvas coordinates remain in logical pixels; multiply raster image resolution
by pixel_density when generating HiDPI content. Backends that cannot report a
display scale use 1.0.
Cookbook
python -m cookbook PySide6 (requires pygments for syntax highlight)
The code viewer uses a fixed dark Monokai palette. Backends without HTML support show plain text with the same dark background and light foreground.
python -m cookbook textual
Use make textual to run the Textual cookbook. For development mode, install
textual-dev and use make textual_dev.
python -m cookbook flet
python -m cookbook tkinter
Hot Reload
pip install jurigged
Then PUI will take care of view update (code)
Backends
Tier-1
- PySide6
Lower Priority
- wx
- tkinter
- flet
- textual (Text Mode)
- no canvas
Documents
Used by
TODO
- Dump with layout parameters and add test cases
- Toga
- [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?
Metadata
Release files for qpuiq 1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qpuiq-1.1.tar.gz | 87.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qpuiq-1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 207.4 kB
Release files / qpuiq-1.1.tar.gz
| Download URL | qpuiq-1.1.tar.gz |
|---|---|
| Size | 87.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
027a29ff25efee0386009ccff4b5ff2755e0284715531978d0d7e4d45b28ace2
|
|
BLAKE2b-256 checksum How to use checksums |
8ed3724f53210579fa888b99050b0f051168ab64a160a6cf17abbb3e3537f57a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.8
|
Release files / qpuiq-1.1-py3-none-any.whl
| Download URL | qpuiq-1.1-py3-none-any.whl |
|---|---|
| Size | 120.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6990595f48f537be926c977a1ca7eaa062aecb6220b4b353b292ec0158c70e36
|
|
BLAKE2b-256 checksum How to use checksums |
44a6f53dc3083d0a2fb83485ec30a45636a0a42eeb44d8e8d2f47efd4bf3e1e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.8
|