diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a140f6043..6b63ba96e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ - Update depedencies by [@pngwn](https://github.com/pngwn) in [PR 4643](https://github.com/gradio-app/gradio/pull/4643) - The theme builder now launches successfully, and the API docs are cleaned up. By [@abidlabs](https://github.com/aliabid94) in [PR 4683](https://github.com/gradio-app/gradio/pull/4683) - Remove `cleared_value` from some components as its no longer used internally by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4685](https://github.com/gradio-app/gradio/pull/4685) +- Better errors when you define two Blocks and reference components in one Blocks from the events in the other Blocks [@abidlabs](https://github.com/aliabid94) in [PR 4738](https://github.com/gradio-app/gradio/pull/4738). + ## Breaking Changes: diff --git a/gradio/analytics.py b/gradio/analytics.py index c4b710aa26..6724619ae1 100644 --- a/gradio/analytics.py +++ b/gradio/analytics.py @@ -120,10 +120,10 @@ def launched_analytics(blocks: gradio.Blocks, data: dict[str, Any]) -> None: if y in blocks.blocks ] inputs_telemetry = inputs_telemetry + [ - str(blocks.blocks[y]) for y in x["inputs"] + str(blocks.blocks[y]) for y in x["inputs"] if y in blocks.blocks ] outputs_telemetry = outputs_telemetry + [ - str(blocks.blocks[y]) for y in x["outputs"] + str(blocks.blocks[y]) for y in x["outputs"] if y in blocks.blocks ] additional_data = { "version": GRADIO_VERSION, diff --git a/gradio/blocks.py b/gradio/blocks.py index bf6bc1f9de..b3369f8356 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -36,7 +36,11 @@ from gradio import ( ) from gradio.context import Context from gradio.deprecation import check_deprecated_parameters, warn_deprecation -from gradio.exceptions import DuplicateBlockError, InvalidApiNameError +from gradio.exceptions import ( + DuplicateBlockError, + InvalidApiNameError, + InvalidBlockError, +) from gradio.helpers import EventData, create_tracker, skip, special_args from gradio.themes import Default as DefaultTheme from gradio.themes import ThemeClass as Theme @@ -1116,7 +1120,12 @@ class Blocks(BlockContext): processed_input = [] for i, input_id in enumerate(dependency["inputs"]): - block = self.blocks[input_id] + try: + block = self.blocks[input_id] + except KeyError as e: + raise InvalidBlockError( + f"Input component with id {input_id} used in {dependency['trigger']}() event is not defined in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." + ) from e assert isinstance( block, components.IOComponent ), f"{block.__class__} Component with id {input_id} not a valid input component." @@ -1130,7 +1139,12 @@ class Blocks(BlockContext): predictions = [] for o, output_id in enumerate(dependency["outputs"]): - block = self.blocks[output_id] + try: + block = self.blocks[output_id] + except KeyError as e: + raise InvalidBlockError( + f"Output component with id {output_id} used in {dependency['trigger']}() event not found in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." + ) from e assert isinstance( block, components.IOComponent ), f"{block.__class__} Component with id {output_id} not a valid output component." @@ -1191,7 +1205,12 @@ Received inputs: if block_fn.preprocess: processed_input = [] for i, input_id in enumerate(dependency["inputs"]): - block = self.blocks[input_id] + try: + block = self.blocks[input_id] + except KeyError as e: + raise InvalidBlockError( + f"Input component with id {input_id} used in {dependency['trigger']}() event not found in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." + ) from e assert isinstance( block, components.Component ), f"{block.__class__} Component with id {input_id} not a valid input component." @@ -1269,7 +1288,14 @@ Received outputs: "Number of output components does not match number " f"of values returned from from function {block_fn.name}" ) from err - block = self.blocks[output_id] + + try: + block = self.blocks[output_id] + except KeyError as e: + raise InvalidBlockError( + f"Output component with id {output_id} used in {dependency['trigger']}() event not found in this gr.Blocks context. You are allowed to nest gr.Blocks contexts, but there must be a gr.Blocks context that contains all components and events." + ) from e + if getattr(block, "stateful", False): if not utils.is_update(predictions[i]): state[output_id] = predictions[i] diff --git a/gradio/exceptions.py b/gradio/exceptions.py index 3dcf67e76e..903e505e3c 100644 --- a/gradio/exceptions.py +++ b/gradio/exceptions.py @@ -23,6 +23,12 @@ class ServerFailedToStartError(Exception): pass +class InvalidBlockError(ValueError): + """Raised when an event in a Blocks contains a reference to a Block that is not in the original Blocks""" + + pass + + InvalidApiName = InvalidApiNameError # backwards compatibility diff --git a/test/test_blocks.py b/test/test_blocks.py index 3490eb3d99..83b52b00dc 100644 --- a/test/test_blocks.py +++ b/test/test_blocks.py @@ -212,10 +212,25 @@ class TestBlocksMethods: @mock.patch("requests.post") def test_initiated_analytics(self, mock_post, monkeypatch): monkeypatch.setenv("GRADIO_ANALYTICS_ENABLED", "True") - with gr.Blocks(analytics_enabled=True): + with gr.Blocks(): pass mock_post.assert_called_once() + @mock.patch("requests.post") + def test_launch_analytics_does_not_error_with_invalid_blocks( + self, mock_post, monkeypatch + ): + monkeypatch.setenv("GRADIO_ANALYTICS_ENABLED", "True") + with gr.Blocks(): + t1 = gr.Textbox() + + with gr.Blocks() as demo: + t2 = gr.Textbox() + t2.change(lambda x: x, t2, t1) + + demo.launch(prevent_thread_lock=True) + mock_post.assert_called() + def test_show_error(self): with gr.Blocks() as demo: pass