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 <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
Abubakar Abid 2024-07-08 18:58:07 -07:00 committed by GitHub
parent 012da05287
commit 936c7137a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 127 additions and 24 deletions

View File

@ -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

View File

@ -153,7 +153,7 @@ export type SpaceStatusCallback = (a: SpaceStatus) => void;
// Configuration and Response Types // Configuration and Response Types
// -------------------------------- // --------------------------------
export interface Config { export interface Config {
auth_required: boolean; auth_required?: true;
analytics_enabled: boolean; analytics_enabled: boolean;
connect_heartbeat: boolean; connect_heartbeat: boolean;
auth_message: string; auth_message: string;
@ -181,6 +181,7 @@ export interface Config {
protocol: "sse_v3" | "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws"; protocol: "sse_v3" | "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
max_file_size?: number; max_file_size?: number;
theme_hash?: number; theme_hash?: number;
username: string | null;
} }
// todo: DRY up types // todo: DRY up types

3
globals.d.ts vendored
View File

@ -16,7 +16,7 @@ declare global {
} }
export interface Config { export interface Config {
auth_required: boolean; auth_required?: true;
auth_message: string; auth_message: string;
components: any[]; components: any[];
css: string | null; css: string | null;
@ -41,4 +41,5 @@ export interface Config {
is_space: boolean; is_space: boolean;
protocol: "ws" | "sse" | "sse_v1" | "sse_v2" | "sse_v2.1" | "sse_v3"; protocol: "ws" | "sse" | "sse_v1" | "sse_v2" | "sse_v2.1" | "sse_v3";
theme_hash?: number; theme_hash?: number;
username: string | null;
} }

View File

@ -47,7 +47,7 @@ from gradio.context import (
get_render_context, get_render_context,
set_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 ( from gradio.events import (
EventData, EventData,
EventListener, EventListener,
@ -1024,7 +1024,7 @@ class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta):
self.predict = None self.predict = None
self.input_components = None self.input_components = None
self.output_components = None self.output_components = None
self.__name__ = None self.__name__ = None # type: ignore
self.api_mode = None self.api_mode = None
self.progress_tracking = None self.progress_tracking = None
@ -1082,7 +1082,7 @@ class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta):
@classmethod @classmethod
def from_config( def from_config(
cls, cls,
config: dict, config: BlocksConfigDict,
fns: list[Callable], fns: list[Callable],
proxy_url: str, proxy_url: str,
) -> Blocks: ) -> Blocks:
@ -1978,8 +1978,8 @@ Received outputs:
def get_config(self): def get_config(self):
return {"type": "column"} return {"type": "column"}
def get_config_file(self): def get_config_file(self) -> BlocksConfigDict:
config = { config: BlocksConfigDict = {
"version": routes.VERSION, "version": routes.VERSION,
"mode": self.mode, "mode": self.mode,
"app_id": self.app_id, "app_id": self.app_id,
@ -2015,7 +2015,7 @@ Received outputs:
"fill_height": self.fill_height, "fill_height": self.fill_height,
"theme_hash": self.theme_hash, "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["connect_heartbeat"] = utils.connect_heartbeat(
config, self.blocks.values() config, self.blocks.values()
) )

View File

@ -8,10 +8,11 @@ import secrets
import shutil import shutil
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum, auto 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 fastapi import Request
from gradio_client.utils import traverse from gradio_client.utils import traverse
from typing_extensions import NotRequired
from . import wasm_utils from . import wasm_utils
@ -288,3 +289,45 @@ class _StaticFiles:
@classmethod @classmethod
def clear(cls): def clear(cls):
cls.all_paths = [] 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]

View File

@ -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)) predict_fns.append(endpoint.make_end_to_end_fn(helper))
else: else:
predict_fns.append(None) 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( def from_spaces_interface(

View File

@ -41,7 +41,7 @@ from starlette.responses import PlainTextResponse, Response
from starlette.types import ASGIApp, Message, Receive, Scope, Send from starlette.types import ASGIApp, Message, Receive, Scope, Send
from gradio import processing_utils, utils 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.exceptions import Error
from gradio.helpers import EventData from gradio.helpers import EventData
from gradio.state_holder import SessionState 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) 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 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 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") previous_root = config.get("root")
if previous_root is None or previous_root != root: if previous_root is None or previous_root != root:
config["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 return config

View File

@ -384,6 +384,7 @@ class App(FastAPI):
if (app.auth is None and app.auth_dependency is None) or user is not None: if (app.auth is None and app.auth_dependency is None) or user is not None:
config = blocks.config config = blocks.config
config = route_utils.update_root_in_config(config, root) config = route_utils.update_root_in_config(config, root)
config["username"] = user
elif app.auth_dependency: elif app.auth_dependency:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated" 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 request=request, route_path="/config", root_path=app.root_path
) )
config = route_utils.update_root_in_config(config, root) config = route_utils.update_root_in_config(config, root)
config["username"] = get_current_user(request)
return ORJSONResponse(content=config) return ORJSONResponse(content=config)
@app.get("/static/{path:path}") @app.get("/static/{path:path}")

View File

@ -52,7 +52,7 @@ from typing_extensions import ParamSpec
import gradio import gradio
from gradio.context import get_blocks_context 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.exceptions import Error
from gradio.strings import en from gradio.strings import en
@ -1363,11 +1363,21 @@ def _parse_file_size(size: str | int | None) -> int | None:
return multiple * size_int 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 from gradio.components import State
any_state = any(isinstance(block, State) for block in blocks) any_state = any(isinstance(block, State) for block in blocks)
any_unload = False 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 dep in config["dependencies"]:
for target in dep["targets"]: for target in dep["targets"]:
if isinstance(target, (list, tuple)) and len(target) == 2: if isinstance(target, (list, tuple)) and len(target) == 2:

View File

@ -88,6 +88,20 @@ from gradio_client import Client
client = Client("https://bec81a83-5b5c-471e.gradio.live") 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 ## 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: 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:

View File

@ -111,6 +111,20 @@ import { Client } from "@gradio/client";
const app = Client.connect("https://bec81a83-5b5c-471e.gradio.live"); 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 ## 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. 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.

View File

@ -44,6 +44,7 @@
export let js: string | null; export let js: string | null;
export let fill_height = false; export let fill_height = false;
export let ready: boolean; export let ready: boolean;
export let username: string | null;
const { const {
layout: _layout, layout: _layout,
@ -678,6 +679,7 @@
{app} {app}
{space_id} {space_id}
{api_calls} {api_calls}
{username}
/> />
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@
declare let BUILD_MODE: string; declare let BUILD_MODE: string;
interface Config { interface Config {
auth_required: boolean | undefined; auth_required?: true;
auth_message: string; auth_message: string;
components: ComponentMeta[]; components: ComponentMeta[];
css: string | null; css: string | null;
@ -30,6 +30,7 @@
app_id?: string; app_id?: string;
fill_height?: boolean; fill_height?: boolean;
theme_hash?: number; theme_hash?: number;
username: string | null;
} }
let id = -1; let id = -1;

View File

@ -23,6 +23,8 @@
export let app: Awaited<ReturnType<typeof Client.connect>>; export let app: Awaited<ReturnType<typeof Client.connect>>;
export let space_id: string | null; export let space_id: string | null;
export let root_node: ComponentMeta; export let root_node: ComponentMeta;
export let username: string | null;
const js_docs = const js_docs =
"https://www.gradio.app/guides/getting-started-with-the-js-client"; "https://www.gradio.app/guides/getting-started-with-the-js-client";
const py_docs = const py_docs =
@ -142,6 +144,7 @@
{dependencies} {dependencies}
{root} {root}
short_root={space_id || root} short_root={space_id || root}
{username}
/> />
<p> <p>
Note: Some API calls only affect the UI, so when using the Note: Some API calls only affect the UI, so when using the
@ -195,7 +198,7 @@
<p class="self-baseline">API Recorder</p> <p class="self-baseline">API Recorder</p>
</Button> </Button>
to automatically generate your API requests. to automatically generate your API requests.
{#if current_language == "bash"}<br />&nbsp;<br />Note: making a {#if current_language == "bash"}<br />&nbsp;<br />Making a
prediction and getting a result requires prediction and getting a result requires
<strong>2 requests</strong>: a <strong>2 requests</strong>: a
<code>POST</code> <code>POST</code>
@ -204,7 +207,10 @@
<code>GET</code> request to fetch the results. In these snippets, <code>GET</code> request to fetch the results. In these snippets,
we've used <code>awk</code> and <code>read</code> to parse the we've used <code>awk</code> and <code>read</code> to parse the
results, combining these two requests into one command for ease of results, combining these two requests into one command for ease of
use. See <a href={bash_docs} target="_blank">curl docs</a>. use. {#if username !== null}
Note: connecting to an authenticated app requires an additional
request.{/if} See
<a href={bash_docs} target="_blank">curl docs</a>.
{/if} {/if}
<!-- <span <!-- <span
@ -228,6 +234,7 @@
{current_language} {current_language}
{root} {root}
{space_id} {space_id}
{username}
/> />
<ParametersSnippet <ParametersSnippet

View File

@ -20,7 +20,7 @@
export let space_id: string | null; export let space_id: string | null;
export let endpoint_parameters: any; export let endpoint_parameters: any;
export let named: boolean; export let named: boolean;
export let username: string | null;
export let current_language: "python" | "javascript" | "bash"; export let current_language: "python" | "javascript" | "bash";
let python_code: HTMLElement; let python_code: HTMLElement;
@ -54,7 +54,7 @@
class="highlight">import</span class="highlight">import</span
> Client{#if has_file_path}, handle_file{/if} > Client{#if has_file_path}, handle_file{/if}
client = Client(<span class="token string">"{space_id || root}"</span>) client = Client(<span class="token string">"{space_id || root}"</span>{#if username !== null}, auth=("{username}", **password**){/if})
result = client.<span class="highlight">predict</span result = client.<span class="highlight">predict</span
>(<!-- >(<!--
-->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!-- -->{#each endpoint_parameters as { python_type, example_input, parameter_name, parameter_has_default, parameter_default }, i}<!--
@ -93,7 +93,7 @@ const example{component} = await response_{i}.blob();
--> -->
const client = await Client.connect(<span class="token string" const client = await Client.connect(<span class="token string"
>"{space_id || root}"</span >"{space_id || root}"</span
>); >{#if username !== null}, &lbrace;auth: ["{username}", **password**]&rbrace;{/if});
const result = await client.predict({#if named}<span class="api-name" const result = await client.predict({#if named}<span class="api-name"
>"/{dependency.api_name}"</span >"/{dependency.api_name}"</span
>{:else}{dependency_index}{/if}, &lbrace; <!-- >{:else}{dependency_index}{/if}, &lbrace; <!--

View File

@ -9,6 +9,7 @@
export let short_root: string; export let short_root: string;
export let root: string; export let root: string;
export let current_language: "python" | "javascript" | "bash"; export let current_language: "python" | "javascript" | "bash";
export let username: string | null;
let python_code: HTMLElement; let python_code: HTMLElement;
let python_code_text: string; let python_code_text: string;
@ -133,7 +134,7 @@
class="highlight">import</span class="highlight">import</span
> Client, file > Client, file
client = Client(<span class="token string">"{short_root}"</span>) client = Client(<span class="token string">"{short_root}"</span>{#if username !== null}, auth=("{username}", **password**){/if})
{#each py_zipped as { call, api_name }}<!-- {#each py_zipped as { call, api_name }}<!--
--> -->
client.<span class="highlight" client.<span class="highlight"
@ -152,12 +153,12 @@ client.<span class="highlight"
<pre>import &lbrace; Client &rbrace; from "@gradio/client"; <pre>import &lbrace; Client &rbrace; from "@gradio/client";
const app = await Client.connect(<span class="token string">"{short_root}"</span const app = await Client.connect(<span class="token string">"{short_root}"</span
>); >{#if username !== null}, &lbrace;auth: ["{username}", **password**]&rbrace;{/if});
{#each js_zipped as { call, api_name }}<!-- {#each js_zipped as { call, api_name }}<!--
--> -->
await client.predict(<span class="api-name"> await client.predict(<span class="api-name">
"/{api_name}"</span "/{api_name}"</span
>{#if call},{/if}{call}); >{#if call}, {/if}{call});
{/each}</pre> {/each}</pre>
</div> </div>
</code> </code>