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

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]'
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 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 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]'.

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

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.

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.

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

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

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

Layout & Styling

Standalone examples

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 tkinter

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

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.

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

Hot Reload

pip install 'QPUIQ[reload]'

Then PUI will take care of view update (code)

Backends

The progress documents below track backend alignment, remaining work and checked runtime versions.

Documents

Used by

TODO

  • 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?

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