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

To use it with Jupyter, if you install the library in a custom environment (conda, venv, or other), you will likely need to tell Jupyter where to find the front-end files. You can do this by running the following command (only once):

pret update-jupyter-config --apply

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, create_store, run, use_state, use_store_snapshot
from pret.ui.joy import Checkbox, Input, Stack, Typography

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


@component
def TodoApp():
  todos = use_store_snapshot(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