diff --git a/.changeset/true-candles-pay.md b/.changeset/true-candles-pay.md new file mode 100644 index 000000000000..052838f27fe3 --- /dev/null +++ b/.changeset/true-candles-pay.md @@ -0,0 +1,5 @@ +--- +"gradio": patch +--- + +feat:Fix for deepcopy errors when running the replica-related logic on Spaces diff --git a/gradio/route_utils.py b/gradio/route_utils.py index 1b3d846edc3f..0d357cafc3fc 100644 --- a/gradio/route_utils.py +++ b/gradio/route_utils.py @@ -1,6 +1,5 @@ from __future__ import annotations -import copy import json from typing import TYPE_CHECKING, Optional, Union @@ -248,11 +247,18 @@ async def call_process_api( return output -def set_replica_url_in_config(config: dict, replica_url: str) -> dict: +def set_replica_url_in_config( + config: dict, replica_url: str, all_replica_urls: set[str] +) -> None: """ If the Gradio app is running on Hugging Face Spaces and the machine has multiple replicas, we pass in the direct URL to the replica so that we have the fully resolved path to any files on that machine. This direct URL can be shared with other users and the path will still work. + + Parameters: + config: The config dictionary to modify. + replica_url: The direct URL to the replica. + all_replica_urls: The direct URLs to the other replicas. These should be replaced with the replica_url. """ parsed_url = httpx.URL(replica_url) stripped_url = parsed_url.copy_with(query=None) @@ -260,11 +266,9 @@ def set_replica_url_in_config(config: dict, replica_url: str) -> dict: if not stripped_url.endswith("/"): stripped_url += "/" - config_ = copy.deepcopy(config) - for component in config_["components"]: - if ( - component.get("props") is not None - and component["props"].get("root_url") is None - ): - component["props"]["root_url"] = stripped_url - return config_ + for component in config["components"]: + if component.get("props") is not None: + root_url = component["props"].get("root_url") + # Don't replace the root_url if it's loaded from a different Space + if root_url is None or root_url in all_replica_urls: + component["props"]["root_url"] = stripped_url diff --git a/gradio/routes.py b/gradio/routes.py index 1035c3fe4a2d..9f449e6408e6 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -303,7 +303,7 @@ def login(form_data: OAuth2PasswordRequestForm = Depends()): @app.head("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse) - def main(request: fastapi.Request, user: str = Depends(get_current_user)): + async def main(request: fastapi.Request, user: str = Depends(get_current_user)): mimetypes.add_type("application/javascript", ".js") blocks = app.get_blocks() root_path = request.scope.get("root_path", "") @@ -317,7 +317,8 @@ def main(request: fastapi.Request, user: str = Depends(get_current_user)): replica_url = request.headers.get("X-Direct-Url") if utils.get_space() and replica_url: app.replica_urls.add(replica_url) - config = set_replica_url_in_config(config, replica_url) + async with app.lock: + set_replica_url_in_config(config, replica_url, app.replica_urls) else: config = { "auth_required": True, @@ -354,7 +355,7 @@ def api_info(serialize: bool = True): @app.get("/config/", dependencies=[Depends(login_check)]) @app.get("/config", dependencies=[Depends(login_check)]) - def get_config(request: fastapi.Request): + async def get_config(request: fastapi.Request): config = app.get_blocks().config # Handles the case where the app is running on Hugging Face Spaces with @@ -362,7 +363,8 @@ def get_config(request: fastapi.Request): replica_url = request.headers.get("X-Direct-Url") if utils.get_space() and replica_url: app.replica_urls.add(replica_url) - config = set_replica_url_in_config(config, replica_url) + async with app.lock: + set_replica_url_in_config(config, replica_url, app.replica_urls) root_path = request.scope.get("root_path", "") config["root"] = root_path diff --git a/test/test_route_utils.py b/test/test_route_utils.py index 66ebd751ba0b..a273c7d063d6 100644 --- a/test/test_route_utils.py +++ b/test/test_route_utils.py @@ -3,24 +3,33 @@ def test_set_replica_url(): config = { - "components": [{"props": {}}, {"props": {"root_url": "existing_url/"}}, {}] + "components": [ + {"props": {}}, + {"props": {"root_url": "existing_url/"}}, + {"props": {"root_url": "different_url/"}}, + {}, + ] } replica_url = "https://abidlabs-test-client-replica--fttzk.hf.space?__theme=light" - config = set_replica_url_in_config(config, replica_url) + set_replica_url_in_config(config, replica_url, {"existing_url/"}) assert ( config["components"][0]["props"]["root_url"] == "https://abidlabs-test-client-replica--fttzk.hf.space/" ) - assert config["components"][1]["props"]["root_url"] == "existing_url/" - assert "props" not in config["components"][2] + assert ( + config["components"][1]["props"]["root_url"] + == "https://abidlabs-test-client-replica--fttzk.hf.space/" + ) + assert config["components"][2]["props"]["root_url"] == "different_url/" + assert "props" not in config["components"][3] def test_url_without_trailing_slash(): config = {"components": [{"props": {}}]} replica_url = "https://abidlabs-test-client-replica--fttzk.hf.space" - config = set_replica_url_in_config(config, replica_url) + set_replica_url_in_config(config, replica_url, set()) assert ( config["components"][0]["props"]["root_url"] == "https://abidlabs-test-client-replica--fttzk.hf.space/"