diff --git a/.changeset/itchy-wombats-end.md b/.changeset/itchy-wombats-end.md new file mode 100644 index 0000000000..177df4d72a --- /dev/null +++ b/.changeset/itchy-wombats-end.md @@ -0,0 +1,5 @@ +--- +"gradio": patch +--- + +feat:Refactor `example_inputs()`, separating its logic into two separate methods: `example_payload()` and `example_value()` diff --git a/gradio/_simple_templates/simpledropdown.py b/gradio/_simple_templates/simpledropdown.py index 41bde30b18..a55c8fc006 100644 --- a/gradio/_simple_templates/simpledropdown.py +++ b/gradio/_simple_templates/simpledropdown.py @@ -73,7 +73,10 @@ class SimpleDropdown(FormComponent): "enum": [c[1] for c in self.choices], } - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return self.choices[0][1] if self.choices else None + + def example_value(self) -> Any: return self.choices[0][1] if self.choices else None def preprocess(self, payload: str | int | float | None) -> str | int | float | None: diff --git a/gradio/_simple_templates/simpleimage.py b/gradio/_simple_templates/simpleimage.py index 24371a1f6d..e9a7516162 100644 --- a/gradio/_simple_templates/simpleimage.py +++ b/gradio/_simple_templates/simpleimage.py @@ -97,5 +97,8 @@ class SimpleImage(Component): return None return FileData(path=str(value), orig_name=Path(value).name) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" + + def example_value(self) -> Any: return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" diff --git a/gradio/_simple_templates/simpletextbox.py b/gradio/_simple_templates/simpletextbox.py index 8ecad105d9..d3f0c525ae 100644 --- a/gradio/_simple_templates/simpletextbox.py +++ b/gradio/_simple_templates/simpletextbox.py @@ -87,5 +87,8 @@ class SimpleTextbox(FormComponent): def api_info(self) -> dict[str, Any]: return {"type": "string"} - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "Hello!!" + + def example_value(self) -> Any: return "Hello!!" diff --git a/gradio/blocks.py b/gradio/blocks.py index 514494f488..b686c05570 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -1776,6 +1776,9 @@ Received outputs: if not block.skip_api: block_config["api_info"] = block.api_info() # type: ignore + # .example_inputs() has been renamed .example_payload() but + # we use the old name for backwards compatibility with custom components + # created on Gradio 4.20.0 or earlier block_config["example_inputs"] = block.example_inputs() # type: ignore config["components"].append(block_config) config["dependencies"] = self.dependencies diff --git a/gradio/cli/commands/components/_create_utils.py b/gradio/cli/commands/components/_create_utils.py index 18ed93accb..a4507b3b07 100644 --- a/gradio/cli/commands/components/_create_utils.py +++ b/gradio/cli/commands/components/_create_utils.py @@ -18,7 +18,7 @@ def _in_test_dir(): default_demo_code = """ -example = {name}().example_inputs() +example = {name}().example_value() demo = gr.Interface( lambda x:x, @@ -29,7 +29,7 @@ demo = gr.Interface( """ static_only_demo_code = """ -example = {name}().example_inputs() +example = {name}().example_value() with gr.Blocks() as demo: with gr.Row(): diff --git a/gradio/components/annotated_image.py b/gradio/components/annotated_image.py index 3543e3c83a..35fc018fa9 100644 --- a/gradio/components/annotated_image.py +++ b/gradio/components/annotated_image.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Any, List +import gradio_client.utils as client_utils import numpy as np import PIL.Image from gradio_client.documentation import document @@ -103,7 +104,7 @@ class AnnotatedImage(Component): ) -> tuple[str, list[tuple[str, str]]] | None: """ Parameters: - payload: Tuple of base image and list of annotations. + payload: Dict of base image and list of annotations. Returns: Passes its value as a `tuple` consisting of a `str` filepath to a base image and `list` of annotations. Each annotation itself is `tuple` of a mask (as a `str` filepath to image) and a `str` label. """ @@ -131,6 +132,10 @@ class AnnotatedImage(Component): return None base_img = value[0] if isinstance(base_img, str): + if client_utils.is_http_url_like(base_img): + base_img = processing_utils.save_url_to_cache( + base_img, cache_dir=self.GRADIO_CACHE + ) base_img_path = base_img base_img = np.array(PIL.Image.open(base_img)) elif isinstance(base_img, np.ndarray): @@ -198,5 +203,14 @@ class AnnotatedImage(Component): annotations=sections, ) - def example_inputs(self) -> Any: - return {} + def example_payload(self) -> Any: + return { + "image": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", + "annotations": [], + } + + def example_value(self) -> Any: + return ( + "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", + [([0, 0, 100, 100], "bus")], + ) diff --git a/gradio/components/audio.py b/gradio/components/audio.py index cb15917d92..4982d6014b 100644 --- a/gradio/components/audio.py +++ b/gradio/components/audio.py @@ -181,7 +181,10 @@ class Audio( value=value, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav" + + def example_value(self) -> Any: return "https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav" def preprocess( diff --git a/gradio/components/bar_plot.py b/gradio/components/bar_plot.py index 8f0e441a35..d61d0efdf6 100644 --- a/gradio/components/bar_plot.py +++ b/gradio/components/bar_plot.py @@ -299,5 +299,8 @@ class BarPlot(Plot): return AltairPlotData(type="altair", plot=chart.to_json(), chart="bar") - def example_inputs(self) -> dict[str, Any]: - return {} + def example_payload(self) -> Any: + return None + + def example_value(self) -> Any: + return pd.DataFrame({self.x: [1, 2, 3], self.y: [4, 5, 6]}) diff --git a/gradio/components/base.py b/gradio/components/base.py index e274ef8b54..5dc33145f2 100644 --- a/gradio/components/base.py +++ b/gradio/components/base.py @@ -83,8 +83,7 @@ class ComponentBase(ABC, metaclass=ComponentMeta): @abstractmethod def example_inputs(self) -> Any: """ - The example inputs for this component as a dictionary whose values are example inputs compatible with this component. - Keys of the dictionary are: raw, serialized + Deprecated and replaced by `example_payload()` and `example_value()`. """ pass @@ -267,6 +266,24 @@ class Component(ComponentBase, Block): """Deprecated and replaced by `process_example()`.""" return self.process_example(value) + def example_inputs(self) -> Any: + """Deprecated and replaced by `example_payload()` and `example_value()`.""" + return self.example_payload() + + def example_payload(self) -> Any: + """ + An example input data for this component, e.g. what is passed to this component's preprocess() method. + This is used to generate the docs for the View API page for Gradio apps using this component. + """ + raise NotImplementedError() + + def example_value(self) -> Any: + """ + An example output data for this component, e.g. what is passed to this component's postprocess() method. + This is used to generate an example value if this component is used as a template for a custom component. + """ + raise NotImplementedError() + def api_info(self) -> dict[str, Any]: """ The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. diff --git a/gradio/components/button.py b/gradio/components/button.py index 49426dafb6..b1caee4158 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -89,5 +89,8 @@ class Button(Component): """ return value - def example_inputs(self) -> Any: - return None + def example_payload(self) -> Any: + return "Run" + + def example_value(self) -> Any: + return "Run" diff --git a/gradio/components/chatbot.py b/gradio/components/chatbot.py index 395f6ce4d6..f715c12270 100644 --- a/gradio/components/chatbot.py +++ b/gradio/components/chatbot.py @@ -226,5 +226,8 @@ class Chatbot(Component): ) return ChatbotData(root=processed_messages) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return [["Hello!", None]] + + def example_value(self) -> Any: return [["Hello!", None]] diff --git a/gradio/components/checkbox.py b/gradio/components/checkbox.py index 9c541ab7b2..1b082fbd79 100644 --- a/gradio/components/checkbox.py +++ b/gradio/components/checkbox.py @@ -73,7 +73,10 @@ class Checkbox(FormComponent): def api_info(self) -> dict[str, Any]: return {"type": "boolean"} - def example_inputs(self) -> bool: + def example_payload(self) -> bool: + return True + + def example_value(self) -> bool: return True def preprocess(self, payload: bool | None) -> bool | None: diff --git a/gradio/components/checkboxgroup.py b/gradio/components/checkboxgroup.py index 83025f7ecc..b4d0ae8461 100644 --- a/gradio/components/checkboxgroup.py +++ b/gradio/components/checkboxgroup.py @@ -85,7 +85,10 @@ class CheckboxGroup(FormComponent): value=value, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return [self.choices[0][1]] if self.choices else None + + def example_value(self) -> Any: return [self.choices[0][1]] if self.choices else None def api_info(self) -> dict[str, Any]: diff --git a/gradio/components/clear_button.py b/gradio/components/clear_button.py index ac0773e4f8..1f6fec1871 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -127,5 +127,8 @@ class ClearButton(Button): """ return value - def example_inputs(self) -> Any: - return None + def example_payload(self) -> Any: + return "Clear" + + def example_value(self) -> Any: + return "Clear" diff --git a/gradio/components/code.py b/gradio/components/code.py index d5943bfab3..7add8f28b9 100644 --- a/gradio/components/code.py +++ b/gradio/components/code.py @@ -162,5 +162,8 @@ class Code(Component): def api_info(self) -> dict[str, Any]: return {"type": "string"} - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "print('Hello World')" + + def example_value(self) -> Any: return "print('Hello World')" diff --git a/gradio/components/color_picker.py b/gradio/components/color_picker.py index 1d707eca9b..c05ff8c785 100644 --- a/gradio/components/color_picker.py +++ b/gradio/components/color_picker.py @@ -68,7 +68,10 @@ class ColorPicker(Component): value=value, ) - def example_inputs(self) -> str: + def example_payload(self) -> str: + return "#000000" + + def example_value(self) -> str: return "#000000" def api_info(self) -> dict[str, Any]: diff --git a/gradio/components/dataframe.py b/gradio/components/dataframe.py index 4960579b71..adb9562969 100644 --- a/gradio/components/dataframe.py +++ b/gradio/components/dataframe.py @@ -371,5 +371,8 @@ class Dataframe(Component): value_df = pd.DataFrame(value_df_data.data, columns=value_df_data.headers) return value_df.head(n=5).to_dict(orient="split")["data"] - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return {"headers": ["a", "b"], "data": [["foo", "bar"]]} + + def example_value(self) -> Any: return {"headers": ["a", "b"], "data": [["foo", "bar"]]} diff --git a/gradio/components/dataset.py b/gradio/components/dataset.py index 3efe66a10f..e77da3923b 100644 --- a/gradio/components/dataset.py +++ b/gradio/components/dataset.py @@ -152,5 +152,8 @@ class Dataset(Component): "__type__": "update", } - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return 0 + + def example_value(self) -> Any: + return [] diff --git a/gradio/components/download_button.py b/gradio/components/download_button.py index f5976083d2..cea9fd14c2 100644 --- a/gradio/components/download_button.py +++ b/gradio/components/download_button.py @@ -99,7 +99,10 @@ class DownloadButton(Component): return None return FileData(path=str(value)) - def example_inputs(self) -> str: + def example_payload(self) -> str: + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + + def example_value(self) -> str: return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" @property diff --git a/gradio/components/dropdown.py b/gradio/components/dropdown.py index c7fa008db4..de6bf1c061 100644 --- a/gradio/components/dropdown.py +++ b/gradio/components/dropdown.py @@ -130,7 +130,13 @@ class Dropdown(FormComponent): } return json_type - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + if self.multiselect: + return [self.choices[0][1]] if self.choices else [] + else: + return self.choices[0][1] if self.choices else None + + def example_value(self) -> Any: if self.multiselect: return [self.choices[0][1]] if self.choices else [] else: diff --git a/gradio/components/fallback.py b/gradio/components/fallback.py index 9e3359a755..dce172d005 100644 --- a/gradio/components/fallback.py +++ b/gradio/components/fallback.py @@ -22,7 +22,10 @@ class Fallback(Component): """ return value - def example_inputs(self): + def example_payload(self): + return {"foo": "bar"} + + def example_value(self): return {"foo": "bar"} def api_info(self): diff --git a/gradio/components/file.py b/gradio/components/file.py index 79b8f849cc..be619620d8 100644 --- a/gradio/components/file.py +++ b/gradio/components/file.py @@ -7,8 +7,10 @@ import warnings from pathlib import Path from typing import Any, Callable, Literal +import gradio_client.utils as client_utils from gradio_client.documentation import document +from gradio import processing_utils from gradio.components.base import Component from gradio.data_classes import FileData, ListFiles from gradio.events import Events @@ -18,7 +20,7 @@ from gradio.utils import NamedString @document() class File(Component): """ - Creates a file component that allows uploading one or more generic files (when used as an input) or displaying generic files (as output). + Creates a file component that allows uploading one or more generic files (when used as an input) or displaying generic files or URLs for download (as output). Demo: zip_files, zip_to_json """ @@ -47,7 +49,7 @@ class File(Component): ): """ Parameters: - value: Default file to display, given as str file path. If callable, the function will be called whenever the app loads to set the initial value of the component. + value: Default file(s) to display, given as a str file path or URL, or a list of str file paths / URLs. If callable, the function will be called whenever the app loads to set the initial value of the component. file_count: if single, allows user to upload one file. If "multiple", user uploads multiple files. If "directory", user uploads all files in selected directory. Return type will be list for each file in case of "multiple" or "directory". file_types: List of file extensions or types of files to be uploaded (e.g. ['image', '.json', '.mp4']). "file" allows any file to be uploaded, "image" allows only image files to be uploaded, "audio" allows only audio files to be uploaded, "video" allows only video files to be uploaded, "text" allows only text files to be uploaded. type: Type of value to be returned by component. "file" returns a temporary file object with the same base name as the uploaded file, whose full path can be retrieved by file_obj.name, "binary" returns an bytes object. @@ -139,15 +141,36 @@ class File(Component): return [self._process_single_file(f) for f in payload] # type: ignore return [self._process_single_file(payload)] # type: ignore + def _download_files(self, value: str | list[str]) -> str | list[str]: + downloaded_files = [] + if isinstance(value, list): + for file in value: + if client_utils.is_http_url_like(file): + downloaded_file = processing_utils.save_url_to_cache( + file, self.GRADIO_CACHE + ) + downloaded_files.append(downloaded_file) + else: + downloaded_files.append(file) + return downloaded_files + if client_utils.is_http_url_like(value): + downloaded_file = processing_utils.save_url_to_cache( + value, self.GRADIO_CACHE + ) + return downloaded_file + else: + return value + def postprocess(self, value: str | list[str] | None) -> ListFiles | FileData | None: """ Parameters: - value: Expects a `str` filepath, or a `list[str]` of filepaths. + value: Expects a `str` filepath or URL, or a `list[str]` of filepaths/URLs. Returns: File information as a FileData object, or a list of FileData objects. """ if value is None: return None + value = self._download_files(value) if isinstance(value, list): return ListFiles( root=[ @@ -174,7 +197,15 @@ class File(Component): else: return Path(input_data).name - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + if self.file_count == "single": + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + else: + return [ + "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + ] + + def example_value(self) -> Any: if self.file_count == "single": return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" else: diff --git a/gradio/components/file_explorer.py b/gradio/components/file_explorer.py index 4af663619e..690c694286 100644 --- a/gradio/components/file_explorer.py +++ b/gradio/components/file_explorer.py @@ -104,7 +104,10 @@ class FileExplorer(Component): value=value, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return [["Users", "gradio", "app.py"]] + + def example_value(self) -> Any: return ["Users", "gradio", "app.py"] def preprocess(self, payload: FileExplorerData | None) -> list[str] | str | None: diff --git a/gradio/components/gallery.py b/gradio/components/gallery.py index 691ed4ccd8..06b145d474 100644 --- a/gradio/components/gallery.py +++ b/gradio/components/gallery.py @@ -220,7 +220,14 @@ class Gallery(Component): converted_image = np.array(converted_image) return converted_image - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return [ + { + "image": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" + }, + ] + + def example_value(self) -> Any: return [ "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" ] diff --git a/gradio/components/highlighted_text.py b/gradio/components/highlighted_text.py index fa120f6fd0..ef69ef49c7 100644 --- a/gradio/components/highlighted_text.py +++ b/gradio/components/highlighted_text.py @@ -91,8 +91,14 @@ class HighlightedText(Component): interactive=interactive, ) - def example_inputs(self) -> Any: - return {"value": [{"token": "Hello", "class_or_confidence": "1"}]} + def example_payload(self) -> Any: + return [ + {"token": "The", "class_or_confidence": None}, + {"token": "quick", "class_or_confidence": "adj"}, + ] + + def example_value(self) -> Any: + return [("The", None), ("quick", "adj"), ("brown", "adj"), ("fox", "noun")] def preprocess( self, payload: HighlightedTextData | None diff --git a/gradio/components/html.py b/gradio/components/html.py index 18e923ef5b..7310884c90 100644 --- a/gradio/components/html.py +++ b/gradio/components/html.py @@ -55,7 +55,10 @@ class HTML(Component): value=value, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "

Hello

" + + def example_value(self) -> Any: return "

Hello

" def preprocess(self, payload: str | None) -> str | None: diff --git a/gradio/components/image.py b/gradio/components/image.py index a6a150919c..652ded6986 100644 --- a/gradio/components/image.py +++ b/gradio/components/image.py @@ -208,5 +208,8 @@ class Image(StreamingInput, Component): "Image streaming only available if sources is ['webcam']. Streaming not supported with multiple sources." ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" + + def example_value(self) -> Any: return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png" diff --git a/gradio/components/image_editor.py b/gradio/components/image_editor.py index cf6fe627fc..344fe01a7a 100644 --- a/gradio/components/image_editor.py +++ b/gradio/components/image_editor.py @@ -315,7 +315,14 @@ class ImageEditor(Component): else None, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return { + "background": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", + "layers": [], + "composite": None, + } + + def example_value(self) -> Any: return { "background": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", "layers": [], diff --git a/gradio/components/json_component.py b/gradio/components/json_component.py index 34eb455382..639f801cc5 100644 --- a/gradio/components/json_component.py +++ b/gradio/components/json_component.py @@ -88,7 +88,10 @@ class JSON(Component): else: return value - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return {"foo": "bar"} + + def example_value(self) -> Any: return {"foo": "bar"} def flag( diff --git a/gradio/components/label.py b/gradio/components/label.py index 31da698216..d4ae8521f0 100644 --- a/gradio/components/label.py +++ b/gradio/components/label.py @@ -141,7 +141,7 @@ class Label(Component): f"Instead, got a {type(value)}" ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return { "label": "Cat", "confidences": [ @@ -149,3 +149,6 @@ class Label(Component): {"label": "dog", "confidence": 0.1}, ], } + + def example_value(self) -> Any: + return {"cat": 0.9, "dog": 0.1} diff --git a/gradio/components/line_plot.py b/gradio/components/line_plot.py index 5324347aea..18dad1edad 100644 --- a/gradio/components/line_plot.py +++ b/gradio/components/line_plot.py @@ -331,5 +331,8 @@ class LinePlot(Plot): return AltairPlotData(type="altair", plot=chart.to_json(), chart="line") - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return None + + def example_value(self) -> Any: + return pd.DataFrame({self.x: [1, 2, 3], self.y: [4, 5, 6]}) diff --git a/gradio/components/markdown.py b/gradio/components/markdown.py index 1f6f823c1f..7d2c9896e2 100644 --- a/gradio/components/markdown.py +++ b/gradio/components/markdown.py @@ -96,7 +96,10 @@ class Markdown(Component): unindented_y = inspect.cleandoc(value) return unindented_y - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "# Hello!" + + def example_value(self) -> Any: return "# Hello!" def api_info(self) -> dict[str, Any]: diff --git a/gradio/components/model3d.py b/gradio/components/model3d.py index 034d430963..7ca5aea9d6 100644 --- a/gradio/components/model3d.py +++ b/gradio/components/model3d.py @@ -117,6 +117,8 @@ class Model3D(Component): def process_example(self, input_data: str | Path | None) -> str: return Path(input_data).name if input_data else "" - def example_inputs(self): - # TODO: Use permanent link + def example_payload(self): + return "https://raw.githubusercontent.com/gradio-app/gradio/main/demo/model3D/files/Fox.gltf" + + def example_value(self): return "https://raw.githubusercontent.com/gradio-app/gradio/main/demo/model3D/files/Fox.gltf" diff --git a/gradio/components/number.py b/gradio/components/number.py index b3cb9c3fe4..76ad7ada76 100644 --- a/gradio/components/number.py +++ b/gradio/components/number.py @@ -134,5 +134,8 @@ class Number(FormComponent): def api_info(self) -> dict[str, str]: return {"type": "number"} - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return 3 + + def example_value(self) -> Any: return 3 diff --git a/gradio/components/paramviewer.py b/gradio/components/paramviewer.py index ae1ba18be6..49d80504d0 100644 --- a/gradio/components/paramviewer.py +++ b/gradio/components/paramviewer.py @@ -71,7 +71,16 @@ class ParamViewer(Component): """ return value - def example_inputs(self): + def example_payload(self): + return { + "array": { + "type": "numpy", + "description": "any valid json", + "default": "None", + } + } + + def example_value(self): return { "array": { "type": "numpy", diff --git a/gradio/components/plot.py b/gradio/components/plot.py index f6ee9649aa..2a71208682 100644 --- a/gradio/components/plot.py +++ b/gradio/components/plot.py @@ -102,7 +102,10 @@ class Plot(Component): """ return payload - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return None + + def example_value(self) -> Any: return None def postprocess(self, value: Any) -> PlotData | None: diff --git a/gradio/components/radio.py b/gradio/components/radio.py index befda3e2f9..689d472c71 100644 --- a/gradio/components/radio.py +++ b/gradio/components/radio.py @@ -86,7 +86,10 @@ class Radio(FormComponent): value=value, ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return self.choices[0][1] if self.choices else None + + def example_value(self) -> Any: return self.choices[0][1] if self.choices else None def preprocess(self, payload: str | int | float | None) -> str | int | float | None: diff --git a/gradio/components/scatter_plot.py b/gradio/components/scatter_plot.py index 224372a39d..6d17299402 100644 --- a/gradio/components/scatter_plot.py +++ b/gradio/components/scatter_plot.py @@ -356,5 +356,8 @@ class ScatterPlot(Plot): return AltairPlotData(type="altair", plot=chart.to_json(), chart="scatter") - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return None + + def example_value(self) -> Any: + return pd.DataFrame({self.x: [1, 2, 3], self.y: [4, 5, 6]}) diff --git a/gradio/components/slider.py b/gradio/components/slider.py index 90c474bf71..610bcaefd9 100644 --- a/gradio/components/slider.py +++ b/gradio/components/slider.py @@ -96,7 +96,10 @@ class Slider(FormComponent): "description": f"numeric value between {self.minimum} and {self.maximum}", } - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return self.minimum + + def example_value(self) -> Any: return self.minimum def get_random_value(self): diff --git a/gradio/components/state.py b/gradio/components/state.py index 9b987d37c7..80be5fdfd2 100644 --- a/gradio/components/state.py +++ b/gradio/components/state.py @@ -63,7 +63,10 @@ class State(Component): def api_info(self) -> dict[str, Any]: return {"type": {}, "description": "any valid json"} - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return None + + def example_value(self) -> Any: return None @property diff --git a/gradio/components/textbox.py b/gradio/components/textbox.py index 9d8d583f45..7bf5b8b1e1 100644 --- a/gradio/components/textbox.py +++ b/gradio/components/textbox.py @@ -131,5 +131,8 @@ class Textbox(FormComponent): def api_info(self) -> dict[str, Any]: return {"type": "string"} - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + return "Hello!!" + + def example_value(self) -> Any: return "Hello!!" diff --git a/gradio/components/upload_button.py b/gradio/components/upload_button.py index e8a9ce5ad2..5d504dd9ca 100644 --- a/gradio/components/upload_button.py +++ b/gradio/components/upload_button.py @@ -7,8 +7,10 @@ import warnings from pathlib import Path from typing import Any, Callable, Literal +import gradio_client.utils as client_utils from gradio_client.documentation import document +from gradio import processing_utils from gradio.components.base import Component from gradio.data_classes import FileData, ListFiles from gradio.events import Events @@ -110,7 +112,15 @@ class UploadButton(Component): else: return ListFiles.model_json_schema() - def example_inputs(self) -> Any: + def example_payload(self) -> Any: + if self.file_count == "single": + return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + else: + return [ + "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" + ] + + def example_value(self) -> Any: if self.file_count == "single": return "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" else: @@ -155,15 +165,36 @@ class UploadButton(Component): return [self._process_single_file(f) for f in payload] # type: ignore return [self._process_single_file(payload)] # type: ignore + def _download_files(self, value: str | list[str]) -> str | list[str]: + downloaded_files = [] + if isinstance(value, list): + for file in value: + if client_utils.is_http_url_like(file): + downloaded_file = processing_utils.save_url_to_cache( + file, self.GRADIO_CACHE + ) + downloaded_files.append(downloaded_file) + else: + downloaded_files.append(file) + return downloaded_files + if client_utils.is_http_url_like(value): + downloaded_file = processing_utils.save_url_to_cache( + value, self.GRADIO_CACHE + ) + return downloaded_file + else: + return value + def postprocess(self, value: str | list[str] | None) -> ListFiles | FileData | None: """ Parameters: - value: Expects a `str` filepath, or a `list[str]` of filepaths. + value: Expects a `str` filepath or URL, or a `list[str]` of filepaths/URLs. Returns: File information as a FileData object, or a list of FileData objects. """ if value is None: return None + value = self._download_files(value) if isinstance(value, list): return ListFiles( root=[ diff --git a/gradio/components/video.py b/gradio/components/video.py index edefdac4bc..91d110ab5f 100644 --- a/gradio/components/video.py +++ b/gradio/components/video.py @@ -351,8 +351,10 @@ class Video(Component): return FileData(path=str(subtitle)) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return { "video": "https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/world.mp4", - "subtitles": None, } + + def example_value(self) -> Any: + return "https://github.com/gradio-app/gradio/raw/main/demo/video_component/files/world.mp4" diff --git a/guides/05_custom-components/04_backend.md b/guides/05_custom-components/04_backend.md index 62d1494808..f5d52ef863 100644 --- a/guides/05_custom-components/04_backend.md +++ b/guides/05_custom-components/04_backend.md @@ -40,14 +40,12 @@ Explained in the [Key Concepts](./key-component-concepts#the-value-and-how-it-is They handle the conversion from the data sent by the frontend to the format expected by the python function. ```python - @abstractmethod def preprocess(self, x: Any) -> Any: """ Convert from the web-friendly (typically JSON) value in the frontend to the format expected by the python function. """ return x - @abstractmethod def postprocess(self, y): """ Convert from the data returned by the python function to the web-friendly (typically JSON) value expected by the frontend. @@ -67,11 +65,6 @@ def process_example(self, input_data): Since `self.choices` is a list of tuples corresponding to (`display_name`, `value`), this converts the value that a user provides to the display value (or if the value is not present in `self.choices`, it is converted to `None`). -```python -@abstractmethod -def process_example(self, y): - pass -``` ### `api_info` @@ -81,7 +74,6 @@ You do **not** need to implement this yourself if you components specifies a `da The `data_model` in the following section. ```python -@abstractmethod def api_info(self) -> dict[str, list[str]]: """ A JSON-schema representation of the value that the `preprocess` expects and the `postprocess` returns. @@ -89,15 +81,27 @@ def api_info(self) -> dict[str, list[str]]: pass ``` -### `example_inputs` +### `example_payload` -The example inputs for this component displayed in the `View API` page. -Must be JSON-serializable. -If your component expects a file, it is best to use a publicly accessible URL. +An example payload for your component, e.g. something that can be passed into the `.preprocess()` method +of your component. The example input is displayed in the `View API` page of a Gradio app that uses your custom component. +Must be JSON-serializable. If your component expects a file, it is best to use a publicly accessible URL. ```python -@abstractmethod -def example_inputs(self) -> Any: +def example_payload(self) -> Any: + """ + The example inputs for this component for API usage. Must be JSON-serializable. + """ + pass +``` + +### `example_value` + +An example value for your component, e.g. something that can be passed into the `.postprocess()` method +of your component. This is used as the example value in the default app that is created in custom component development. + +```python +def example_payload(self) -> Any: """ The example inputs for this component for API usage. Must be JSON-serializable. """ @@ -111,7 +115,6 @@ You do **not** need to implement this yourself if you components specifies a `da The `data_model` in the following section. ```python -@abstractmethod def flag(self, x: Any | GradioDataModel, flag_dir: str | Path = "") -> str: pass ``` @@ -122,7 +125,6 @@ You do **not** need to implement this yourself if you components specifies a `da The `data_model` in the following section. ```python -@abstractmethod def read_from_flag( self, x: Any, @@ -138,7 +140,7 @@ def read_from_flag( The `data_model` is how you define the expected data format your component's value will be stored in the frontend. It specifies the data format your `preprocess` method expects and the format the `postprocess` method returns. It is not necessary to define a `data_model` for your component but it greatly simplifies the process of creating a custom component. -If you define a custom component you only need to implement three methods - `preprocess`, `postprocess`, and `example_inputs`! +If you define a custom component you only need to implement four methods - `preprocess`, `postprocess`, `example_payload`, and `example_value`! You define a `data_model` by defining a [pydantic model](https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage) that inherits from either `GradioModel` or `GradioRootModel`. diff --git a/guides/05_custom-components/06_frequently-asked-questions.md b/guides/05_custom-components/06_frequently-asked-questions.md index d7b6c5d43b..2448dd0679 100644 --- a/guides/05_custom-components/06_frequently-asked-questions.md +++ b/guides/05_custom-components/06_frequently-asked-questions.md @@ -22,7 +22,7 @@ If you would like to share your component with the gradio community, it is recom ## What methods are mandatory for implementing a custom component in Gradio? -You must implement the `preprocess`, `postprocess`, `api_info`, `example_inputs`, `flag`, and `read_from_flag` methods. Read more in the [backend guide](./backend). +You must implement the `preprocess`, `postprocess`, `example_payload`, and `example_value` methods. If your component does not use a data model, you must also define the `api_info`, `flag`, and `read_from_flag` methods. Read more in the [backend guide](./backend). ## What is the purpose of a `data_model` in Gradio custom components? @@ -38,7 +38,7 @@ You can define event triggers in the `EVENTS` class attribute by listing the des ## Can I implement a custom Gradio component without defining a `data_model`? -Yes, it is possible to create custom components without a `data_model`, but you are going to have to manually implement `api_info`, `example_inputs`, `flag`, and `read_from_flag` methods. +Yes, it is possible to create custom components without a `data_model`, but you are going to have to manually implement `api_info`, `flag`, and `read_from_flag` methods. ## Are there sample custom components I can learn from? diff --git a/guides/05_custom-components/07_pdf-component-example.md b/guides/05_custom-components/07_pdf-component-example.md index d9089687fc..1bbad463fe 100644 --- a/guides/05_custom-components/07_pdf-component-example.md +++ b/guides/05_custom-components/07_pdf-component-example.md @@ -587,7 +587,10 @@ class PDF(Component): return None return FileData(path=value) - def example_inputs(self): + def example_payload(self): + return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf" + + def example_value(self): return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf" ``` diff --git a/guides/05_custom-components/08_multimodal-chatbot-part1.md b/guides/05_custom-components/08_multimodal-chatbot-part1.md index 6fc435b142..f05b3236fe 100644 --- a/guides/05_custom-components/08_multimodal-chatbot-part1.md +++ b/guides/05_custom-components/08_multimodal-chatbot-part1.md @@ -102,10 +102,13 @@ def _postprocess_chat_messages( return chat_message ``` -Before we wrap up with the backend code, let's modify the `example_inputs` method to return a valid dictionary representation of the `ChatbotData`: +Before we wrap up with the backend code, let's modify the `example_value` and `example_payload` method to return a valid dictionary representation of the `ChatbotData`: ```python -def example_inputs(self) -> Any: +def example_value(self) -> Any: + return [[{"text": "Hello!", "files": []}, None]] + +def example_payload(self) -> Any: return [[{"text": "Hello!", "files": []}, None]] ``` diff --git a/test/test_components.py b/test/test_components.py index b6030b184c..8d043f6c22 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -22,6 +22,7 @@ import pytest import vega_datasets from gradio_client import media_data from gradio_client import utils as client_utils +from gradio_pdf import PDF from scipy.io import wavfile try: @@ -31,6 +32,7 @@ except ImportError: import gradio as gr from gradio import processing_utils, utils +from gradio.components.base import Component from gradio.components.dataframe import DataframeData from gradio.components.file_explorer import FileExplorerData from gradio.components.image_editor import EditorData @@ -2964,3 +2966,14 @@ def test_template_component_configs(io_components): template_config = component().get_config() parent_config = component_parent_class().get_config() assert set(parent_config.keys()).issubset(set(template_config.keys())) + + +def test_component_example_values(io_components): + for component in io_components: + if component == PDF: + continue + elif component in [gr.BarPlot, gr.LinePlot, gr.ScatterPlot]: + c: Component = component(x="x", y="y") + else: + c: Component = component() + c.postprocess(c.example_value())