Creating full-stack, interactive browser UI apps is a complex process, typically involving knowledge of multiple programming and layout/style languages, dependency managers, build systems, HTTP/Websockets, a frontend framework, a UI component library, and a backend framework. In addition, if you want a reactive UI that updates without reloading, you typically have to design a network protocol to shuttle messages back and forth and update the UI state in response to server messages.
Hyperdiv is an attempt to reduce the complexity of building basic browser UI apps and tools, while minimizing negative tradeoffs.
Hyperdiv arrives at:
Hyperdiv is a pure Python framework and API. You pip install
it,
import it, and get going making an app.
Hyperdiv runs your Python app in Python. It does not compile your app
to Javascript to ship it to the browser. You run your app with python my-app.py
without extra-Pythonic steps or boilerplate.
Hyperdiv internally manages the communication with the browser. It uses a custom internal protocol to keep the Python app and the browser in sync, without the Hyperdiv programmer having to worry about network communication details.
Hyperdiv comes pre-packaged with the Shoelace component library, whose components are pre-styled and which includes dark/light modes with color palettes that adapt to the mode. Hyperdiv has forms with a rich set of input components and input validation implemented in Python.
Hyperdiv also has built-in markdown support via Mistune and Pygments, data tables, and charts via Chart.js.
Hyperdiv provides a concept of "state", from which all of its components are derived. Hyperdiv's reactive state architecture resembles that of Preact Signal Hooks, but with important differences.
When any piece of state is updated, Hyperdiv re-runs the top-level app function and the UI automatically updates.
When the app re-runs, Hyperdiv collects the new virtual DOM from the run and diffs it against the current virtual DOM, that was generated by the previous run. Then it sends minimal patches to the in-browser Hyperdiv Javascript frontend, which patches them into the real in-browser DOM.
Hyperdiv's frontend listens for UI events and sends state updates to Hyperdiv's Python server. The Python side applies the state updates, re-runs the app function, and sends virtual DOM patches to the frontend.
Hyperdiv uniquely blends reactive state with immediate-mode UI. We believe the ergonomic features of immediate-mode UI lead to a smoother and more direct programming experience for people who want to get basic stuff done quickly. Namely:
Instead of having to build a UI data structure, like this:
def my_ui(show_text=False):
ui = [button("Button 1")]
if show_text:
ui.append(text("Hello"))
ui.append(button("Button 2"))
return ui
Hyperdiv does this:
def my_ui(show_text=False):
button("Button 1")
if show_text:
text("Hello")
button("Button 2")
The UI is implicitly "collected" as the code runs, instead of you having to manually build a data structure. And then the return values of your functions can be whatever you want.
Instead of
def my_callback(event):
# do something
def my_app():
button("Click Me", on_click=my_callback)
Hyperdiv does this:
def my_app():
b = button("Click Me")
if b.clicked:
# Do something
Or more succinctly
def my_app():
if button("Click Me").clicked:
# Do something
clicked
is a prop like any other, with the important
difference that it is (a) readonly and (b) automatically reset
back to its default value False
after one run of the app.
Note that for slow event handlers you can use Hyperdiv tasks to run handler code asynchronously.
We could have taken the common approach of immutable UI components + external mutable state. Hypothetical pseudocode example:
class State:
checked = False
@staticmethod
def toggle():
State.checked = not State.checked
def my_app():
checkbox("Check Me", checked=State.checked, on_change=State.toggle)
text("Checkbox is", State.checked)
In this popular approach, the checkbox component and its props are immutable, and the only way to endow them with mutability is to define external mutable state and connect the state to the checkbox's props.
Instead, Hyperdiv does this:
def my_app():
ch = checkbox("Check Me")
text("Checkbox is", ch.checked)
The checkbox's checked
prop is automatically hooked up
to mutable state internally, so you don't have to do that
plumbing yourself. When a user toggles the checkbox in the
UI, the checked
state is mutated and the app re-runs
with the new state.
Note that the checkbox does not "encapsulate" its
state. And really, it cannot, since the my_app()
function is executed over and over as state changes, and
therefore the checkbox component is recreated over and
over. The actual state is held in a global data structure
that persists across app runs, and component props are
read/write proxies into that global state.
Hyperdiv uses an indexing technique based on call stack introspection, previously explored in the Immediate-Mode UI space, to correctly look up the state of each component as the app re-runs.
Hyperdiv's Javascript frontend is a piece of fixed, generic boilerplate that mainly applies incoming virtual dom and virtual dom patches to the in-browser dom. In this sense, Hyperdiv is similar to HTML over the wire systems like Hotwire and Htmx.
As a side effect, Hyperdiv does not expose your app's logic to the browser. At any one time, the browser only contains the currently rendered state of the app. An attacker trying to reverse engineer your UI's logic will be unable to do so, short of enumerating its internal states via UI interactions.
In addition, the Javascript frontend never receives unrendered data, as is typical in SPAs. It only receives already rendered virtual DOM patches. So there's no possibility of leaking out unrendered data fields over the network.
Install Hyperdiv and open the documentation app locally:
> pip install hyperdiv
> hyperdiv docs
Hyperdiv requires Python 3.9+ and has been tested on macOS and Linux.
Star on GitHub