Skip to content
buganiniPublic

About

Python Reactive/Declarative UI Framework. PUI doesn't do UI itself, it turns imperative UI libraries into declarative flavor with virtual DOM and aims to maintain interoperability.

Topics

Resources

Stars

96 stars

Watchers

5 watching

Forks

Repository files navigation

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.

Slides for SciWork 2023

CPPUI: C++ Version

Features

Documents

Try it: Browser Cookbook

Documentation: Index

API and layout references, backend guides, examples and remaining work.

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[web]'
pip install 'QPUIQ[textual]'

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 dependencies

The 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 PUI/doc/landlock.md
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,cookbook]'
# Add .[wx] when wxPython is available for the platform.
# Add reload to enable hot reload, e.g. -e '.[web,cookbook,reload]'.

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.

Hello World

# 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()

Hello World

Reference: Declarative Components

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()

State & Data Binding

Cookbook: State & Binding

State transactions across threads

State operations are synchronized across threads. Use with state: for compound updates or consistent multi-field reads, and keep native widget access on its UI thread:

with data:
    data.count += 1

Reference: State Transactions

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)

....

View Component

Layout & Styling

PUI sizes components from their content and distributes extra space using shared, per-axis expansion priorities. Labels and buttons normally fit their content; text fields expand horizontally, while Canvas and MatplotlibCanvas can expand on both axes. Nested containers carry their children's expansion needs upward. Grid applies the same priorities to rows and columns, including spanning cells.

Use .layout() to adjust sizes and spacing:

  • width / height set desired dimensions and stop automatic expansion on that axis.
  • minWidth / minHeight set minimum sizes without stopping expansion.
  • Positive weight takes precedence on an HBox/VBox's main axis and shares space proportionally with other weighted siblings. In Grid it applies to both axes. weight=0 disables expansion on those axes.
  • padding and margin control spacing.

Use .style() for shared colors and typography, or .qt(StyleSheet={...}) for Qt-specific styling such as borders and other QSS properties. Textual uses .tLayout() and .tStyle() for terminal-cell dimensions and terminal styling. Native fonts, themes and size hints can still produce different exact dimensions; component support and styling limits are documented in the reference.

# Inside content(), with components imported from your selected backend:
with VBox().layout(padding=12).tLayout(padding=1):
    Label("Details").style(fontWeight=FontWeight.Bold).tStyle(
        fontWeight=FontWeight.Bold,
    )
    Label("Qt-specific details").qt(StyleSheet={
        "font-weight": "bold", "border-bottom": "1px solid #888",
    })
    with HBox():
        Text("Notes").layout(weight=1, minWidth=180).tLayout(minWidth=20)
        Text("Preview").layout(weight=2, padding=10).style(
            color=0x1C2A30, bgColor=0xFFF7E6,
        ).tLayout(padding=1).tStyle(color=0x1C2A30, bgColor=0xFFF7E6)
  • Reference: Layout
  • Reference: Layout and Style Modifiers

Responsive / Adaptive Layout

Adapt to the parent container's available size: switch between rows and columns, show or hide content at breakpoints, and override layout or style settings. Desktop and browser backends use logical pixels; Textual has separate cell-based breakpoints and settings.

# Inside content(), with components imported from your selected backend:
with HVBox(minWidth=600, tMinWidth=60):
    Label("Title").style(fontSize=14).wideStyle(900, fontSize=20)
    Button("Save")

Grid layouts can also change their column count at breakpoints and automatically place items while respecting spans.

  • Reference: Responsive Layout
  • Cookbook: Adaptive Grid
  • Cookbook: Adaptive Layout

Cookbook

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 PySide6

The 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.

Styled examples, Grid tiles, Canvas and Matplotlib use muted blues, terracotta, greens and warm creams, with color values written directly in each example.

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.

Cookbook 1 Cookbook 2

python -m PUI.cookbook textual

Use make textual to run the Textual cookbook. For development mode, install textual-dev and use make textual_dev.

Cookbook textual

python -m PUI.cookbook flet Cookbook flet

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().

Cookbook tkinter

AI Development

Install an optional agent SDK alongside your backend (Python 3.10+):

pip install 'QPUIQ[pyside6,codex]'   # or QPUIQ[web,claude]
python -m PUI

The 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, Flet and tkinter 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/Flet/tkinter/Web targets.

Used by

About

Python Reactive/Declarative UI Framework. PUI doesn't do UI itself, it turns imperative UI libraries into declarative flavor with virtual DOM and aims to maintain interoperability.

Topics

Resources

Stars

96 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages