Steveeeeeeen's picture
Steveeeeeeen HF staff
Upload folder using huggingface_hub
9f0d781 verified
raw
history blame
21 kB
"""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()
# For backwards compatibility
def input_api_info(self) -> tuple[str, str]:
api_info = self.api_info()
types = api_info.get("serialized_input", [api_info["info"]["type"]] * 2) # type: ignore
return (types[0], types[1])
# For backwards compatibility
def output_api_info(self) -> tuple[str, str]:
api_info = self.api_info()
types = api_info.get("serialized_output", [api_info["info"]["type"]] * 2) # type: ignore
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) # type: ignore
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) # type: ignore
if isinstance(deserialized_file, list):
return deserialized_file[0] # ignore subtitles
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,
}