Repository: gradio-app/gradio
Pull request: Fix for analytics error
So this is a weird issue. It turns out that if you define two Blocks and reference components in one Blocks from the events in the other Blocks, our analytics errors out. 

Here's an example (note that this is invalid Gradio):

```py
import gradio as gr

with gr.Blocks() as demo1:
    t1 = gr.Textbox()

with gr.Blocks() as demo2:
    t2 = gr.Textbox()
    t2.change(lambda x:x, t2, t1)

demo2.launch()
```

Although this is invalid set of Blocks, this might be a WIP state. Anyways, the analytics error is not illuminating at all and prevents the app from launching, so I added a new `InvalidBlockError` with some diagnostic info that only gets triggered if someone tries to use the invalid event trigger. It also suggests at the fix, which is to wrap both Blocks inside a parent Blocks -- this is valid Gradio:

```py
import gradio as gr

with gr.Blocks() as demo:
    with gr.Blocks() as demo1:
        t1 = gr.Textbox()

    with gr.Blocks() as demo2:
        t2 = gr.Textbox()
        t2.change(lambda x:x, t2, t1)

demo.launch()
```

Closes: #4719
Closes: #4642

Problem statement:
KeyError: 13 in analytics.py
### Describe the bug

I encountered a KeyError: 13 error while using the Gradio library. The error occurred in the analytics.py file on line 126. Here is the full error message:

File "C:\Users\●●●\AppData\Local\Programs\Python\Python310\lib\site-packages\gradio\analytics.py", line 126, in <listcomp>
    str(blocks.blocks[y]) for y in x["outputs"]
KeyError: 13

### Have you searched existing issues?  🔎

- [X] I have searched and found no existing issues

### Reproduction

import gradio as gr
import whisper
import os
from pathlib import Path
import torch


# 毎回 whisper model をダウンロードすると時間かかるので
# ２回目以降で使いまわせるようにハンドリング
def load_whisper_model(model_name):
    model_dir = Path("whisper_models")
    model_path = model_dir / f"{model_name}.pt"

    if not model_path.exists():
        os.makedirs(model_dir, exist_ok=True)
        model = whisper.load_model(model_name)
        torch.save(model, model_path)
    else:
        model = torch.load(model_path)

    return model


model = load_whisper_model("medium")


# テキストを生成し、指定フォーマットに整形して出力する関数
def create_textfile(video_name, sr, timelag) -> object:
    # 動画からテキスト抽出結果を得る。
    results = model.transcribe(video_name, verbose=False, language="ja")

    # 出力用の文字列を初期化する。
    output_str = ""

    for index, _dict in enumerate(results["segments"]):
        start_time = _dict["start"]
        end_time = _dict["end"]

        # タイムラグ分だけシフトさせる。
        start_time += timelag
        end_time += timelag

        # 秒単位の開始時刻・終了時刻を計算する。
        s_h, e_h = int(start_time // (60 * 60)), int(end_time // (60 * 60))
        s_m, e_m = int(start_time // 60) % 60, int(end_time // 60) % 60
        s_s, e_s = start_time % 60, end_time % 60

        # インデックスとタイムコード、テキストを出力用文字列に追加する。
        output_str += f"{index + 1}\n{s_h:02d}:{s_m:02d}:{s_s:06.3f} --> {e_h:02d}:{e_m:02d}:{e_s:06.3f}\n{_dict['text']}。"

        # 句点がある場合は改行して次の行へ移動する。
        if "。" in _dict["text"]:
            output_str += "\n\n"

    # テキストファイルに出力する。
    with open("transcribe.docx", mode="w") as f:
        f.write(output_str)

    return "transcribe.docx"


# web の UI
css = """
.gradio-container {
	font-family : 'IBM Plex Sans', sans-serif;
}

.container {
	max-width :730px;
	margin:auto;
	padding-top :1.5rem;
}

#gallery {
	min-height                :22rem;
	margin-bottom             :15px;
	margin-left               :auto; 
	margin-right              :auto; 
	border-bottom-right-radius:.5rem !important ;
	border-bottom-left-radius :.5rem !important ;
}
#gallery>div>.h-full {
	min-height:20rem
}
"""

block = gr.Blocks(css=css)

with block:
    gr.Markdown("subtitle generator")
    with gr.Group():
        with gr.Box():
            with gr.Row(mobile_collapse=False, equal_height=True):
                # テキスト出力フォーマットを指定するためのオプション
                format_options = ["Microsoft Word (docx)"]

                segment_length_slider = gr.Slider(
                    minimum=0.1,
                    maximum=2,
                    label="segment length ratio",
                    value=1)

                timelag_slider = gr.Number(value=0, label="time lag (second)")

                text_button = gr.Button("transcribe").style(
                    margin=False,
                    rounded=(True, True, True, True),
                    background_color="#ff2e63",
                    color="white")

        video = gr.Audio(source="upload", type="filepath", interactive=True)

        transcribe_options_box = gr.Box()

        # 出力用フォーマットを選択するコンポーネント
    transcribe_format_radio = gr.Radio(format_options,
                                       label="Select the output format of "
                                             "transcripts text file.",
                                       default=format_options[0], group_id=None)


    # 出力ボタンを押したときに実行される関数。
    def transcribe_func(video_name, sr, timelag, output_format):
        if output_format == "Microsoft Word (docx)":
            create_textfile(video_name, sr, timelag)

# コンポーネントをブロックに追加する。
transcribe_options_box.add(transcribe_format_radio)

# 入出力データの設定
inputs = [video, segment_length_slider, timelag_slider]

outputs = [gr.outputs.File(label='Transcripts text file')]

# 出力ボタン
with block:
    # ...
    text_button.click(transcribe_func,
                      inputs=inputs + [transcribe_format_radio],
                      outputs=outputs)

block.queue(default_enabled=False).launch(debug=True)


### Screenshot

_No response_

### Logs

_No response_

### System Info

```shell
gradio ライブラリの最新バージョンは、3.35.2 です
```


### Severity

Blocking usage of gradio

Telemetry appears to break the app
### Describe the bug

---> 20 demo2.launch(
     21     enable_queue=True,
     22 )

File [c:\Users\admin\Desktop\one-click-installers-tts-main\installer_files\env\lib\site-packages\gradio\blocks.py:1964](file:///C:/Users/admin/Desktop/one-click-installers-tts-main/installer_files/env/lib/site-packages/gradio/blocks.py:1964), in Blocks.launch(self, inline, inbrowser, share, debug, enable_queue, max_threads, auth, auth_message, prevent_thread_lock, show_error, server_name, server_port, show_tips, height, width, encrypt, favicon_path, ssl_keyfile, ssl_certfile, ssl_keyfile_password, ssl_verify, quiet, show_api, file_directories, allowed_paths, blocked_paths, root_path, _frontend, app_kwargs)
   1951 if getattr(self, "analytics_enabled", False):
   1952     data = {
   1953         "launch_method": "browser" if inbrowser else "inline",
   1954         "is_google_colab": self.is_colab,
   (...)
   1962         "mode": self.mode,
   1963     }
-> 1964     analytics.launched_analytics(self, data)
   1966 utils.show_tip(self)
   1968 # Block main thread if debug==True

File [c:\Users\admin\Desktop\one-click-installers-tts-main\installer_files\env\lib\site-packages\gradio\analytics.py:122](file:///C:/Users/admin/Desktop/one-click-installers-tts-main/installer_files/env/lib/site-packages/gradio/analytics.py:122), in launched_analytics(blocks, data)
    115 for x in blocks.dependencies:
    116     targets_telemetry = targets_telemetry + [
    117         # Sometimes the target can be the Blocks object itself, so we need to check if its in blocks.blocks
    118         str(blocks.blocks[y])
    119         for y in x["targets"]
    120         if y in blocks.blocks
    121     ]
--> 122     inputs_telemetry = inputs_telemetry + [
    123         str(blocks.blocks[y]) for y in x["inputs"]
    124     ]
    125     outputs_telemetry = outputs_telemetry + [
    126         str(blocks.blocks[y]) for y in x["outputs"]
    127     ]
    128 additional_data = {
    129     "version": GRADIO_VERSION,
    130     "is_kaggle": blocks.is_kaggle,
   (...)
    145     "events": [str(x["trigger"]) for x in blocks.dependencies],
    146 }

File [c:\Users\admin\Desktop\one-click-installers-tts-main\installer_files\env\lib\site-packages\gradio\analytics.py:123](file:///C:/Users/admin/Desktop/one-click-installers-tts-main/installer_files/env/lib/site-packages/gradio/analytics.py:123), in (.0)
    115 for x in blocks.dependencies:
    116     targets_telemetry = targets_telemetry + [
    117         # Sometimes the target can be the Blocks object itself, so we need to check if its in blocks.blocks
    118         str(blocks.blocks[y])
    119         for y in x["targets"]
    120         if y in blocks.blocks
    121     ]
    122     inputs_telemetry = inputs_telemetry + [
--> 123         str(blocks.blocks[y]) for y in x["inputs"]
    124     ]
    125     outputs_telemetry = outputs_telemetry + [
    126         str(blocks.blocks[y]) for y in x["outputs"]
    127     ]
    128 additional_data = {
    129     "version": GRADIO_VERSION,
    130     "is_kaggle": blocks.is_kaggle,
   (...)
    145     "events": [str(x["trigger"]) for x in blocks.dependencies],
    146 }

KeyError: 1

### Have you searched existing issues?  🔎

- [X] I have searched and found no existing issues

### Reproduction

It's part of a larger codebase. I cannot provide a minimal example.

### Screenshot

_No response_

### Logs

_No response_

### System Info

```shell
Name: gradio
Version: 3.35.2
Summary: Python library for easily interacting with trained machine learning models
Home-page:
Author:
Author-email: Abubakar Abid <team@gradio.app>, Ali Abid <team@gradio.app>, Ali Abdalla <team@gradio.app>, Dawood Khan <team@gradio.app>, Ahsen Khaliq <team@gradio.app>, Pete Allen <team@gradio.app>, Ömer Faruk Özdemir <team@gradio.app>
License:
Location: c:\users\admin\desktop\one-click-installers-tts-main\installer_files\env\lib\site-packages
Requires: aiofiles, aiohttp, altair, fastapi, ffmpy, gradio-client, httpx, huggingface-hub, jinja2, markdown-it-py, markupsafe, matplotlib, 
mdit-py-plugins, numpy, orjson, pandas, pillow, pydantic, pydub, pygments, python-multipart, pyyaml, requests, semantic-version, uvicorn, websockets
Required-by:
```


### Severity

I can work around it

Hints from issue discussion:
I get the same error the only difference being key error : 64
Hi @dachi920 or @arian81 can you provide a well-formatted and self-contained code example we can use to reproduce this issue? 
well the issue is resolved after I restarted my jupyter kerne. I don't have specific way of reproducing the bug yet. The problem being in the analytics module doesn't help much with finding what the issue is. 
the code takes a really long time to establish connection to the gradio servers to provision a new instance so there might be an issue with your backend ?
> Hi @dachi920 or @arian81 can you provide a well-formatted and self-contained code example we can use to reproduce this issue?

ok now i'm getting the error and the number has increased. Could it be because I have created too many share instances? 
Hi, @abidlabs @arian81  @dachi920 
I guess I know why.
Check here: https://colab.research.google.com/drive/1xi6oVNAuzqbU7tB44UMLe-297mGD46fP?usp=sharing
![2MI{C$R}WAOU8MIZ~XA00PC](https://github.com/gradio-app/gradio/assets/126865849/5720cd59-a966-48f6-b43d-6207298f00ff)

---

![0U 96P56H$AE3N64I F HJ4](https://github.com/gradio-app/gradio/assets/126865849/2e54df69-943d-4f36-b6ea-106aa56305aa)

Workaround - gr.Blocks(... analytics_enabled=False ...)
Does this happen every time you launch the demo @rsxdalv ?
I have a jupyter notebook that I use, and there it happens 100% of the time, although when I run it in my project it works.
I tried a couple of minimal examples but they all pass, only the integration fails for now. The code I'm working with is as such:

```python
import gradio as gr
from src.history_tab.main import history_tab

if __name__ == "__main__":
    with gr.Blocks() as demo:
        history_tab(
            register_use_as_history_button=lambda x, y: None,
        )
        
    demo.launch()
```
We're seeing this error quite a bit, I think we can make the analytics code more fault tolerant, but if you have an example (even if it spans multiple files), that'll be helpful @rsxdalv 
Related issue: https://github.com/gradio-app/gradio/issues/4642

I'll take a look into this

Commit message: format

Patch to review:
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..3df7d55fd5 100644
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -36,7 +36,11 @@
 )
 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 @@ def serialize_data(self, fn_index: int, inputs: list[Any]) -> list[Any]:
         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 @@ def deserialize_data(self, fn_index: int, outputs: list[Any]) -> list[Any]:
         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 @@ def preprocess_data(self, fn_index: int, inputs: list[Any], state: dict[int, Any
         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 @@ def postprocess_data(
                     "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..7fe730ae57 100644
--- a/gradio/exceptions.py
+++ b/gradio/exceptions.py
@@ -23,6 +23,12 @@ class ServerFailedToStartError(Exception):
     pass
 
 
+class InvalidBlockError(ValueError):
+    """Raised when an en 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..83fe5b6724 100644
--- a/test/test_blocks.py
+++ b/test/test_blocks.py
@@ -216,6 +216,17 @@ def test_initiated_analytics(self, mock_post, monkeypatch):
             pass
         mock_post.assert_called_once()
 
+    def test_launch_analytics_does_not_error_with_invalid_blocks(self, 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)
+
     def test_show_error(self):
         with gr.Blocks() as demo:
             pass


Code review comments:
diff_hunk: @@ -216,6 +216,17 @@ def test_initiated_analytics(self, mock_post, monkeypatch):
             pass
         mock_post.assert_called_once()
 
+    def test_launch_analytics_does_not_error_with_invalid_blocks(self, monkeypatch):
+        monkeypatch.setenv("GRADIO_ANALYTICS_ENABLED", "True")
path: test/test_blocks.py
text: @user1:
You may want to monkeypatch the `_do_analytics_request` function to be a no-op though so this doesn't actually send spurious analytics from every CI run.

@author:
Thanks you're right

diff_hunk: @@ -23,6 +23,12 @@ class ServerFailedToStartError(Exception):
     pass
 
 
+class InvalidBlockError(ValueError):
+    """Raised when an en event in a Blocks contains a reference to a  Block that is not in the original Blocks"""
path: gradio/exceptions.py
text: @user1:
```suggestion
    """Raised when an event in a Blocks contains a reference to a Block that is not in the original Blocks"""
```