Skip to content

Pass any visible error modals from a Gradio app downstream to the app that has gr.load-ed it #11043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 17, 2025
6 changes: 6 additions & 0 deletions .changeset/grumpy-queens-stay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"gradio": minor
"gradio_client": minor
---

feat:Pass any visible error modals from a Gradio app downstream to the app that has `gr.load`-ed it
6 changes: 2 additions & 4 deletions client/python/gradio_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1272,10 +1272,8 @@ def _predict(*data) -> tuple:
"verbose error reporting. To enable, set show_error=True in launch()."
)
else:
raise AppError(
"The upstream Gradio app has raised an exception: "
+ result["error"]
)
message = result.pop("error")
raise AppError(message=message, **result)

try:
output = result["data"]
Expand Down
23 changes: 23 additions & 0 deletions client/python/gradio_client/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,26 @@ class AuthenticationError(ValueError):

class AppError(ValueError):
"""Raised when the upstream Gradio app throws an error because of the value submitted by the client."""

def __init__(
self,
message: str = "Error raised.",
duration: float | None = 10,
visible: bool = True,
title: str = "Error",
print_exception: bool = True,
):
"""
Parameters:
message: The error message to be displayed to the user. Can be HTML, which will be rendered in the modal.
duration: The duration in seconds to display the error message. If None or 0, the error message will be displayed until the user closes it.
visible: Whether the error message should be displayed in the UI.
title: The title to be displayed to the user at the top of the error modal.
print_exception: Whether to print traceback of the error to the console when the error is raised.
"""
self.title = title
self.message = message
self.duration = duration
self.visible = visible
self.print_exception = print_exception
super().__init__(self.message)
2 changes: 1 addition & 1 deletion gradio/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2444,7 +2444,7 @@ def launch(
auth: If provided, username and password (or list of username-password tuples) required to access app. Can also provide function that takes username and password and returns True if valid login.
auth_message: If provided, HTML message provided on login page.
prevent_thread_lock: By default, the gradio app blocks the main thread while the server is running. If set to True, the gradio app will not block and the gradio server will terminate as soon as the script finishes.
show_error: If True, any errors in the gradio app will be displayed in an alert modal and printed in the browser console log
show_error: If True, any errors in the gradio app will be displayed in an alert modal and printed in the browser console log. They will also be displayed in the alert modal of downstream apps that gr.load() this app.
server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT. If None, will search for an available port starting at 7860.
server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME. If None, will use "127.0.0.1".
max_threads: the maximum number of total threads that the Gradio app can generate in parallel. The default is inherited from the starlette library (currently 40).
Expand Down
16 changes: 9 additions & 7 deletions gradio/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from gradio_client.documentation import document
from gradio_client.exceptions import AppError


class DuplicateBlockError(ValueError):
Expand Down Expand Up @@ -57,7 +58,7 @@ class GradioVersionIncompatibleError(Exception):


@document(documentation_group="modals")
class Error(Exception):
class Error(AppError):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit but I think AppError should subclass Exception instead of ValueError ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably right, but I don't think it's a big deal and would be a breaking change for anyone building on top of the Gradio Client who's catching a ValueError. I've added it here: #10471

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you're trying to catch AppErrors in your client code, you would catch AppError explicitly as opposed to catching ValueErrors generally but sound good it's a minor point!

"""
This class allows you to pass custom error messages to the user. You can do so by raising a gr.Error("custom message") anywhere in the code, and when that line is executed the custom message will appear in a modal on the demo.
Example:
Expand Down Expand Up @@ -85,12 +86,13 @@ def __init__(
title: The title to be displayed to the user at the top of the error modal.
print_exception: Whether to print traceback of the error to the console when the error is raised.
"""
self.title = title
self.message = message
self.duration = duration
self.visible = visible
self.print_exception = print_exception
super().__init__(self.message)
super().__init__(
message=message,
duration=duration,
visible=visible,
title=title,
print_exception=print_exception,
)

def __str__(self):
return repr(self.message)
Expand Down
5 changes: 3 additions & 2 deletions gradio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import httpx
import orjson
from gradio_client.documentation import document
from gradio_client.exceptions import AppError
from typing_extensions import ParamSpec

import gradio
Expand Down Expand Up @@ -1460,9 +1461,9 @@ def error_payload(
error: BaseException | None, show_error: bool
) -> dict[str, bool | str | float | None]:
content: dict[str, bool | str | float | None] = {"error": None}
show_error = show_error or isinstance(error, Error)
show_error = show_error or isinstance(error, AppError)
if show_error:
if isinstance(error, Error):
if isinstance(error, AppError):
content["error"] = error.message
content["duration"] = error.duration
content["visible"] = error.visible
Expand Down
29 changes: 29 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

import numpy as np
import pytest
from gradio_client.exceptions import AppError
from hypothesis import given, settings
from hypothesis import strategies as st

from gradio import EventData, Request
from gradio.exceptions import Error
from gradio.external_utils import format_ner_list
from gradio.utils import (
FileSize,
Expand All @@ -28,6 +30,7 @@
delete_none,
diff,
download_if_url,
error_payload,
get_extension_from_file_path_or_url,
get_function_params,
get_icon_path,
Expand Down Expand Up @@ -695,3 +698,29 @@ def __deepcopy__(self, memo):
def test_get_icon_path():
assert get_icon_path("plus.svg").endswith("plus.svg")
assert get_icon_path("huggingface-logo.svg").endswith("huggingface-logo.svg")


def test_error_payload():
result = error_payload(None, False)
assert result == {"error": None}

result = error_payload(Exception("test error"), True)
assert result == {"error": "test error"}

gr_error = Error("custom error", duration=1.5, visible=True, title="Error Title")
result = error_payload(gr_error, False)
assert result == {
"error": "custom error",
"duration": 1.5,
"visible": True,
"title": "Error Title",
}

app_error = AppError("custom error")
result = error_payload(app_error, False)
assert result == {
"error": "custom error",
"duration": 10,
"visible": True,
"title": "Error",
}
Loading