diff --git a/.changeset/itchy-wombats-end.md b/.changeset/itchy-wombats-end.md new file mode 100644 index 000000000000..177df4d72a75 --- /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 41bde30b1818..a55c8fc00631 100644 --- a/gradio/_simple_templates/simpledropdown.py +++ b/gradio/_simple_templates/simpledropdown.py @@ -73,7 +73,10 @@ def api_info(self) -> dict[str, Any]: "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 24371a1f6d80..e9a7516162a3 100644 --- a/gradio/_simple_templates/simpleimage.py +++ b/gradio/_simple_templates/simpleimage.py @@ -97,5 +97,8 @@ def postprocess(self, value: str | Path | None) -> FileData | None: 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 8ecad105d983..d3f0c525ae2a 100644 --- a/gradio/_simple_templates/simpletextbox.py +++ b/gradio/_simple_templates/simpletextbox.py @@ -87,5 +87,8 @@ def postprocess(self, value: str | None) -> str | None: 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 514494f48860..b686c055706b 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -1776,6 +1776,9 @@ def get_layout(block): 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 18ed93accb8b..a4507b3b0732 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 @@ def _in_test_dir(): """ 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 3543e3c83af4..35fc018fa952 100644 --- a/gradio/components/annotated_image.py +++ b/gradio/components/annotated_image.py @@ -4,6 +4,7 @@ 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 @@ def preprocess( ) -> 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 @@ def postprocess( 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 @@ def hex_to_rgb(value): 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 cb15917d92cd..4982d6014bc1 100644 --- a/gradio/components/audio.py +++ b/gradio/components/audio.py @@ -181,7 +181,10 @@ def __init__( 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 8f0e441a3533..d61d0efdf670 100644 --- a/gradio/components/bar_plot.py +++ b/gradio/components/bar_plot.py @@ -299,5 +299,8 @@ def postprocess(self, value: pd.DataFrame | None) -> AltairPlotData | None: 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 e274ef8b54b8..5dc33145f2cc 100644 --- a/gradio/components/base.py +++ b/gradio/components/base.py @@ -83,8 +83,7 @@ def api_info(self) -> dict[str, list[str]]: @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 @@ def as_example(self, value): """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 49426dafb6e3..b1caee415808 100644 --- a/gradio/components/button.py +++ b/gradio/components/button.py @@ -89,5 +89,8 @@ def postprocess(self, value: str | None) -> str | None: """ 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 395f6ce4d617..f715c12270ba 100644 --- a/gradio/components/chatbot.py +++ b/gradio/components/chatbot.py @@ -226,5 +226,8 @@ def postprocess( ) 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 9c541ab7b223..1b082fbd7904 100644 --- a/gradio/components/checkbox.py +++ b/gradio/components/checkbox.py @@ -73,7 +73,10 @@ def __init__( 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 83025f7ecc6a..b4d0ae846117 100644 --- a/gradio/components/checkboxgroup.py +++ b/gradio/components/checkboxgroup.py @@ -85,7 +85,10 @@ def __init__( 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 ac0773e4f809..1f6fec18718a 100644 --- a/gradio/components/clear_button.py +++ b/gradio/components/clear_button.py @@ -127,5 +127,8 @@ def postprocess(self, value: str | None) -> str | None: """ 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 d5943bfab32a..7add8f28b9e2 100644 --- a/gradio/components/code.py +++ b/gradio/components/code.py @@ -162,5 +162,8 @@ def flag(self, payload: Any, flag_dir: str | Path = "") -> str: 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 1d707eca9be6..c05ff8c785f4 100644 --- a/gradio/components/color_picker.py +++ b/gradio/components/color_picker.py @@ -68,7 +68,10 @@ def __init__( 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 4960579b71c1..adb956296925 100644 --- a/gradio/components/dataframe.py +++ b/gradio/components/dataframe.py @@ -371,5 +371,8 @@ def process_example( 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 3efe66a10f47..e77da3923bfe 100644 --- a/gradio/components/dataset.py +++ b/gradio/components/dataset.py @@ -152,5 +152,8 @@ def postprocess(self, samples: list[list]) -> dict: "__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 f5976083d25c..cea9fd14c2c9 100644 --- a/gradio/components/download_button.py +++ b/gradio/components/download_button.py @@ -99,7 +99,10 @@ def postprocess(self, value: str | Path | None) -> FileData | None: 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 c7fa008db43c..de6bf1c0612b 100644 --- a/gradio/components/dropdown.py +++ b/gradio/components/dropdown.py @@ -130,7 +130,13 @@ def api_info(self) -> dict[str, Any]: } 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 9e3359a7559e..dce172d0057b 100644 --- a/gradio/components/fallback.py +++ b/gradio/components/fallback.py @@ -22,7 +22,10 @@ def postprocess(self, value): """ 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 79b8f849cc7b..be619620d824 100644 --- a/gradio/components/file.py +++ b/gradio/components/file.py @@ -7,8 +7,10 @@ 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 @@ @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 @@ def __init__( ): """ 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 @@ def preprocess( 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 @@ def process_example(self, input_data: str | list | None) -> str: 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 4af663619e31..690c69428632 100644 --- a/gradio/components/file_explorer.py +++ b/gradio/components/file_explorer.py @@ -104,7 +104,10 @@ def __init__( 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 691ed4ccd849..06b145d474e7 100644 --- a/gradio/components/gallery.py +++ b/gradio/components/gallery.py @@ -220,7 +220,14 @@ def convert_to_type(img: str, type: Literal["filepath", "numpy", "pil"]): 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 fa120f6fd099..ef69ef49c730 100644 --- a/gradio/components/highlighted_text.py +++ b/gradio/components/highlighted_text.py @@ -91,8 +91,14 @@ def __init__( 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 18e923ef5bb3..7310884c90c6 100644 --- a/gradio/components/html.py +++ b/gradio/components/html.py @@ -55,7 +55,10 @@ def __init__( 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 a6a150919c22..652ded698675 100644 --- a/gradio/components/image.py +++ b/gradio/components/image.py @@ -208,5 +208,8 @@ def check_streamable(self): "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 cf6fe627fc9f..344fe01a7ab0 100644 --- a/gradio/components/image_editor.py +++ b/gradio/components/image_editor.py @@ -315,7 +315,14 @@ def postprocess(self, value: EditorValue | ImageType | None) -> EditorData | Non 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 34eb455382a6..639f801cc563 100644 --- a/gradio/components/json_component.py +++ b/gradio/components/json_component.py @@ -88,7 +88,10 @@ def postprocess(self, value: dict | list | str | None) -> dict | list | None: 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 31da698216e6..d4ae8521f07f 100644 --- a/gradio/components/label.py +++ b/gradio/components/label.py @@ -141,7 +141,7 @@ def postprocess( f"Instead, got a {type(value)}" ) - def example_inputs(self) -> Any: + def example_payload(self) -> Any: return { "label": "Cat", "confidences": [ @@ -149,3 +149,6 @@ def example_inputs(self) -> Any: {"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 5324347aea4f..18dad1edad70 100644 --- a/gradio/components/line_plot.py +++ b/gradio/components/line_plot.py @@ -331,5 +331,8 @@ def postprocess( 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 1f6f823c1f95..7d2c9896e203 100644 --- a/gradio/components/markdown.py +++ b/gradio/components/markdown.py @@ -96,7 +96,10 @@ def postprocess(self, value: str | None) -> str | None: 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 034d43096327..7ca5aea9d673 100644 --- a/gradio/components/model3d.py +++ b/gradio/components/model3d.py @@ -117,6 +117,8 @@ def postprocess(self, value: str | Path | None) -> FileData | None: 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 b3cb9c3fe4a0..76ad7ada768f 100644 --- a/gradio/components/number.py +++ b/gradio/components/number.py @@ -134,5 +134,8 @@ def postprocess(self, value: float | int | None) -> float | int | None: 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 ae1ba18be6bf..49d80504d017 100644 --- a/gradio/components/paramviewer.py +++ b/gradio/components/paramviewer.py @@ -71,7 +71,16 @@ def postprocess(self, value: dict[str, Parameter]) -> dict[str, Parameter]: """ 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 f6ee9649aa93..2a712086828c 100644 --- a/gradio/components/plot.py +++ b/gradio/components/plot.py @@ -102,7 +102,10 @@ def preprocess(self, payload: PlotData | None) -> PlotData | None: """ 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 befda3e2f96d..689d472c713f 100644 --- a/gradio/components/radio.py +++ b/gradio/components/radio.py @@ -86,7 +86,10 @@ def __init__( 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 224372a39d01..6d17299402a4 100644 --- a/gradio/components/scatter_plot.py +++ b/gradio/components/scatter_plot.py @@ -356,5 +356,8 @@ def postprocess( 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 90c474bf71a8..610bcaefd971 100644 --- a/gradio/components/slider.py +++ b/gradio/components/slider.py @@ -96,7 +96,10 @@ def api_info(self) -> dict[str, Any]: "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 9b987d37c72f..80be5fdfd2e2 100644 --- a/gradio/components/state.py +++ b/gradio/components/state.py @@ -63,7 +63,10 @@ def postprocess(self, value: Any) -> Any: 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 9d8d583f45a0..7bf5b8b1e1a9 100644 --- a/gradio/components/textbox.py +++ b/gradio/components/textbox.py @@ -131,5 +131,8 @@ def postprocess(self, value: str | None) -> str | None: 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 e8a9ce5ad25e..5d504dd9caf9 100644 --- a/gradio/components/upload_button.py +++ b/gradio/components/upload_button.py @@ -7,8 +7,10 @@ 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 @@ def api_info(self) -> dict[str, list[str]]: 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 @@ def preprocess( 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 edefdac4bc6d..91d110ab5fb7 100644 --- a/gradio/components/video.py +++ b/gradio/components/video.py @@ -351,8 +351,10 @@ def srt_to_vtt(srt_file_path, vtt_file_path): 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 62d149480885..f5d52ef863bd 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` + +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 +def example_payload(self) -> Any: + """ + The example inputs for this component for API usage. Must be JSON-serializable. + """ + pass +``` + +### `example_value` -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 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 -@abstractmethod -def example_inputs(self) -> Any: +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 d7b6c5d43bcf..2448dd067969 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 d9089687fceb..1bbad463fe2b 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 6fc435b142e7..f05b3236fea2 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 b6030b184c38..8d043f6c225b 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -22,6 +22,7 @@ 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 @@ 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())