From a9e1a8ac5633c5336fea1c63d7f66a9883e7e6e1 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 13 May 2024 12:06:06 -0700 Subject: [PATCH] Make exceptions in the Client more specific (#8264) * more specific exceptions * format * add changeset * fix * add changeset --------- Co-authored-by: gradio-pr-bot --- .changeset/blue-frogs-fetch.md | 6 ++++ client/python/gradio_client/client.py | 13 ++++++-- client/python/gradio_client/exceptions.py | 4 +++ client/python/gradio_client/utils.py | 6 ++-- client/python/test/conftest.py | 2 +- client/python/test/test_client.py | 37 ++++++++++++++--------- client/python/test/test_utils.py | 6 ++-- 7 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 .changeset/blue-frogs-fetch.md diff --git a/.changeset/blue-frogs-fetch.md b/.changeset/blue-frogs-fetch.md new file mode 100644 index 0000000000..31a58a3161 --- /dev/null +++ b/.changeset/blue-frogs-fetch.md @@ -0,0 +1,6 @@ +--- +"gradio": patch +"gradio_client": patch +--- + +feat:Make exceptions in the Client more specific diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 7aa0a6894c..091db7b0e2 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -38,7 +38,7 @@ from gradio_client import utils from gradio_client.compatibility import EndpointV3Compatibility from gradio_client.data_classes import ParameterInfo from gradio_client.documentation import document -from gradio_client.exceptions import AuthenticationError +from gradio_client.exceptions import AppError, AuthenticationError from gradio_client.utils import ( Communicator, JobStatus, @@ -1216,7 +1216,16 @@ class Endpoint: raise ValueError(f"Unsupported protocol: {self.protocol}") if "error" in result: - raise ValueError(result["error"]) + if result["error"] is None: + raise AppError( + "The upstream Gradio app has raised an exception but has not enabled " + "verbose error reporting. To enable, set show_error=True in launch()." + ) + else: + raise AppError( + "The upstream Gradio app has raised an exception: " + + result["error"] + ) try: output = result["data"] diff --git a/client/python/gradio_client/exceptions.py b/client/python/gradio_client/exceptions.py index 5df7b15f14..f8da6bbc6f 100644 --- a/client/python/gradio_client/exceptions.py +++ b/client/python/gradio_client/exceptions.py @@ -8,3 +8,7 @@ class AuthenticationError(ValueError): """Raised when the client is unable to authenticate itself to a Gradio app due to invalid or missing credentials.""" pass + + +class AppError(ValueError): + """Raised when the upstream Gradio app throws an error because of the value submitted by the client.""" diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index 71a113ba6e..86855c3b67 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -1126,18 +1126,18 @@ def construct_args( for key, value in kwargs.items(): if key in kwarg_arg_mapping: if kwarg_arg_mapping[key] < num_args: - raise ValueError( + raise TypeError( f"Parameter `{key}` is already set as a positional argument. Please click on 'view API' in the footer of the Gradio app to see usage." ) else: _args[kwarg_arg_mapping[key]] = value else: - raise ValueError( + raise TypeError( f"Parameter `{key}` is not a valid key-word argument. Please click on 'view API' in the footer of the Gradio app to see usage." ) if _Keywords.NO_VALUE in _args: - raise ValueError( + raise TypeError( f"No value provided for required argument: {kwarg_names[_args.index(_Keywords.NO_VALUE)]}" ) diff --git a/client/python/test/conftest.py b/client/python/test/conftest.py index 4613820835..87648ca635 100644 --- a/client/python/test/conftest.py +++ b/client/python/test/conftest.py @@ -242,7 +242,7 @@ def count_generator_no_api(): def count_generator_demo_exception(): def count(n): for i in range(int(n)): - time.sleep(0.1) + time.sleep(0.01) if i == 5: raise ValueError("Oh no!") yield i diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index c69530d6b0..f321fb319c 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -22,7 +22,7 @@ from huggingface_hub.utils import RepositoryNotFoundError from gradio_client import Client, file from gradio_client.client import DEFAULT_TEMP_DIR -from gradio_client.exceptions import AuthenticationError +from gradio_client.exceptions import AppError, AuthenticationError from gradio_client.utils import ( Communicator, ProgressUnit, @@ -39,9 +39,9 @@ def connect( demo: gr.Blocks, serialize: bool = True, output_dir: str = DEFAULT_TEMP_DIR, - max_file_size=None, + **kwargs, ): - _, local_url, _ = demo.launch(prevent_thread_lock=True, max_file_size=max_file_size) + _, local_url, _ = demo.launch(prevent_thread_lock=True, **kwargs) try: yield Client(local_url, serialize=serialize, output_dir=output_dir) finally: @@ -228,17 +228,6 @@ class TestClientPredictions: outputs.append(o) assert outputs == [str(i) for i in range(3)] - @pytest.mark.flaky - def test_intermediate_outputs_with_exception(self, count_generator_demo_exception): - with connect(count_generator_demo_exception) as client: - with pytest.raises(Exception): - client.predict(7, api_name="/count") - - with pytest.raises( - ValueError, match="Cannot call predict on this function" - ): - client.predict(5, api_name="/count_forever") - def test_break_in_loop_if_error(self, calculator_demo): with connect(calculator_demo) as client: job = client.submit("foo", "add", 4, fn_index=0) @@ -682,7 +671,7 @@ class TestClientPredictionsWithKwargs: def test_missing_params(self, calculator_demo): with connect(calculator_demo) as client: with pytest.raises( - ValueError, match="No value provided for required argument: num2" + TypeError, match="No value provided for required argument: num2" ): client.predict(num1=3, operation="add", api_name="/predict") @@ -1371,3 +1360,21 @@ class TestDuplication: "test_value2", token=HF_TOKEN, ) + + +def test_upstream_exceptions(count_generator_demo_exception): + with connect(count_generator_demo_exception, show_error=True) as client: + with pytest.raises( + AppError, match="The upstream Gradio app has raised an exception: Oh no!" + ): + client.predict(7, api_name="/count") + + with connect(count_generator_demo_exception) as client: + with pytest.raises( + AppError, + match="The upstream Gradio app has raised an exception but has not enabled verbose error reporting.", + ): + client.predict(7, api_name="/count") + + with pytest.raises(ValueError, match="Cannot call predict on this function"): + client.predict(5, api_name="/count_forever") diff --git a/client/python/test/test_utils.py b/client/python/test/test_utils.py index 3847611a6a..c8f3fd6967 100644 --- a/client/python/test/test_utils.py +++ b/client/python/test/test_utils.py @@ -230,20 +230,20 @@ class TestConstructArgs: def test_positional_arg_and_kwarg_for_same_parameter(self): parameters_info = [{"label": "param1", "parameter_name": "a"}] with pytest.raises( - ValueError, match="Parameter `a` is already set as a positional argument." + TypeError, match="Parameter `a` is already set as a positional argument." ): utils.construct_args(parameters_info, (1,), {"a": 2}) def test_invalid_kwarg(self): parameters_info = [{"label": "param1", "parameter_name": "a"}] with pytest.raises( - ValueError, match="Parameter `b` is not a valid key-word argument." + TypeError, match="Parameter `b` is not a valid key-word argument." ): utils.construct_args(parameters_info, (), {"b": 1}) def test_required_arg_missing(self): parameters_info = [{"label": "param1", "parameter_name": "a"}] with pytest.raises( - ValueError, match="No value provided for required argument: a" + TypeError, match="No value provided for required argument: a" ): utils.construct_args(parameters_info, (), {})