Skip to content

Commit 3e039ec

Browse files
committed
Surface errors in a similar fashion to Chat()
1 parent c7d6e05 commit 3e039ec

File tree

1 file changed

+37
-0
lines changed

1 file changed

+37
-0
lines changed

shiny/ui/_markdown_stream.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .._docstring import add_example
66
from .._typing_extensions import TypedDict
77
from ..session import require_active_session
8+
from ..types import NotifyException
89
from . import Tag
910
from ._html_deps_py_shiny import markdown_stream_dependency
1011

@@ -49,6 +50,16 @@ class MarkdownStream:
4950
- `"html"`: for rendering HTML content.
5051
- `"text"`: for plain text.
5152
- `"semi-markdown"`: for rendering markdown, but with HTML tags escaped.
53+
on_error
54+
How to handle errors that occur while streaming. When `"unhandled"`,
55+
the app will stop running when an error occurs. Otherwise, a notification
56+
is displayed to the user and the app continues to run.
57+
58+
* `"auto"`: Sanitize the error message if the app is set to sanitize errors,
59+
otherwise display the actual error message.
60+
* `"actual"`: Display the actual error message to the user.
61+
* `"sanitize"`: Sanitize the error message before displaying it to the user.
62+
* `"unhandled"`: Do not display any error message to the user.
5263
5364
Note
5465
----
@@ -62,11 +73,21 @@ def __init__(
6273
*,
6374
content: str = "",
6475
content_type: StreamingContentType = "markdown",
76+
on_error: Literal["auto", "actual", "sanitize", "unhandled"] = "auto",
6577
):
6678
self.id = id
6779
self._content = content
6880
self._content_type: StreamingContentType = content_type
6981

82+
# Default to sanitizing until we know the app isn't sanitizing errors
83+
if on_error == "auto":
84+
on_error = "sanitize"
85+
app = self._session.app
86+
if app is not None and not app.sanitize_errors: # type: ignore
87+
on_error = "actual"
88+
89+
self.on_error = on_error
90+
7091
# TODO: remove the `None` when this PR lands:
7192
# https://github.com/posit-dev/py-shiny/pull/793/files
7293
self._session = require_active_session(None)
@@ -109,6 +130,15 @@ async def _task():
109130

110131
_task()
111132

133+
# Since the task runs in the background (outside/beyond the current context,
134+
# if any), we need to manually raise any exceptions that occur
135+
@reactive.effect
136+
async def _handle_error():
137+
e = _task.error()
138+
if e:
139+
await self._raise_exception(e)
140+
_handle_error.destroy() # type: ignore
141+
112142
def _append(self, content: str):
113143
msg: ContentMessage = {
114144
"id": self.id,
@@ -144,6 +174,13 @@ def _streaming_dot(self):
144174
}
145175
self._send_custom_message(end)
146176

177+
async def _raise_exception(self, e: BaseException):
178+
if self.on_error == "unhandled":
179+
raise e
180+
else:
181+
sanitize = self.on_error == "sanitize"
182+
raise NotifyException(str(e), sanitize=sanitize) from e
183+
147184
def _send_custom_message(self, msg: Union[ContentMessage, isStreamingMessage]):
148185
if self._session.is_stub_session():
149186
return

0 commit comments

Comments
 (0)