Refactors get_fetchable_url_or_file() to remove it from the frontend (#7528)

* fetch

* add changeset

* add changeset

* simplify

* add changeset

* changes

* format

* add changeset

* changes

* format

* fixes

* lint

* fix components

* fixes

* typing

* format frontend

* path

* fixes

* fix stories

* lint

* lint

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
Abubakar Abid 2024-02-26 13:01:00 -08:00 committed by GitHub
parent 8c9c98a741
commit eda33b3763
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 106 additions and 162 deletions

View File

@ -0,0 +1,15 @@
---
"@gradio/button": patch
"@gradio/chatbot": patch
"@gradio/client": patch
"@gradio/dataframe": patch
"@gradio/dataset": patch
"@gradio/image": patch
"@gradio/imageeditor": patch
"@gradio/simpleimage": patch
"@gradio/uploadbutton": patch
"@gradio/video": patch
"gradio": patch
---
feat:Refactors `get_fetchable_url_or_file()` to remove it from the frontend

View File

@ -6,9 +6,4 @@ export {
api_factory
} from "./client.js";
export type { SpaceStatus } from "./types.js";
export {
FileData,
upload,
get_fetchable_url_or_file,
prepare_files
} from "./upload.js";
export { FileData, upload, prepare_files } from "./upload.js";

View File

@ -9,22 +9,6 @@ function is_url(str: string): boolean {
}
}
export function get_fetchable_url_or_file(
path: string | null,
server_url: string,
proxy_url: string | null
): string {
if (path == null) {
return proxy_url ? `/proxy=${proxy_url}file=` : `${server_url}/file=`;
}
if (is_url(path)) {
return path;
}
return proxy_url
? `/proxy=${proxy_url}file=${path}`
: `${server_url}/file=${path}`;
}
export async function upload(
file_data: FileData[],
root: string,

View File

@ -234,6 +234,9 @@ class Block:
"""Moves a file or downloads a file from a url to a block's cache directory, adds
to to the block's temp_files, and returns the path to the file in cache. This
ensures that the file is accessible to the Block and can be served to users.
Note: this method is not used in any core Gradio components, but is kept here
for backwards compatibility with custom components created with gradio<=4.20.0.
"""
if url_or_file_path is None:
return None
@ -258,6 +261,27 @@ class Block:
return temp_file_path
def serve_static_file(self, url_or_file_path: str | Path | None) -> dict | None:
"""If a file is a local file, moves it to the block's cache directory and returns
a FileData-type dictionary corresponding to the file. If the file is a URL, returns a
FileData-type dictionary corresponding to the URL. This ensures that the file is
accessible in the frontend and can be served to users.
Examples:
>>> block.serve_static_file("https://gradio.app/logo.png") -> {"path": "https://gradio.app/logo.png", "url": "https://gradio.app/logo.png"}
>>> block.serve_static_file("logo.png") -> {"path": "logo.png", "url": "/file=logo.png"}
"""
if url_or_file_path is None:
return None
if isinstance(url_or_file_path, Path):
url_or_file_path = str(url_or_file_path)
if client_utils.is_http_url_like(url_or_file_path):
return FileData(path=url_or_file_path, url=url_or_file_path).model_dump()
else:
data = {"path": url_or_file_path}
return processing_utils.move_files_to_cache(data, self)
class BlockContext(Block):
def __init__(
@ -1328,7 +1352,6 @@ Received inputs:
inputs_cached = processing_utils.move_files_to_cache(
inputs[i],
block,
add_urls=True,
check_in_upload_folder=not explicit_call,
)
if getattr(block, "data_model", None) and inputs_cached is not None:
@ -1459,9 +1482,8 @@ Received outputs:
outputs_cached = processing_utils.move_files_to_cache(
prediction_value,
block, # type: ignore
block,
postprocess=True,
add_urls=True,
)
output.append(outputs_cached)

View File

@ -199,7 +199,6 @@ class Component(ComponentBase, Block):
initial_value,
self, # type: ignore
postprocess=True,
add_urls=True,
)
if callable(load_fn):

View File

@ -62,7 +62,7 @@ class Button(Component):
scale=scale,
min_width=min_width,
)
self.icon = self.move_resource_to_block_cache(icon)
self.icon = self.serve_static_file(icon)
self.variant = variant
self.size = size
self.link = link

View File

@ -9,7 +9,7 @@ from typing import Any, Callable, List, Literal, Optional, Tuple, Union
from gradio_client import utils as client_utils
from gradio_client.documentation import document
from gradio import processing_utils, utils
from gradio import utils
from gradio.components.base import Component
from gradio.data_classes import FileData, GradioModel, GradioRootModel
from gradio.events import Events
@ -123,13 +123,13 @@ class Chatbot(Component):
render=render,
value=value,
)
self.avatar_images: list[str | None] = [None, None]
self.avatar_images: list[dict | None] = [None, None]
if avatar_images is None:
pass
else:
self.avatar_images = [
processing_utils.move_resource_to_block_cache(avatar_images[0], self),
processing_utils.move_resource_to_block_cache(avatar_images[1], self),
self.serve_static_file(avatar_images[0]),
self.serve_static_file(avatar_images[1]),
]
def _preprocess_chat_messages(

View File

@ -95,7 +95,8 @@ class Dataset(Component):
# custom components
example[i] = component.as_example(ex)
example[i] = processing_utils.move_files_to_cache(
example[i], component
example[i],
component,
)
self.type = type
self.label = label

View File

@ -101,7 +101,7 @@ class UploadButton(Component):
min_width=min_width,
interactive=interactive,
)
self.icon = self.move_resource_to_block_cache(icon)
self.icon = self.serve_static_file(icon)
def api_info(self) -> dict[str, list[str]]:
if self.file_count == "single":

View File

@ -210,7 +210,9 @@ class Examples:
if isinstance(prediction_value, (GradioRootModel, GradioModel)):
prediction_value = prediction_value.model_dump()
prediction_value = processing_utils.move_files_to_cache(
prediction_value, component, postprocess=True
prediction_value,
component,
postprocess=True,
)
sub.append(prediction_value)
self.processed_examples.append(sub)

View File

@ -29,7 +29,7 @@ with warnings.catch_warnings():
log = logging.getLogger(__name__)
if TYPE_CHECKING:
from gradio.components.base import Component
from gradio.blocks import Block
#########################
# GENERAL
@ -228,7 +228,7 @@ def save_base64_to_cache(
def move_resource_to_block_cache(
url_or_file_path: str | Path | None, block: Component
url_or_file_path: str | Path | None, block: Block
) -> str | None:
"""This method has been replaced by Block.move_resource_to_block_cache(), but is
left here for backwards compatibility for any custom components created in Gradio 4.2.0 or earlier.
@ -238,9 +238,8 @@ def move_resource_to_block_cache(
def move_files_to_cache(
data: Any,
block: Component,
block: Block,
postprocess: bool = False,
add_urls=False,
check_in_upload_folder=False,
) -> dict:
"""Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file.
@ -252,8 +251,6 @@ def move_files_to_cache(
data: The input or output data for a component. Can be a dictionary or a dataclass
block: The component whose data is being processed
postprocess: Whether its running from postprocessing
root_url: The root URL of the local server, if applicable
add_urls: Whether to add URLs to the payload
check_in_upload_folder: If True, instead of moving the file to cache, checks if the file is in already in cache (exception if not).
"""
@ -263,7 +260,7 @@ def move_files_to_cache(
# postprocess, it means the component can display a URL
# without it being served from the gradio server
# This makes it so that the URL is not downloaded and speeds up event processing
if payload.url and postprocess:
if payload.url and postprocess and client_utils.is_http_url_like(payload.url):
payload.path = payload.url
elif not block.proxy_url:
# If the file is on a remote server, do not move it to cache.
@ -280,18 +277,17 @@ def move_files_to_cache(
raise ValueError("Did not determine a file path for the resource.")
payload.path = temp_file_path
if add_urls:
url_prefix = "/stream/" if payload.is_stream else "/file="
if block.proxy_url:
proxy_url = block.proxy_url.rstrip("/")
url = f"/proxy={proxy_url}{url_prefix}{payload.path}"
elif client_utils.is_http_url_like(payload.path) or payload.path.startswith(
f"{url_prefix}"
):
url = payload.path
else:
url = f"{url_prefix}{payload.path}"
payload.url = url
url_prefix = "/stream/" if payload.is_stream else "/file="
if block.proxy_url:
proxy_url = block.proxy_url.rstrip("/")
url = f"/proxy={proxy_url}{url_prefix}{payload.path}"
elif client_utils.is_http_url_like(payload.path) or payload.path.startswith(
f"{url_prefix}"
):
url = payload.path
else:
url = f"{url_prefix}{payload.path}"
payload.url = url
return payload.model_dump()

View File

@ -104,7 +104,6 @@ The `Example.svelte` file should expose the following props:
export let value: string;
export let type: "gallery" | "table";
export let selected = false;
export let samples_dir: string;
export let index: number;
```
@ -114,8 +113,6 @@ The `Example.svelte` file should expose the following props:
* `selected`: You can also adjust how the examples are displayed if a user "selects" a particular example by using the selected variable.
* `samples_dir`: A URL to prepend to `value` if your example is fetching a file from the server
* `index`: The current index of the selected value.
* Any additional props your "non-example" component takes!

View File

@ -472,7 +472,6 @@ To do so, we're going to add some of the pdf rendering logic in `Index.svelte` t
```ts
<script lang="ts">
export let value: string;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
import pdfjsLib from "pdfjs-dist";
@ -505,7 +504,7 @@ To do so, we're going to add some of the pdf rendering logic in `Index.svelte` t
});
}
$: get_doc(samples_dir + value);
$: get_doc(value);
</script>
<div
@ -590,11 +589,6 @@ class PDF(Component):
def example_inputs(self):
return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf"
def as_example(self, input_data: str | None) -> str | None:
if input_data is None:
return None
return processing_utils.move_resource_to_block_cache(input_data, self)
```
## Step 10: Add a demo and publish!

View File

@ -65,7 +65,10 @@
<Story
name="Button with external image icon"
args={{
icon: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
icon: {
url: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg",
path: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
}
}}
/>
<Story

View File

@ -3,7 +3,8 @@
</script>
<script lang="ts">
import type { Gradio, ShareData } from "@gradio/utils";
import type { Gradio } from "@gradio/utils";
import { type FileData } from "@gradio/client";
import Button from "./shared/Button.svelte";
@ -15,14 +16,12 @@
export let interactive: boolean;
export let size: "sm" | "lg" = "lg";
export let scale: number | null = null;
export let icon: string | null = null;
export let icon: FileData | null = null;
export let link: string | null = null;
export let min_width: number | undefined = undefined;
export let gradio: Gradio<{
click: never;
}>;
export let root = "";
export let proxy_url: null | string = null;
</script>
<Button
@ -36,8 +35,6 @@
{icon}
{min_width}
{visible}
{root}
{proxy_url}
disabled={!interactive}
on:click={() => gradio.dispatch("click")}
>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { get_fetchable_url_or_file } from "@gradio/client";
import { type FileData } from "@gradio/client";
export let elem_id = "";
export let elem_classes: string[] = [];
@ -8,13 +8,10 @@
export let size: "sm" | "lg" = "lg";
export let value: string | null = null;
export let link: string | null = null;
export let icon: string | null = null;
export let icon: FileData | null = null;
export let disabled = false;
export let scale: number | null = null;
export let min_width: number | undefined = undefined;
export let root = "";
export let proxy_url: string | null = null;
$: icon_path = get_fetchable_url_or_file(icon, root, proxy_url);
</script>
{#if link && link.length > 0}
@ -34,7 +31,7 @@
id={elem_id}
>
{#if icon}
<img class="button-icon" src={icon_path} alt={`${value} icon`} />
<img class="button-icon" src={icon.url} alt={`${value} icon`} />
{/if}
<slot />
</a>
@ -52,7 +49,7 @@
{disabled}
>
{#if icon}
<img class="button-icon" src={icon_path} alt={`${value} icon`} />
<img class="button-icon" src={icon.url} alt={`${value} icon`} />
{/if}
<slot />
</button>

View File

@ -106,8 +106,8 @@
args={{
layout: "panel",
avatar_images: [
"https://avatars.githubusercontent.com/u/100000?v=4",
"https://avatars.githubusercontent.com/u/100000?v=4"
{ url: "https://avatars.githubusercontent.com/u/100000?v=4" },
{ url: "https://avatars.githubusercontent.com/u/100000?v=4" }
]
}}
/>
@ -118,8 +118,8 @@
bubble_full_width: true,
layout: "bubble",
avatar_images: [
"https://avatars.githubusercontent.com/u/100000?v=4",
"https://avatars.githubusercontent.com/u/100000?v=4"
{ url: "https://avatars.githubusercontent.com/u/100000?v=4" },
{ url: "https://avatars.githubusercontent.com/u/100000?v=4" }
]
}}
/>

View File

@ -24,7 +24,6 @@
export let label: string;
export let show_label = true;
export let root: string;
export let proxy_url: null | string;
export let _selectable = false;
export let likeable = false;
export let show_share_button = false;
@ -47,7 +46,7 @@
error: string;
like: LikeData;
}>;
export let avatar_images: [string | null, string | null] = [null, null];
export let avatar_images: [FileData | null, FileData | null] = [null, null];
let _value: [
string | { file: FileData; alt_text: string | null } | null,
@ -134,8 +133,6 @@
{bubble_full_width}
{line_breaks}
{layout}
{proxy_url}
{root}
/>
</div>
</Block>

View File

@ -10,7 +10,7 @@
import { Video } from "@gradio/video/shared";
import type { SelectData, LikeData } from "@gradio/utils";
import { MarkdownCode as Markdown } from "@gradio/markdown";
import { get_fetchable_url_or_file, type FileData } from "@gradio/client";
import { type FileData } from "@gradio/client";
import Copy from "./Copy.svelte";
import type { I18nFormatter } from "js/app/src/gradio_helper";
import LikeDislike from "./LikeDislike.svelte";
@ -39,13 +39,11 @@
export let show_share_button = false;
export let rtl = false;
export let show_copy_button = false;
export let avatar_images: [string | null, string | null] = [null, null];
export let avatar_images: [FileData | null, FileData | null] = [null, null];
export let sanitize_html = true;
export let bubble_full_width = true;
export let render_markdown = true;
export let line_breaks = true;
export let root: string;
export let proxy_url: null | string;
export let i18n: I18nFormatter;
export let layout: "bubble" | "panel" = "bubble";
@ -168,11 +166,7 @@
<div class="avatar-container">
<Image
class="avatar-image"
src={get_fetchable_url_or_file(
avatar_images[j],
root,
proxy_url
)}
src={avatar_images[j]?.url}
alt="{j == 0 ? 'user' : 'bot'} avatar"
/>
</div>

View File

@ -1,10 +1,5 @@
<script lang="ts">
import { csvParseRows, tsvParseRows } from "d3-dsv";
import type { Gradio } from "@gradio/utils";
export let gradio: Gradio;
export let value: (string | number)[][] | string;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
export let index: number;
@ -12,42 +7,6 @@
let hovered = false;
let loaded_value: (string | number)[][] | string = value;
let loaded = Array.isArray(loaded_value);
$: if (!loaded && typeof value === "string" && /\.[a-zA-Z]+$/.test(value)) {
fetch(samples_dir + value)
.then((v) => v.text())
.then((v) => {
try {
if ((value as string).endsWith("csv")) {
const small_df = v
.split("\n")
.slice(0, 4)
.map((v) => v.split(",").slice(0, 4).join(","))
.join("\n");
loaded_value = csvParseRows(small_df);
} else if ((value as string).endsWith("tsv")) {
const small_df = v
.split("\n")
.slice(0, 4)
.map((v) => v.split("\t").slice(0, 4).join("\t"))
.join("\n");
loaded_value = tsvParseRows(small_df);
} else {
throw new Error(gradio.i18n("dataframe.incorrect_format"));
}
loaded = true;
} catch (e) {
console.error(e);
}
})
.catch((e) => {
loaded_value = value;
loaded = true;
});
}
</script>
{#if loaded}

View File

@ -35,7 +35,6 @@ BaseExample
```javascript
export let gradio: Gradio;
export let value: (string | number)[][] | string;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
export let index: number;

View File

@ -2,7 +2,6 @@
import { Block } from "@gradio/atoms";
import type { SvelteComponent, ComponentType } from "svelte";
import type { Gradio, SelectData } from "@gradio/utils";
import { get_fetchable_url_or_file } from "@gradio/client";
export let components: string[];
export let component_props: Record<string, any>[];
export let component_map: Map<
@ -29,7 +28,11 @@
select: SelectData;
}>;
let samples_dir: string = get_fetchable_url_or_file(null, root, proxy_url);
// Although the `samples_dir` prop is not used in any of the core Gradio component, it is kept for backward compatibility
// with any custom components created with gradio<=4.20.0
let samples_dir: string = proxy_url
? `/proxy=${proxy_url}file=`
: `${root}/file=`;
let page = 0;
$: gallery = components.length < 2;
let paginate = samples.length > samples_per_page;

View File

@ -3,7 +3,6 @@
import type { FileData } from "@gradio/client";
export let value: null | FileData;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
</script>
@ -16,7 +15,7 @@
class:border={value}
>
{#if value}
<Image src={samples_dir + value.path} alt="" />
<Image src={value.url} alt="" />
{/if}
</div>

View File

@ -12,7 +12,6 @@
<Story
name="Image file"
args={{
samples_dir: "",
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",

View File

@ -3,7 +3,6 @@
import type { EditorData } from "./shared/InteractiveImageEditor.svelte";
export let value: EditorData;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
</script>
@ -14,10 +13,7 @@
class:gallery={type === "gallery"}
class:selected
>
<Image
src={samples_dir + (value.composite?.path || value.background?.path)}
alt=""
/>
<Image src={value.composite?.url || value.background?.url} alt="" />
</div>
<style>

View File

@ -2,7 +2,6 @@
import type { FileData } from "@gradio/client";
export let value: null | FileData;
export let samples_dir: string;
export let type: "gallery" | "table";
export let selected = false;
</script>
@ -14,7 +13,7 @@
class:gallery={type === "gallery"}
class:selected
>
<img src={samples_dir + value.path} alt="" />
<img src={value.url} alt="" />
</div>
{/if}

View File

@ -17,7 +17,7 @@
export let root: string;
export let size: "sm" | "lg" = "lg";
export let scale: number | null = null;
export let icon: string | null = null;
export let icon: FileData | null = null;
export let min_width: number | undefined = undefined;
export let variant: "primary" | "secondary" | "stop" = "secondary";
export let gradio: Gradio<{
@ -26,7 +26,6 @@
click: never;
}>;
export let interactive: boolean;
export let proxy_url: null | string = null;
$: disabled = !interactive;
@ -54,7 +53,6 @@
{disabled}
{variant}
{label}
{proxy_url}
on:click={() => gradio.dispatch("click")}
on:change={({ detail }) => handle_event(detail, "change")}
on:upload={({ detail }) => handle_event(detail, "upload")}

View File

@ -68,7 +68,10 @@
name="Button with external image icon"
args={{
label: "Upload",
icon: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
icon: {
path: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg",
url: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
}
}}
/>
<Story

View File

@ -18,7 +18,6 @@ describe("UploadButton", () => {
return {
...actual,
get_fetchable_url_or_file: () => "",
prepare_files: () => [],
// upload: vi.fn((f) => new Promise((res) => res([]))),
upload_files: vi.fn((f) => new Promise((res) => res({})))
@ -51,7 +50,6 @@ describe("UploadButton", () => {
return {
...actual,
get_fetchable_url_or_file: () => "",
prepare_files: () => [],
// upload: vi.fn((f) => new Promise((res) => res([]))),
upload_files: vi.fn((f) => new Promise((res) => res({})))

View File

@ -5,7 +5,6 @@
upload,
prepare_files,
type FileData,
get_fetchable_url_or_file,
type upload_files
} from "@gradio/client";
@ -18,13 +17,11 @@
export let file_types: string[] = [];
export let root: string;
export let size: "sm" | "lg" = "lg";
export let icon: string | null = null;
export let icon: FileData | null = null;
export let scale: number | null = null;
export let min_width: number | undefined = undefined;
export let variant: "primary" | "secondary" | "stop" = "secondary";
export let disabled = false;
export let proxy_url: string | null = null;
$: icon_path = get_fetchable_url_or_file(icon, root, proxy_url);
const dispatch = createEventDispatcher();
@ -108,7 +105,7 @@
{disabled}
>
{#if icon}
<img class="button-icon" src={icon_path} alt={`${value} icon`} />
<img class="button-icon" src={icon.url} alt={`${value} icon`} />
{/if}
<slot />
</BaseButton>

View File

@ -6,7 +6,6 @@
export let type: "gallery" | "table";
export let selected = false;
export let value: { video: FileData; subtitles: FileData | null } | null;
export let samples_dir: string;
let video: HTMLVideoElement;
async function init(): Promise<void> {
@ -35,7 +34,7 @@
on:loadeddata={init}
on:mouseover={video.play.bind(video)}
on:mouseout={video.pause.bind(video)}
src={samples_dir + value?.video.path}
src={value?.video.url}
/>
</div>
{:else}

View File

@ -2024,7 +2024,9 @@ class TestChatbot:
def test_avatar_images_are_moved_to_cache(self):
chatbot = gr.Chatbot(avatar_images=("test/test_files/bus.png", None))
assert chatbot.avatar_images[0]
assert utils.is_in_or_equal(chatbot.avatar_images[0], chatbot.GRADIO_CACHE)
assert utils.is_in_or_equal(
chatbot.avatar_images[0]["path"], chatbot.GRADIO_CACHE
)
assert chatbot.avatar_images[1] is None