|
"""Included for backwards compatibility with 3.x spaces/apps.""" |
|
|
|
from __future__ import annotations |
|
|
|
import json |
|
import os |
|
import secrets |
|
import tempfile |
|
import uuid |
|
from pathlib import Path |
|
from typing import Any |
|
|
|
from gradio_client import media_data, utils |
|
from gradio_client.data_classes import FileData |
|
|
|
with open(Path(__file__).parent / "types.json") as f: |
|
serializer_types = json.load(f) |
|
|
|
|
|
class Serializable: |
|
def serialized_info(self): |
|
""" |
|
The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. |
|
Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output |
|
""" |
|
return self.api_info() |
|
|
|
def api_info(self) -> dict[str, list[str]]: |
|
""" |
|
The typing information for this component as a dictionary whose values are a list of 2 strings: [Python type, language-agnostic description]. |
|
Keys of the dictionary are: raw_input, raw_output, serialized_input, serialized_output |
|
""" |
|
raise NotImplementedError() |
|
|
|
def example_inputs(self) -> dict[str, 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 |
|
""" |
|
raise NotImplementedError() |
|
|
|
|
|
def input_api_info(self) -> tuple[str, str]: |
|
api_info = self.api_info() |
|
types = api_info.get("serialized_input", [api_info["info"]["type"]] * 2) |
|
return (types[0], types[1]) |
|
|
|
|
|
def output_api_info(self) -> tuple[str, str]: |
|
api_info = self.api_info() |
|
types = api_info.get("serialized_output", [api_info["info"]["type"]] * 2) |
|
return (types[0], types[1]) |
|
|
|
def serialize(self, x: Any, load_dir: str | Path = "", allow_links: bool = False): |
|
""" |
|
Convert data from human-readable format to serialized format for a browser. |
|
""" |
|
return x |
|
|
|
def deserialize( |
|
self, |
|
x: Any, |
|
save_dir: str | Path | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
): |
|
""" |
|
Convert data from serialized format for a browser to human-readable format. |
|
""" |
|
return x |
|
|
|
|
|
class SimpleSerializable(Serializable): |
|
"""General class that does not perform any serialization or deserialization.""" |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return { |
|
"info": serializer_types["SimpleSerializable"], |
|
"serialized_info": False, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": None, |
|
"serialized": None, |
|
} |
|
|
|
|
|
class StringSerializable(Serializable): |
|
"""Expects a string as input/output but performs no serialization.""" |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return { |
|
"info": serializer_types["StringSerializable"], |
|
"serialized_info": False, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": "Howdy!", |
|
"serialized": "Howdy!", |
|
} |
|
|
|
|
|
class ListStringSerializable(Serializable): |
|
"""Expects a list of strings as input/output but performs no serialization.""" |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return { |
|
"info": serializer_types["ListStringSerializable"], |
|
"serialized_info": False, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": ["Howdy!", "Merhaba"], |
|
"serialized": ["Howdy!", "Merhaba"], |
|
} |
|
|
|
|
|
class BooleanSerializable(Serializable): |
|
"""Expects a boolean as input/output but performs no serialization.""" |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return { |
|
"info": serializer_types["BooleanSerializable"], |
|
"serialized_info": False, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": True, |
|
"serialized": True, |
|
} |
|
|
|
|
|
class NumberSerializable(Serializable): |
|
"""Expects a number (int/float) as input/output but performs no serialization.""" |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return { |
|
"info": serializer_types["NumberSerializable"], |
|
"serialized_info": False, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": 5, |
|
"serialized": 5, |
|
} |
|
|
|
|
|
class ImgSerializable(Serializable): |
|
"""Expects a base64 string as input/output which is serialized to a filepath.""" |
|
|
|
def serialized_info(self): |
|
return { |
|
"type": "string", |
|
"description": "filepath on your computer (or URL) of image", |
|
} |
|
|
|
def api_info(self) -> dict[str, bool | dict]: |
|
return {"info": serializer_types["ImgSerializable"], "serialized_info": True} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": media_data.BASE64_IMAGE, |
|
"serialized": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", |
|
} |
|
|
|
def serialize( |
|
self, |
|
x: str | None, |
|
load_dir: str | Path = "", |
|
allow_links: bool = False, |
|
) -> str | None: |
|
""" |
|
Convert from human-friendly version of a file (string filepath) to a serialized |
|
representation (base64). |
|
Parameters: |
|
x: String path to file to serialize |
|
load_dir: Path to directory containing x |
|
""" |
|
if not x: |
|
return None |
|
if utils.is_http_url_like(x): |
|
return utils.encode_url_to_base64(x) |
|
return utils.encode_file_to_base64(Path(load_dir) / x) |
|
|
|
def deserialize( |
|
self, |
|
x: str | None, |
|
save_dir: str | Path | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> str | None: |
|
""" |
|
Convert from serialized representation of a file (base64) to a human-friendly |
|
version (string filepath). Optionally, save the file to the directory specified by save_dir |
|
Parameters: |
|
x: Base64 representation of image to deserialize into a string filepath |
|
save_dir: Path to directory to save the deserialized image to |
|
root_url: Ignored |
|
hf_token: Ignored |
|
""" |
|
if x is None or x == "": |
|
return None |
|
file = utils.decode_base64_to_file(x, dir=save_dir) |
|
return file.name |
|
|
|
|
|
class FileSerializable(Serializable): |
|
"""Expects a dict with base64 representation of object as input/output which is serialized to a filepath.""" |
|
|
|
def __init__(self) -> None: |
|
self.stream = None |
|
self.stream_name = None |
|
super().__init__() |
|
|
|
def serialized_info(self): |
|
return self._single_file_serialized_info() |
|
|
|
def _single_file_api_info(self): |
|
return { |
|
"info": serializer_types["SingleFileSerializable"], |
|
"serialized_info": True, |
|
} |
|
|
|
def _single_file_serialized_info(self): |
|
return { |
|
"type": "string", |
|
"description": "filepath on your computer (or URL) of file", |
|
} |
|
|
|
def _multiple_file_serialized_info(self): |
|
return { |
|
"type": "array", |
|
"description": "List of filepath(s) or URL(s) to files", |
|
"items": { |
|
"type": "string", |
|
"description": "filepath on your computer (or URL) of file", |
|
}, |
|
} |
|
|
|
def _multiple_file_api_info(self): |
|
return { |
|
"info": serializer_types["MultipleFileSerializable"], |
|
"serialized_info": True, |
|
} |
|
|
|
def api_info(self) -> dict[str, dict | bool]: |
|
return self._single_file_api_info() |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return self._single_file_example_inputs() |
|
|
|
def _single_file_example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": {"is_file": False, "data": media_data.BASE64_FILE}, |
|
"serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf", |
|
} |
|
|
|
def _multiple_file_example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": [{"is_file": False, "data": media_data.BASE64_FILE}], |
|
"serialized": [ |
|
"https://github.com/gradio-app/gradio/raw/main/test/test_files/sample_file.pdf" |
|
], |
|
} |
|
|
|
def _serialize_single( |
|
self, |
|
x: str | FileData | None, |
|
load_dir: str | Path = "", |
|
allow_links: bool = False, |
|
) -> FileData | None: |
|
if x is None or isinstance(x, dict): |
|
return x |
|
if utils.is_http_url_like(x): |
|
filename = x |
|
size = None |
|
else: |
|
filename = str(Path(load_dir) / x) |
|
size = Path(filename).stat().st_size |
|
return { |
|
"name": filename or None, |
|
"data": None |
|
if allow_links |
|
else utils.encode_url_or_file_to_base64(filename), |
|
"orig_name": Path(filename).name, |
|
"size": size, |
|
} |
|
|
|
def _setup_stream(self, url, hf_token): |
|
return utils.download_byte_stream(url, hf_token) |
|
|
|
def _deserialize_single( |
|
self, |
|
x: str | FileData | None, |
|
save_dir: str | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> str | None: |
|
if x is None: |
|
return None |
|
if isinstance(x, str): |
|
file_name = utils.decode_base64_to_file(x, dir=save_dir).name |
|
elif isinstance(x, dict): |
|
if x.get("is_file"): |
|
filepath = x.get("name") |
|
if filepath is None: |
|
raise ValueError(f"The 'name' field is missing in {x}") |
|
if root_url is not None: |
|
file_name = utils.download_tmp_copy_of_file( |
|
root_url + "file=" + filepath, |
|
hf_token=hf_token, |
|
dir=save_dir, |
|
) |
|
else: |
|
file_name = utils.create_tmp_copy_of_file(filepath, dir=save_dir) |
|
elif x.get("is_stream"): |
|
if not (x["name"] and root_url and save_dir): |
|
raise ValueError( |
|
"name and root_url and save_dir must all be present" |
|
) |
|
if not self.stream or self.stream_name != x["name"]: |
|
self.stream = self._setup_stream( |
|
root_url + "stream/" + x["name"], hf_token=hf_token |
|
) |
|
self.stream_name = x["name"] |
|
chunk = next(self.stream) |
|
path = Path(save_dir or tempfile.gettempdir()) / secrets.token_hex(20) |
|
path.mkdir(parents=True, exist_ok=True) |
|
path = path / x.get("orig_name", "output") |
|
path.write_bytes(chunk) |
|
file_name = str(path) |
|
else: |
|
data = x.get("data") |
|
if data is None: |
|
raise ValueError(f"The 'data' field is missing in {x}") |
|
file_name = utils.decode_base64_to_file(data, dir=save_dir).name |
|
else: |
|
raise ValueError( |
|
f"A FileSerializable component can only deserialize a string or a dict, not a {type(x)}: {x}" |
|
) |
|
return file_name |
|
|
|
def serialize( |
|
self, |
|
x: str | FileData | None | list[str | FileData | None], |
|
load_dir: str | Path = "", |
|
allow_links: bool = False, |
|
) -> FileData | None | list[FileData | None]: |
|
""" |
|
Convert from human-friendly version of a file (string filepath) to a |
|
serialized representation (base64) |
|
Parameters: |
|
x: String path to file to serialize |
|
load_dir: Path to directory containing x |
|
allow_links: Will allow path returns instead of raw file content |
|
""" |
|
if x is None or x == "": |
|
return None |
|
if isinstance(x, list): |
|
return [self._serialize_single(f, load_dir, allow_links) for f in x] |
|
else: |
|
return self._serialize_single(x, load_dir, allow_links) |
|
|
|
def deserialize( |
|
self, |
|
x: str | FileData | None | list[str | FileData | None], |
|
save_dir: Path | str | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> str | None | list[str | None]: |
|
""" |
|
Convert from serialized representation of a file (base64) to a human-friendly |
|
version (string filepath). Optionally, save the file to the directory specified by `save_dir` |
|
Parameters: |
|
x: Base64 representation of file to deserialize into a string filepath |
|
save_dir: Path to directory to save the deserialized file to |
|
root_url: If this component is loaded from an external Space, this is the URL of the Space. |
|
hf_token: If this component is loaded from an external private Space, this is the access token for the Space |
|
""" |
|
if x is None: |
|
return None |
|
if isinstance(save_dir, Path): |
|
save_dir = str(save_dir) |
|
if isinstance(x, list): |
|
return [ |
|
self._deserialize_single( |
|
f, save_dir=save_dir, root_url=root_url, hf_token=hf_token |
|
) |
|
for f in x |
|
] |
|
else: |
|
return self._deserialize_single( |
|
x, save_dir=save_dir, root_url=root_url, hf_token=hf_token |
|
) |
|
|
|
|
|
class VideoSerializable(FileSerializable): |
|
def serialized_info(self): |
|
return { |
|
"type": "string", |
|
"description": "filepath on your computer (or URL) of video file", |
|
} |
|
|
|
def api_info(self) -> dict[str, dict | bool]: |
|
return {"info": serializer_types["FileSerializable"], "serialized_info": True} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": {"is_file": False, "data": media_data.BASE64_VIDEO}, |
|
"serialized": "https://github.com/gradio-app/gradio/raw/main/test/test_files/video_sample.mp4", |
|
} |
|
|
|
def serialize( |
|
self, x: str | None, load_dir: str | Path = "", allow_links: bool = False |
|
) -> tuple[FileData | None, None]: |
|
return (super().serialize(x, load_dir, allow_links), None) |
|
|
|
def deserialize( |
|
self, |
|
x: tuple[FileData | None, FileData | None] | None, |
|
save_dir: Path | str | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> str | tuple[str | None, str | None] | None: |
|
""" |
|
Convert from serialized representation of a file (base64) to a human-friendly |
|
version (string filepath). Optionally, save the file to the directory specified by `save_dir` |
|
""" |
|
if isinstance(x, (tuple, list)): |
|
if len(x) != 2: |
|
raise ValueError(f"Expected tuple of length 2. Received: {x}") |
|
x_as_list = [x[0], x[1]] |
|
else: |
|
raise ValueError(f"Expected tuple of length 2. Received: {x}") |
|
deserialized_file = super().deserialize(x_as_list, save_dir, root_url, hf_token) |
|
if isinstance(deserialized_file, list): |
|
return deserialized_file[0] |
|
|
|
|
|
class JSONSerializable(Serializable): |
|
def serialized_info(self): |
|
return {"type": "string", "description": "filepath to JSON file"} |
|
|
|
def api_info(self) -> dict[str, dict | bool]: |
|
return {"info": serializer_types["JSONSerializable"], "serialized_info": True} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": {"a": 1, "b": 2}, |
|
"serialized": None, |
|
} |
|
|
|
def serialize( |
|
self, |
|
x: str | None, |
|
load_dir: str | Path = "", |
|
allow_links: bool = False, |
|
) -> dict | list | None: |
|
""" |
|
Convert from a a human-friendly version (string path to json file) to a |
|
serialized representation (json string) |
|
Parameters: |
|
x: String path to json file to read to get json string |
|
load_dir: Path to directory containing x |
|
""" |
|
if x is None or x == "": |
|
return None |
|
return utils.file_to_json(Path(load_dir) / x) |
|
|
|
def deserialize( |
|
self, |
|
x: str | dict | list, |
|
save_dir: str | Path | None = None, |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> str | None: |
|
""" |
|
Convert from serialized representation (json string) to a human-friendly |
|
version (string path to json file). Optionally, save the file to the directory specified by `save_dir` |
|
Parameters: |
|
x: Json string |
|
save_dir: Path to save the deserialized json file to |
|
root_url: Ignored |
|
hf_token: Ignored |
|
""" |
|
if x is None: |
|
return None |
|
return utils.dict_or_str_to_json_file(x, dir=save_dir).name |
|
|
|
|
|
class GallerySerializable(Serializable): |
|
def serialized_info(self): |
|
return { |
|
"type": "string", |
|
"description": "path to directory with images and a file associating images with captions called captions.json", |
|
} |
|
|
|
def api_info(self) -> dict[str, dict | bool]: |
|
return { |
|
"info": serializer_types["GallerySerializable"], |
|
"serialized_info": True, |
|
} |
|
|
|
def example_inputs(self) -> dict[str, Any]: |
|
return { |
|
"raw": [media_data.BASE64_IMAGE] * 2, |
|
"serialized": [ |
|
"https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png", |
|
] |
|
* 2, |
|
} |
|
|
|
def serialize( |
|
self, x: str | None, load_dir: str | Path = "", allow_links: bool = False |
|
) -> list[list[str | None]] | None: |
|
if x is None or x == "": |
|
return None |
|
files = [] |
|
captions_file = Path(x) / "captions.json" |
|
with captions_file.open("r") as captions_json: |
|
captions = json.load(captions_json) |
|
for file_name, caption in captions.items(): |
|
img = FileSerializable().serialize(file_name, allow_links=allow_links) |
|
files.append([img, caption]) |
|
return files |
|
|
|
def deserialize( |
|
self, |
|
x: list[list[str | None]] | None, |
|
save_dir: str = "", |
|
root_url: str | None = None, |
|
hf_token: str | None = None, |
|
) -> None | str: |
|
if x is None: |
|
return None |
|
gallery_path = Path(save_dir) / str(uuid.uuid4()) |
|
gallery_path.mkdir(exist_ok=True, parents=True) |
|
captions = {} |
|
for img_data in x: |
|
if isinstance(img_data, (list, tuple)): |
|
img_data, caption = img_data |
|
else: |
|
caption = None |
|
name = FileSerializable().deserialize( |
|
img_data, gallery_path, root_url=root_url, hf_token=hf_token |
|
) |
|
captions[name] = caption |
|
captions_file = gallery_path / "captions.json" |
|
with captions_file.open("w") as captions_json: |
|
json.dump(captions, captions_json) |
|
return os.path.abspath(gallery_path) |
|
|
|
|
|
SERIALIZER_MAPPING = {} |
|
for cls in Serializable.__subclasses__(): |
|
SERIALIZER_MAPPING[cls.__name__] = cls |
|
for subcls in cls.__subclasses__(): |
|
SERIALIZER_MAPPING[subcls.__name__] = subcls |
|
|
|
SERIALIZER_MAPPING["Serializable"] = SimpleSerializable |
|
SERIALIZER_MAPPING["File"] = FileSerializable |
|
SERIALIZER_MAPPING["UploadButton"] = FileSerializable |
|
|
|
COMPONENT_MAPPING: dict[str, type] = { |
|
"textbox": StringSerializable, |
|
"number": NumberSerializable, |
|
"slider": NumberSerializable, |
|
"checkbox": BooleanSerializable, |
|
"checkboxgroup": ListStringSerializable, |
|
"radio": StringSerializable, |
|
"dropdown": SimpleSerializable, |
|
"image": ImgSerializable, |
|
"video": FileSerializable, |
|
"audio": FileSerializable, |
|
"file": FileSerializable, |
|
"dataframe": JSONSerializable, |
|
"timeseries": JSONSerializable, |
|
"fileexplorer": JSONSerializable, |
|
"state": SimpleSerializable, |
|
"button": StringSerializable, |
|
"uploadbutton": FileSerializable, |
|
"colorpicker": StringSerializable, |
|
"label": JSONSerializable, |
|
"highlightedtext": JSONSerializable, |
|
"json": JSONSerializable, |
|
"html": StringSerializable, |
|
"gallery": GallerySerializable, |
|
"chatbot": JSONSerializable, |
|
"model3d": FileSerializable, |
|
"plot": JSONSerializable, |
|
"barplot": JSONSerializable, |
|
"lineplot": JSONSerializable, |
|
"scatterplot": JSONSerializable, |
|
"markdown": StringSerializable, |
|
"code": StringSerializable, |
|
"annotatedimage": JSONSerializable, |
|
} |
|
|