From 936c7137a99ef59efdf75bae5dd27eea2ac1f577 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 8 Jul 2024 18:58:07 -0700 Subject: [PATCH] Documents auth in the guides, in the view API page, and also types the Blocks.config object (#8720) * auth docs * changes * add changeset * add changeset * add changeset * type * changes * snippets * import * add changeset * changes * fix typing --------- Co-authored-by: gradio-pr-bot --- .changeset/witty-pugs-beg.md | 7 +++ client/js/src/types.ts | 3 +- globals.d.ts | 3 +- gradio/blocks.py | 12 ++--- gradio/data_classes.py | 45 ++++++++++++++++++- gradio/external.py | 2 +- gradio/route_utils.py | 6 +-- gradio/routes.py | 2 + gradio/utils.py | 14 +++++- ..._getting-started-with-the-python-client.md | 14 ++++++ .../02_getting-started-with-the-js-client.md | 14 ++++++ js/app/src/Blocks.svelte | 2 + js/app/src/Index.svelte | 3 +- js/app/src/api_docs/ApiDocs.svelte | 11 ++++- js/app/src/api_docs/CodeSnippet.svelte | 6 +-- js/app/src/api_docs/RecordingSnippet.svelte | 7 +-- 16 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 .changeset/witty-pugs-beg.md diff --git a/.changeset/witty-pugs-beg.md b/.changeset/witty-pugs-beg.md new file mode 100644 index 0000000000..f678b2912d --- /dev/null +++ b/.changeset/witty-pugs-beg.md @@ -0,0 +1,7 @@ +--- +"@gradio/app": patch +"@gradio/client": patch +"gradio": patch +--- + +fix:Documents auth in the guides, in the view API page, and also types the Blocks.config object diff --git a/client/js/src/types.ts b/client/js/src/types.ts index 63df147090..0f0fe82324 100644 --- a/client/js/src/types.ts +++ b/client/js/src/types.ts @@ -153,7 +153,7 @@ export type SpaceStatusCallback = (a: SpaceStatus) => void; // Configuration and Response Types // -------------------------------- export interface Config { - auth_required: boolean; + auth_required?: true; analytics_enabled: boolean; connect_heartbeat: boolean; auth_message: string; @@ -181,6 +181,7 @@ export interface Config { protocol: "sse_v3" | "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws"; max_file_size?: number; theme_hash?: number; + username: string | null; } // todo: DRY up types diff --git a/globals.d.ts b/globals.d.ts index dcc2827fa6..e3370af913 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -16,7 +16,7 @@ declare global { } export interface Config { - auth_required: boolean; + auth_required?: true; auth_message: string; components: any[]; css: string | null; @@ -41,4 +41,5 @@ export interface Config { is_space: boolean; protocol: "ws" | "sse" | "sse_v1" | "sse_v2" | "sse_v2.1" | "sse_v3"; theme_hash?: number; + username: string | null; } diff --git a/gradio/blocks.py b/gradio/blocks.py index eca2015fbd..a79d3b83a5 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -47,7 +47,7 @@ from gradio.context import ( get_render_context, set_render_context, ) -from gradio.data_classes import FileData, GradioModel, GradioRootModel +from gradio.data_classes import BlocksConfigDict, FileData, GradioModel, GradioRootModel from gradio.events import ( EventData, EventListener, @@ -1024,7 +1024,7 @@ class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta): self.predict = None self.input_components = None self.output_components = None - self.__name__ = None + self.__name__ = None # type: ignore self.api_mode = None self.progress_tracking = None @@ -1082,7 +1082,7 @@ class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta): @classmethod def from_config( cls, - config: dict, + config: BlocksConfigDict, fns: list[Callable], proxy_url: str, ) -> Blocks: @@ -1978,8 +1978,8 @@ Received outputs: def get_config(self): return {"type": "column"} - def get_config_file(self): - config = { + def get_config_file(self) -> BlocksConfigDict: + config: BlocksConfigDict = { "version": routes.VERSION, "mode": self.mode, "app_id": self.app_id, @@ -2015,7 +2015,7 @@ Received outputs: "fill_height": self.fill_height, "theme_hash": self.theme_hash, } - config.update(self.default_config.get_config()) + config.update(self.default_config.get_config()) # type: ignore config["connect_heartbeat"] = utils.connect_heartbeat( config, self.blocks.values() ) diff --git a/gradio/data_classes.py b/gradio/data_classes.py index 2ec18bb800..7e3472dc03 100644 --- a/gradio/data_classes.py +++ b/gradio/data_classes.py @@ -8,10 +8,11 @@ import secrets import shutil from abc import ABC, abstractmethod from enum import Enum, auto -from typing import TYPE_CHECKING, Any, List, Optional, Tuple, TypedDict, Union +from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, TypedDict, Union from fastapi import Request from gradio_client.utils import traverse +from typing_extensions import NotRequired from . import wasm_utils @@ -288,3 +289,45 @@ class _StaticFiles: @classmethod def clear(cls): cls.all_paths = [] + + +class BodyCSS(TypedDict): + body_background_fill: str + body_text_color: str + body_background_fill_dark: str + body_text_color_dark: str + + +class Layout(TypedDict): + id: int + children: list[int | Layout] + + +class BlocksConfigDict(TypedDict): + version: str + mode: str + app_id: int + dev_mode: bool + analytics_enabled: bool + components: list[dict[str, Any]] + css: str | None + connect_heartbeat: bool + js: str | None + head: str | None + title: str + space_id: str | None + enable_queue: bool + show_error: bool + show_api: bool + is_colab: bool + max_file_size: int | None + stylesheets: list[str] + theme: str | None + protocol: Literal["ws", "sse", "sse_v1", "sse_v2", "sse_v2.1", "sse_v3"] + body_css: BodyCSS + fill_height: bool + theme_hash: str + layout: NotRequired[Layout] + dependencies: NotRequired[list[dict[str, Any]]] + root: NotRequired[str | None] + username: NotRequired[str | None] diff --git a/gradio/external.py b/gradio/external.py index 7234fcb248..88709ed5ef 100644 --- a/gradio/external.py +++ b/gradio/external.py @@ -464,7 +464,7 @@ def from_spaces_blocks(space: str, hf_token: str | None) -> Blocks: predict_fns.append(endpoint.make_end_to_end_fn(helper)) else: predict_fns.append(None) - return gradio.Blocks.from_config(client.config, predict_fns, client.src) + return gradio.Blocks.from_config(client.config, predict_fns, client.src) # type: ignore def from_spaces_interface( diff --git a/gradio/route_utils.py b/gradio/route_utils.py index 7c38d425e3..18e4f2684b 100644 --- a/gradio/route_utils.py +++ b/gradio/route_utils.py @@ -41,7 +41,7 @@ from starlette.responses import PlainTextResponse, Response from starlette.types import ASGIApp, Message, Receive, Scope, Send from gradio import processing_utils, utils -from gradio.data_classes import PredictBody +from gradio.data_classes import BlocksConfigDict, PredictBody from gradio.exceptions import Error from gradio.helpers import EventData from gradio.state_holder import SessionState @@ -640,7 +640,7 @@ def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> N shutil.move(file, dest) -def update_root_in_config(config: dict, root: str) -> dict: +def update_root_in_config(config: BlocksConfigDict, root: str) -> BlocksConfigDict: """ Updates the root "key" in the config dictionary to the new root url. If the root url has changed, all of the urls in the config that correspond to component @@ -649,7 +649,7 @@ def update_root_in_config(config: dict, root: str) -> dict: previous_root = config.get("root") if previous_root is None or previous_root != root: config["root"] = root - config = processing_utils.add_root_url(config, root, previous_root) + config = processing_utils.add_root_url(config, root, previous_root) # type: ignore return config diff --git a/gradio/routes.py b/gradio/routes.py index 872c5834ea..51f82bfcd8 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -384,6 +384,7 @@ class App(FastAPI): if (app.auth is None and app.auth_dependency is None) or user is not None: config = blocks.config config = route_utils.update_root_in_config(config, root) + config["username"] = user elif app.auth_dependency: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated" @@ -440,6 +441,7 @@ class App(FastAPI): request=request, route_path="/config", root_path=app.root_path ) config = route_utils.update_root_in_config(config, root) + config["username"] = get_current_user(request) return ORJSONResponse(content=config) @app.get("/static/{path:path}") diff --git a/gradio/utils.py b/gradio/utils.py index 3fb7a1272c..d31204893b 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -52,7 +52,7 @@ from typing_extensions import ParamSpec import gradio from gradio.context import get_blocks_context -from gradio.data_classes import FileData +from gradio.data_classes import BlocksConfigDict, FileData from gradio.exceptions import Error from gradio.strings import en @@ -1363,11 +1363,21 @@ def _parse_file_size(size: str | int | None) -> int | None: return multiple * size_int -def connect_heartbeat(config: dict[str, Any], blocks) -> bool: +def connect_heartbeat(config: BlocksConfigDict, blocks) -> bool: + """ + Determines whether a heartbeat is required for a given config. + """ from gradio.components import State any_state = any(isinstance(block, State) for block in blocks) any_unload = False + + if "dependencies" not in config: + raise ValueError( + "Dependencies not found in config. Cannot determine whether" + "heartbeat is required." + ) + for dep in config["dependencies"]: for target in dep["targets"]: if isinstance(target, (list, tuple)) and len(target) == 2: diff --git a/guides/08_gradio-clients-and-lite/01_getting-started-with-the-python-client.md b/guides/08_gradio-clients-and-lite/01_getting-started-with-the-python-client.md index 1790e290a2..232039a8ef 100644 --- a/guides/08_gradio-clients-and-lite/01_getting-started-with-the-python-client.md +++ b/guides/08_gradio-clients-and-lite/01_getting-started-with-the-python-client.md @@ -88,6 +88,20 @@ from gradio_client import Client client = Client("https://bec81a83-5b5c-471e.gradio.live") ``` +## Connecting to a Gradio app with auth + +If the Gradio application you are connecting to [requires a username and password](/guides/sharing-your-app#authentication), then provide them as a tuple to the `auth` argument of the `Client` class: + +```python +from gradio_client import Client + +Client( + space_name, + auth=[username, password] +) +``` + + ## Inspecting the API endpoints Once you have connected to a Gradio app, you can view the APIs that are available to you by calling the `Client.view_api()` method. For the Whisper Space, we see the following: diff --git a/guides/08_gradio-clients-and-lite/02_getting-started-with-the-js-client.md b/guides/08_gradio-clients-and-lite/02_getting-started-with-the-js-client.md index b3d004a0c9..f175ac7c66 100644 --- a/guides/08_gradio-clients-and-lite/02_getting-started-with-the-js-client.md +++ b/guides/08_gradio-clients-and-lite/02_getting-started-with-the-js-client.md @@ -111,6 +111,20 @@ import { Client } from "@gradio/client"; const app = Client.connect("https://bec81a83-5b5c-471e.gradio.live"); ``` +## Connecting to a Gradio app with auth + +If the Gradio application you are connecting to [requires a username and password](/guides/sharing-your-app#authentication), then provide them as a tuple to the `auth` argument of the `Client` class: + +```js +import { Client } from "@gradio/client"; + +Client.connect( + space_name, + { auth: [username, password] } +) +``` + + ## Inspecting the API endpoints Once you have connected to a Gradio app, you can view the APIs that are available to you by calling the `Client`'s `view_api` method. diff --git a/js/app/src/Blocks.svelte b/js/app/src/Blocks.svelte index 118ec67c4e..c757078be4 100644 --- a/js/app/src/Blocks.svelte +++ b/js/app/src/Blocks.svelte @@ -44,6 +44,7 @@ export let js: string | null; export let fill_height = false; export let ready: boolean; + export let username: string | null; const { layout: _layout, @@ -678,6 +679,7 @@ {app} {space_id} {api_calls} + {username} /> diff --git a/js/app/src/Index.svelte b/js/app/src/Index.svelte index db1163adbb..0df0d92fdd 100644 --- a/js/app/src/Index.svelte +++ b/js/app/src/Index.svelte @@ -7,7 +7,7 @@ declare let BUILD_MODE: string; interface Config { - auth_required: boolean | undefined; + auth_required?: true; auth_message: string; components: ComponentMeta[]; css: string | null; @@ -30,6 +30,7 @@ app_id?: string; fill_height?: boolean; theme_hash?: number; + username: string | null; } let id = -1; diff --git a/js/app/src/api_docs/ApiDocs.svelte b/js/app/src/api_docs/ApiDocs.svelte index 1099fa2cf8..81c3a69717 100644 --- a/js/app/src/api_docs/ApiDocs.svelte +++ b/js/app/src/api_docs/ApiDocs.svelte @@ -23,6 +23,8 @@ export let app: Awaited>; export let space_id: string | null; export let root_node: ComponentMeta; + export let username: string | null; + const js_docs = "https://www.gradio.app/guides/getting-started-with-the-js-client"; const py_docs = @@ -142,6 +144,7 @@ {dependencies} {root} short_root={space_id || root} + {username} />

Note: Some API calls only affect the UI, so when using the @@ -195,7 +198,7 @@

API Recorder

to automatically generate your API requests. - {#if current_language == "bash"}
 
Note: making a + {#if current_language == "bash"}
 
Making a prediction and getting a result requires 2 requests: a POST @@ -204,7 +207,10 @@ GET request to fetch the results. In these snippets, we've used awk and read to parse the results, combining these two requests into one command for ease of - use. See curl docs. + use. {#if username !== null} + Note: connecting to an authenticated app requires an additional + request.{/if} See + curl docs. {/if} {#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i} const client = await Client.connect("{space_id || root}"); + >{#if username !== null}, {auth: ["{username}", **password**]}{/if}); const result = await client.predict({#if named}"/{dependency.api_name}"{:else}{dependency_index}{/if}, { client.import { Client } from "@gradio/client"; const app = await Client.connect("{short_root}"); + >{#if username !== null}, {auth: ["{username}", **password**]}{/if}); {#each js_zipped as { call, api_name }} await client.predict( "/{api_name}"{#if call},{/if}{call}); + >{#if call}, {/if}{call}); {/each}