Skip to main content

Declarative GUI framework for Python and Qt

Project description

Edifice: Declarative GUI framework for Python and Qt

Edifice is a Python library for building declarative application user interfaces.

  • Modern declarative UI paradigm from web development.
  • 100% Python application development, no language inter-op.
  • A native desktop app instead of a bundled web browser.
  • Fast iteration via hot reloading.

This modern declarative UI paradigm is also known as “Model-View-Update,” or “The Elm Architecture.”

Edifice uses PySide6 or PyQt6 as a backend. So Edifice is like React, but with Python instead of JavaScript, and Qt Widgets instead of the HTML DOM.

If you have React experience, you'll find Edifice to be very easy to pick up. Edifice has function props and Hooks just like React.

Getting Started

Why Edifice?

The premise of Edifice is that GUI designers should only need to worry about what is rendered on the screen, not how the content is rendered.

Most existing GUI libraries in Python, such as Tkinter and Qt, operate imperatively. To create a dynamic application using these libraries, you must not only think about what to display to the user given state changes, but also how to issue the commands to achieve the desired effect.

Edifice allows you to declare the GUI as a function mapping state to displayed widgets, leaving the how to the library. User interactions update the state, and state changes update the GUI. Edifice makes it possible to write code like:

number, set_number = use_state(0)

with View():
    Button("Add 5", on_click=set_number(number + 5)
    for i in range(number):
        Label(str(i))

and get the expected result: clicking the Button will add 5 to the number, and this state change will add five more Labels to the GUI. You only need to specify what is to be displayed given the current state, and Edifice will work to ensure that the displayed widgets always correspond to the internal state.

Edifice is designed to make GUI applications easier for humans to reason about. Thus, the displayed GUI always reflect the internal state, even if an exception occurs part way through rendering — in that case, the state changes are unwound, and the display is unchanged.

Declarative UIs are also easier for developer tools to work with. Edifice provides two key features to make development easier:

  • Dynamic reloading of changed source code. This is especially useful for tweaking the looks of your application, allowing you to test if the margin should be 10px or 15px instantly without closing the app, reopening it, and waiting for everything to load.
  • Element inspector. Similar to the Inspect Elements tool of a browser, the component inspector will show you all Elements in your application along with the props and state, allowing you to examine the internal state of your complex component without writing a million print statements. Since the UI is specified as a (pure) function of state, the state you see completely describes your application, and you can even do things like rewinding to a previous state.

QML is another declarative GUI framework for Qt. Edifice differs from QML in these aspects:

  • Edifice interfaces are created purely in Python, whereas QML is written using a separate language.
  • Because Edifice interfaces are built in Python code, binding the code to the declared UI is much more straightforward.
  • Edifice makes it easy to create dynamic applications. It's easy to create, shuffle, and destroy widgets because the interface is written in Python code. QML assumes a much more static interface.

By analogy, QML is like HTML + JavaScript, whereas Edifice is like React.js. While QML and HTML are both declarative UI frameworks, they require imperative logic to add dynamism. Edifice and React allow fully dynamic applications to be specified declaratively.

How it works:

An Edifice Element encapsulates application state and defines the mapping from the state to UI in the render function. The state of a Element is divided into props and state. props are state passed to the Element in the constructor, whereas state is the Element's own internal state.

Changes to props and state will trigger a rerender of the Element and all its children. The old and new Element trees will be compared to one another, and a diffing algorithm will determine which components previously existed and which ones are new (the algorithm behaves similarly to the React diffing algorithm). Elements that previously existed will maintain their state, whereas their props will be updated. Finally, Edifice will try to ensure that the minimal update commands are issued to the UI. All this logic is handled by the library, and the Elements need not care about it.

Development Tools

Edifice also offers a few tools to aid in development.

Dynamic reload

Dyanamic hot-reload is very useful for fine-tuning the presentation styles of Elements deep within your application.

To run your application with dynamic reload, run:

python -m edifice path/to/app.py RootElement

This will run app.py with RootElement mounted as the root. A separate thread will listen to changes in all Python files in the directory containing app.py (recursing into subdirectories). You can customize which directory to listen to using the --dir flag.

When a file in your application is changed, the loader will reload all components in that file with preserved props (since that state comes from the caller), reset state, and trigger a re-render in the main thread.

Because rendering is abstracted away, it is simple to diff the UI trees and have the Edifice renderer figure out what to do using its normal logic.

Element Inspector

The Edifice component inspector shows the Element tree of your application along with the props and state of each component.

set_trace

PDB does not work well with PyQt applications. edifice.set_trace is equivalent to pdb.set_trace(), but it can properly pause the PyQt event loop to enable use of the debugger (users of PySide need not worry about this).

License

Edifice is MIT Licensed.

Edifice uses Qt under the hood, and both PyQt6 and PySide6 are supported. Note that PyQt6 is distributed with the GPL license while PySide6 is distributed under the more flexible LGPL license. See PyQt vs PySide Licensing.

Version History / Change Log / Release Notes

See Release Notes (source: versions.rst)

Contribution

Contributions are welcome; please send Pull Requests! See DEVELOPMENT.md for development notes.

When submitting a Pull Request, think about adding tests to tests and adding a line to the Unreleased section of the change log versions.rst.

Poetry Build System

The Poetry pyproject.toml specifies the package dependecies.

Because Edifice supports PySide6 and PyQt6 at the same time, neither are required by [tool.poetry.dependencies]. Instead they are both optional [tool.poetry.group.dev.dependencies]. A project which depends on Edifice should also depend on either PySide6 or PyQt6.

The requirements.txt is generated by

poetry export -f requirements.txt --output requirements.txt

To use the latest Edifice with a Poetry pyproject.toml from Github instead of PyPI, see Poetry git dependencies, for example:

[tool.poetry.dependencies]
python = ">=3.10,<3.11"
pyedifice = {git = "https://github.com/pyedifice/pyedifice.git"}
pyside6 = "6.5.1.1"

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyedifice-0.2.0.tar.gz (359.5 kB view hashes)

Uploaded Source

Built Distribution

pyedifice-0.2.0-py3-none-any.whl (830.6 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page