Skip to content

Getting started with Pret

Pret is a library for building full-stack reactive user interfaces in Python, using React as a rendering engine.

Installation

pip install pret pret-joy  --user

Features

  • Python, only Python: pret is written in Python: you can write your both your UI and server actions Python. No need to learn a new language.
  • Client-side rendering: unlike other Python UI frameworks, pret runs primarily in the browser. This enables a fast response time to user actions (like hover events), and a better user experience under degraded network conditions.
  • Built on React: pret uses React as a rendering engine, and benefits from its ecosystem.
  • Reactive: unlike other solutions like ipywidgets, pret is reactive. Only the parts of the UI that need to be updated are re-rendered.
  • State management: in addition to React's local state management (i.e. use_state), pret provides a global and modular state management solution that is synchronized both between components, and between the client and the server.
  • Modular: pret is designed to be modular. You can easily create your own components, and reuse them in other pret-based projects.
  • Integrated with Jupyter: pret components can be used in Jupyter notebooks, as well as in standalone web applications.
  • Remote execution: pret can call and use the result of Python functions on the server from the browser

Use it in a notebook

Let's write a simple todo app that should:

  • display a list of todos, that can be checked as done
  • display the number remaining todos
  • change the font to bold as a todo is hovered
  • allow editing the todo list directly in Python

Copy and paste the following code in a notebook:

from pret import component, proxy, run, use_state, use_tracked
from pret.ui.joy import Checkbox, Input, Stack, Typography

state = proxy(
    {
        "faire à manger": True,
        "faire la vaisselle": False,
    },
    remote_sync=True,
)


@component
def TodoApp():
    todos = use_tracked(state)
    typed, set_typed = use_state("")
    num_remaining = sum(not ok for ok in todos.values())
    plural = "s" if num_remaining > 1 else ""

    def on_key_down(event):
        if event.key == "Enter":
            state[typed] = False
            set_typed("")

    return Stack(
        *(
            Checkbox(
                label=todo,
                checked=ok,
                on_change=lambda e, t=todo: state.update({t: e.target.checked}),
            )
            for todo, ok in todos.items()
        ),
        Input(
            value=typed,
            on_change=lambda event: set_typed(event.target.value),
            on_key_down=on_key_down,
            placeholder="Add a todo",
        ),
        Typography(
            f"Number of unfinished todo{plural}: {num_remaining}",
            sx={"minWidth": "230px"},  # just to avoid jittering when it's centered
        ),
        spacing=2,
        sx={"m": 1},
    )


TodoApp()

In comparison, the closest alternative using ipywidgets looks like the following snippet:

IPyWidget's implementation
import ipywidgets as widgets

state = {
    "faire à manger": True,
    "faire la vaisselle": False,
}


class IPWTodoApp:
    def __init__(self):
        self.box = widgets.VBox()
        self.render()

    def _repr_mimebundle_(self, *args, **kwargs):
        return self.box._repr_mimebundle_(*args, **kwargs)

    def render(self, *args, **kwargs):
        num_remaining = sum([not checked for _, checked in state.items()])
        plural = "s" if num_remaining > 1 else ""

        def on_input_submit(sender):
            state[input_widget.value] = False
            self.render()

        def create_todo_item(todo, checked):
            def update_todo_status(*args, **kwargs):
                state[todo] = checkbox.value
                self.render()

            checkbox = widgets.Checkbox(
                value=checked,
                description=todo,
                disabled=False,
                indent=False,
            )
            checkbox.observe(update_todo_status, names="value")
            return checkbox

        input_widget = widgets.Text(
            placeholder="Add a todo",
            description="",
            disabled=False,
        )
        input_widget.on_submit(on_input_submit)

        self.box.children = [
            *(create_todo_item(todo, checked) for todo, checked in state.items()),
            input_widget,
            widgets.Label(value=f"Number of unfinished todo{plural}: {num_remaining}"),
        ]


IPWTodoApp()

You also lose some features:

  • the app stops working if the server shuts down
  • hover events cannot be listened to
  • no React dom diffing: the app must either be re-rendered entirely (as in the example), or you must determine specifically which field of which widget to update

Use it in a standalone app

You can also use pret to build standalone web applications. Copy the above code in a file named app.py, and change the last line to

if __name__ == "__main__":
    run(TodoApp)

Then, run the following command, and voilà !

python app.py