Error boundary
ui.error_boundary(build) returns build() or, when it raises, an error card in its place, so one broken part of the view (a sidebar, a panel) never takes the whole window down. The card shows the error, the file and line it comes from and a Copy error button; on_details adds a Details button and fallback replaces the card with your own widget. on_error(info) is told about the error each time the part fails to draw. ui.ErrorInfo describes an error (its type, message, traceback and origin) and ui.error_card is the card on its own.
ui.error_boundary · Category: Feedback · Full reference →


def totals(): values = [] return ui.label(f"Average: {sum(values) / len(values)}")
pz.row([ ui.card(ui.muted("This panel keeps working."), title="Files", width=200), pz.container(ui.error_boundary(totals, title="Totals", on_details=on_details), width=380),], spacing=12, align="center")When to use
Section titled “When to use”- Around each panel of a big window, so a bug in one leaves the others working.
- Around parts that draw data you don’t control: a plugin, a file the user opened, a reply from a server.
- Open an Error dialog from
on_detailsto show the whole traceback.
import pyzed as pzfrom pyzed import ui
class Report(pz.App): def __init__(self): self.error = None # the ui.ErrorInfo shown in the dialog
def totals(self): ... # may raise
def show(self, info): self.error = info
def close(self): self.error = None
def view(self): page = pz.row([ self.files(), ui.error_boundary(self.totals, title="Totals", on_details=self.show), ]) dialog = ui.error_dialog(self.error, self.close) if self.error else None return ui.dialog_layer(page, dialog, on_dismiss=self.close)Variants
Section titled “Variants”Boundary
Section titled “Boundary”While build() works the boundary returns its widget untouched; only a part that raises is replaced.


Code
ui.error_boundary(lambda: ui.label("Average: 4.2"))

Code
def totals(): values = [] return ui.label(f"Average: {sum(values) / len(values)}")
pz.container(ui.error_boundary(totals), width=360)

title="Totals"Code
def totals(): values = [] return ui.label(f"Average: {sum(values) / len(values)}")
pz.container(ui.error_boundary(totals, title="Totals"), width=360)

on_details=Code
def totals(): values = [] return ui.label(f"Average: {sum(values) / len(values)}")
pz.container(ui.error_boundary(totals, title="Totals", on_details=on_details), width=360)

fallback=Code
def totals(): values = [] return ui.label(f"Average: {sum(values) / len(values)}")
ui.error_boundary(totals, fallback=lambda info: ui.callout( "Totals are not available", info.summary, tint="warning"))Error card
Section titled “Error card”ui.error_card is the card on its own, for errors you catch yourself.


ui.error_card(info)Code
info = ui.ErrorInfo("KeyError", "'email'", origin=("app/forms.py", 42), where="Sign-up form")pz.container(ui.error_card(info), width=360)

title=, actions=Code
info = ui.ErrorInfo("KeyError", "'email'", origin=("app/forms.py", 42), where="Sign-up form")pz.container(ui.error_card(info, title="The form could not be loaded", actions=[ui.button("Retry", on_retry, icon="rotate-ccw", size="sm", variant="ghost")]), width=360)

Code
pz.container(ui.error_card(ui.ErrorInfo("TimeoutError", "the server took too long")), width=360)Events
Section titled “Events”Error boundary has 2 events of its own, listed below. Like every widget it also has the 18 common events (click, double_click, right_click, mouse_enter, drag, key_down, resize…), attached with .on(...): see Events and Events of every component.
| Event | Of | Handler gets | When |
|---|---|---|---|
on_error |
ui.error_boundary |
(info) |
Receives the ui.ErrorInfo. |
on_details |
ui.error_boundary, ui.error_card |
(info) |
Adds a Details button; receives the ui.ErrorInfo (open a ui.error_dialog). |
Handler gets is what Pyzed passes to your callable: () means nothing. Any handler can also be a message (a value that is not callable), which goes to update().
Handling the events
Section titled “Handling the events”The same events written three ways. Imperative: methods of the app change self. Declarative: the handlers produce messages and one update() decides. Functional: a pure function draws an immutable state and asks for changes with set_state(...). See Imperative, declarative and functional.
class Report(pz.App): def __init__(self): self.values = [] self.error = None # the ui.ErrorInfo shown in the dialog
def totals(self): return ui.label(f"Average: {sum(self.values) / len(self.values)}") # raises on []
def view(self): page = ui.error_boundary(self.totals, title="Totals", on_error=lambda info: print(info.message), # every failure on_details=lambda info: setattr(self, "error", info)) # Details dialog = ui.error_dialog(self.error, lambda: setattr(self, "error", None)) if self.error else None return ui.dialog_layer(page, dialog)from dataclasses import dataclass
@dataclassclass ShowError: info: object
class Report(pz.App): error = None
def totals(self): return ui.label(f"Average: {sum([]) / len([])}")
def update(self, message): match message: case ShowError(info): self.error = info case "close": self.error = None
def view(self): page = ui.error_boundary(self.totals, title="Totals", on_details=ShowError) return ui.dialog_layer(page, ui.error_dialog(self.error, "close") if self.error else None)from dataclasses import dataclass
@dataclass(frozen=True)class State: values: tuple = () failures: int = 0
def totals(values): return ui.label(f"Average: {sum(values) / len(values)}")
def view(state, set_state): return pz.column([ ui.error_boundary(lambda: totals(state.values), title="Totals", on_error=lambda info: set_state(failures=state.failures + 1)), ui.button("Add 10", lambda: set_state(values=state.values + (10,))), ], spacing=8, width=380)Properties and API
Section titled “Properties and API”ui.error_boundary
Section titled “ui.error_boundary”error_boundary( build, *, on_error=None, title=None, where='', on_details=None, fallback=None, theme=None,)Draws build(), or an error card when it raises, so one part of the view that fails never takes the whole window down (like React’s error boundaries). on_error(info) is told about the error each time the part is drawn and fails, so report it once (compare info.traceback).
| Property | Default | Description |
|---|---|---|
build |
required | A function that returns the part’s widget. |
title |
None |
Heading of the card (and the error’s where). |
where |
"" |
What the part is, for the report. |
fallback |
None |
Your own widget for the error: fallback(info). |
theme |
None |
Theme override. |
Events: on_error, on_details (events).
ui.error_card
Section titled “ui.error_card”error_card(info, *, title=None, on_details=None, actions=(), theme=None)The card ui.error_boundary draws in place of what failed: the error, where it comes from and a Copy error button (and Details with on_details).
| Property | Default | Description |
|---|---|---|
info |
required | A ui.ErrorInfo. |
title |
None |
Heading. |
actions |
() |
More buttons after them. |
theme |
None |
Theme override. |
Events: on_details (events).
ui.ErrorInfo
Section titled “ui.ErrorInfo”@dataclassclass ErrorInfo(kind, message, traceback='', origin=None, where='', time=0.0)An error as ui.error_card and ui.error_dialog show it: kind (the exception’s type), message, the whole traceback, the origin (path, line) it comes from (the last frame outside the standard library, installed packages and Pyzed), where it happened and time. ErrorInfo.from_exception(err, where=...) makes one (prefer= picks which frames may be the origin); ErrorInfo.current() describes the exception being handled. summary is Type: message, location is file.py:12 and report() is everything as text for a bug report.
| Property | Default | Description |
|---|---|---|
kind |
required | The exception’s type name. |
message |
required | Its message. |
traceback |
"" |
The whole traceback, as Python prints it. |
origin |
None |
(path, line) of the code it comes from, or None. |
where |
"" |
What was running. |
time |
0.0 |
When it happened (time.time()). |