Skip to content

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 →

Error boundaryError boundary
  • 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_details to show the whole traceback.
import pyzed as pz
from 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)

While build() works the boundary returns its widget untouched; only a part that raises is replaced.

Error boundary: No errorError boundary: No error
No error
Code
ui.error_boundary(lambda: ui.label("Average: 4.2"))
Error boundary: Default cardError boundary: Default card
Default card
Code
def totals():
values = []
return ui.label(f"Average: {sum(values) / len(values)}")
pz.container(ui.error_boundary(totals), width=360)
Error boundary: title="Totals"Error boundary: title="Totals"
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)
Error boundary: on_details=Error boundary: on_details=
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)
Error boundary: fallback=Error boundary: fallback=
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"))

ui.error_card is the card on its own, for errors you catch yourself.

Error boundary: ui.error_card(info)Error boundary: ui.error_card(info)
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)
Error boundary: title=, actions=Error boundary: title=, actions=
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)
Error boundary: No originError boundary: No origin
No origin
Code
pz.container(ui.error_card(ui.ErrorInfo("TimeoutError", "the server took too long")),
width=360)

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().

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)
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).

Full reference →

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).

Full reference →

@dataclass
class 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()).

Full reference →