From 46f8b7ea76c29d03ee3de72e0fc383af099249b1 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 26 Apr 2023 11:09:10 -0400 Subject: [PATCH 01/22] First commit --- client/python/gradio_client/types.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 client/python/gradio_client/types.json diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json new file mode 100644 index 0000000000000..965bb09cd7f19 --- /dev/null +++ b/client/python/gradio_client/types.json @@ -0,0 +1,22 @@ +{ + "StringSerializable": { + "type": "string" + }, + "ListStringSerializable": { + "type": "array", + "items": { + "type": "string" + } + }, + "BooleanSerializable": { + "type": "boolean" + }, + "NumberSerializable": { + "type": "number" + }, + "ImgSerializable": { + "type": "string", + "description": {""} + } + +} \ No newline at end of file From 61e1f1fee5190f34cc83771673d2bc89766cf89b Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 26 Apr 2023 13:25:51 -0400 Subject: [PATCH 02/22] All serializers --- client/python/gradio_client/types.json | 70 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 965bb09cd7f19..686b16eb84621 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -16,7 +16,71 @@ }, "ImgSerializable": { "type": "string", - "description": {""} - } - + "description": "base64 representation of an image" + }, + "FileSerializable": { + "oneOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": {"type": "string", "description": "name of file"}, + "data": {"type": "string", "description": "base64 representation of file"}, + "size": {"type": "integer", "description": "size of image in bytes"}, + "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, + "orig_name": {"type": "string", "description": "original name of the file"} + }, + "required": ["name", "data"] + }, + {"type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": {"type": "string", "description": "name of file"}, + "data": {"type": "string", "description": "base64 representation of file"}, + "size": {"type": "integer", "description": "size of image in bytes"}, + "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, + "orig_name": {"type": "string", "description": "original name of the file"} + }, + "required": ["name", "data"] + } + ] + }} + ] + }, + "JSONSerializable": { + "type": {}, + "description": "any valid json" + }, + "GallerySerializable": { + "type": "array", + "items": { + "type": "array", + "items": false, + "maxSize": 2, + "minSize": 2, + "prefixItems": [{ + "type": "object", + "properties": { + "name": {"type": "string", "description": "name of file"}, + "data": {"type": "string", "description": "base64 representation of file"}, + "size": {"type": "integer", "description": "size of image in bytes"}, + "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, + "orig_name": {"type": "string", "description": "original name of the file"} + }, + "required": ["name", "data"] + }, + {"oneOf": [{"type": "string", "description": "caption of image"}, {"type": "null"}]} + ] + } + }, } \ No newline at end of file From 7a021e0a0b7f1387da6c2ac1982ec936cf9ce7a3 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 26 Apr 2023 13:43:36 -0400 Subject: [PATCH 03/22] Remove output type --- client/python/gradio_client/types.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 686b16eb84621..8c60369010b66 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -82,5 +82,5 @@ {"oneOf": [{"type": "string", "description": "caption of image"}, {"type": "null"}]} ] } - }, + } } \ No newline at end of file From c658d541e94aafd9214139b815461180151b2dec Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Tue, 2 May 2023 11:47:31 -0400 Subject: [PATCH 04/22] Add route --- client/python/gradio_client/client.py | 4 +- client/python/gradio_client/serializing.py | 96 +++------------------- client/python/gradio_client/types.json | 4 + client/python/test/test_client.py | 12 ++- gradio/blocks.py | 27 ++---- 5 files changed, 30 insertions(+), 113 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 6513931aac1b9..e86d5f47d6430 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -451,12 +451,12 @@ def _render_endpoints_info( name_or_index: str | int, endpoints_info: Dict[str, List[Dict[str, str]]], ) -> str: - parameter_names = list(p["label"] for p in endpoints_info["parameters"]) + parameter_names = [p["label"] for p in endpoints_info["parameters"]] parameter_names = [utils.sanitize_parameter_names(p) for p in parameter_names] rendered_parameters = ", ".join(parameter_names) if rendered_parameters: rendered_parameters = rendered_parameters + ", " - return_values = list(p["label"] for p in endpoints_info["returns"]) + return_values = [p["label"] for p in endpoints_info["returns"]] return_values = [utils.sanitize_parameter_names(r) for r in return_values] rendered_return_values = ", ".join(return_values) if len(return_values) > 1: diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index d313256d1f687..f8d1b6356672f 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -10,6 +10,8 @@ from gradio_client import media_data, utils from gradio_client.data_classes import FileData +serializer_types = json.load(open(Path(__file__).parent / "types.json")) + class Serializable(ABC): def api_info(self) -> Dict[str, List[str]]: @@ -59,12 +61,7 @@ class SimpleSerializable(Serializable): """General class that does not perform any serialization or deserialization.""" def api_info(self) -> Dict[str, str | List[str]]: - return { - "raw_input": ["Any", ""], - "raw_output": ["Any", ""], - "serialized_input": ["Any", ""], - "serialized_output": ["Any", ""], - } + return serializer_types["SimpleSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -77,12 +74,7 @@ class StringSerializable(Serializable): """Expects a string as input/output but performs no serialization.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - } + return serializer_types["StringSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -95,12 +87,7 @@ class ListStringSerializable(Serializable): """Expects a list of strings as input/output but performs no serialization.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["List[str]", "list of string values"], - "raw_output": ["List[str]", "list of string values"], - "serialized_input": ["List[str]", "list of string values"], - "serialized_output": ["List[str]", "list of string values"], - } + return serializer_types["ListStringSerializer"] def example_inputs(self) -> Dict[str, Any]: return { @@ -113,12 +100,7 @@ class BooleanSerializable(Serializable): """Expects a boolean as input/output but performs no serialization.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["bool", "boolean value"], - "raw_output": ["bool", "boolean value"], - "serialized_input": ["bool", "boolean value"], - "serialized_output": ["bool", "boolean value"], - } + return serializer_types["BooleanSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -131,12 +113,7 @@ class NumberSerializable(Serializable): """Expects a number (int/float) as input/output but performs no serialization.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["int | float", "numeric value"], - "raw_output": ["int | float", "numeric value"], - "serialized_input": ["int | float", "numeric value"], - "serialized_output": ["int | float", "numeric value"], - } + return serializer_types["NumberSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -149,12 +126,7 @@ class ImgSerializable(Serializable): """Expects a base64 string as input/output which is serialized to a filepath.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["str", "base64 representation of image"], - "raw_output": ["str", "base64 representation of image"], - "serialized_input": ["str", "filepath or URL to image"], - "serialized_output": ["str", "filepath or URL to image"], - } + return serializer_types["ImgSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -206,18 +178,7 @@ class FileSerializable(Serializable): """Expects a dict with base64 representation of object as input/output which is serialized to a filepath.""" def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": [ - "str | Dict", - "base64 string representation of file; or a dictionary-like object, the keys should be either: is_file (False), data (base64 representation of file) or is_file (True), name (str filename)", - ], - "raw_output": [ - "Dict", - "dictionary-like object with keys: name (str filename), data (base64 representation of file), is_file (bool, set to False)", - ], - "serialized_input": ["str", "filepath or URL to file"], - "serialized_output": ["str", "filepath or URL to file"], - } + return serializer_types["FileSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -333,18 +294,7 @@ def deserialize( class VideoSerializable(FileSerializable): def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": [ - "str | Dict", - "base64 string representation of file; or a dictionary-like object, the keys should be either: is_file (False), data (base64 representation of file) or is_file (True), name (str filename)", - ], - "raw_output": [ - "Tuple[Dict, Dict]", - "a tuple of 2 dictionary-like object with keys: name (str filename), data (base64 representation of file), is_file (bool, set to False). First dictionary is for the video, second dictionary is for the subtitles.", - ], - "serialized_input": ["str", "filepath or URL to file"], - "serialized_output": ["str", "filepath or URL to file"], - } + return serializer_types["FileSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -380,12 +330,7 @@ def deserialize( class JSONSerializable(Serializable): def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": ["str | Dict | List", "JSON-serializable object or a string"], - "raw_output": ["Dict | List", "dictionary- or list-like object"], - "serialized_input": ["str", "filepath to JSON file"], - "serialized_output": ["str", "filepath to JSON file"], - } + return serializer_types["JSONSerializable"] def example_inputs(self) -> Dict[str, Any]: return { @@ -432,24 +377,7 @@ def deserialize( class GallerySerializable(Serializable): def api_info(self) -> Dict[str, List[str]]: - return { - "raw_input": [ - "List[List[str | None]]", - "List of lists. The inner lists should contain two elements: a base64 file representation and an optional caption, the outer list should contain one such list for each image in the gallery.", - ], - "raw_output": [ - "List[List[str | None]]", - "List of lists. The inner lists should contain two elements: a base64 file representation and an optional caption, the outer list should contain one such list for each image in the gallery.", - ], - "serialized_input": [ - "str", - "path to directory with images and a file associating images with captions called captions.json", - ], - "serialized_output": [ - "str", - "path to directory with images and a file associating images with captions called captions.json", - ], - } + return serializer_types["JSONSerializable"] def example_inputs(self) -> Dict[str, Any]: return { diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 8c60369010b66..1f1ee9d5e6cd9 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -1,4 +1,8 @@ { + "SimpleSerializable": { + "type": {}, + "description": "any valid json" + }, "StringSerializable": { "type": "string" }, diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index 2bbb5a1056239..3be9ee5065f7b 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -133,7 +133,7 @@ def test_intermediate_outputs(self, count_generator_demo): def test_break_in_loop_if_error(self, calculator_demo): with connect(calculator_demo) as client: job = client.submit("foo", "add", 4, fn_index=0) - output = [o for o in job] + output = list(job) assert output == [] @pytest.mark.flaky @@ -268,7 +268,7 @@ def test_upload_file_private_space(self): f.write("Hello from private space!") output = client.submit(f.name, api_name="/upload_btn").result() - open(output).read() == "Hello from private space!" + assert open(output).read() == "Hello from private space!" upload.assert_called_once() with patch.object( @@ -301,7 +301,7 @@ def test_upload_file_upload_route_does_not_exist(self): f.write("Hello from private space!") output = client.submit(1, "foo", f.name, fn_index=0).result() - open(output).read() == "Hello from private space!" + assert open(output).read() == "Hello from private space!" serialize.assert_called_once_with(1, "foo", f.name) @@ -589,10 +589,8 @@ def test_numerical_to_label_space(self): def test_serializable_in_mapping(self, calculator_demo): with connect(calculator_demo) as client: assert all( - [ - isinstance(c, SimpleSerializable) - for c in client.endpoints[0].serializers - ] + isinstance(c, SimpleSerializable) + for c in client.endpoints[0].serializers ) @pytest.mark.flaky diff --git a/gradio/blocks.py b/gradio/blocks.py index cbc160129af59..5b14c862e0a2d 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -495,26 +495,17 @@ def get_api_info(config: Dict, serialize: bool = True): # of the component), so we use that if it exists. Otherwise, we fallback to the # Serializer's API info. if component.get("api_info"): - if serialize: - info = component["api_info"]["serialized_input"] - example = component["example_inputs"]["serialized"] - else: - info = component["api_info"]["raw_input"] - example = component["example_inputs"]["raw"] + info = component["api_info"] + example = component["example_inputs"]["serialized"] else: serializer = serializing.COMPONENT_MAPPING[type]() assert isinstance(serializer, serializing.Serializable) - if serialize: - info = serializer.api_info()["serialized_input"] - example = serializer.example_inputs()["serialized"] - else: - info = serializer.api_info()["raw_input"] - example = serializer.example_inputs()["raw"] + info = serializer.api_info() + example = serializer.example_inputs()["raw"] dependency_info["parameters"].append( { "label": label, - "type_python": info[0], - "type_description": info[1], + "type": info, "component": type.capitalize(), "example_input": example, } @@ -540,15 +531,11 @@ def get_api_info(config: Dict, serialize: bool = True): label = component["props"].get("label", f"value_{o}") serializer = serializing.COMPONENT_MAPPING[type]() assert isinstance(serializer, serializing.Serializable) - if serialize: - info = serializer.api_info()["serialized_output"] - else: - info = serializer.api_info()["raw_output"] + info = serializer.api_info() dependency_info["returns"].append( { "label": label, - "type_python": info[0], - "type_description": info[1], + "type": info, "component": type.capitalize(), } ) From 41dcd56b0d8d1153471fd0394680c7e6c3ed3b7a Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Tue, 2 May 2023 12:03:36 -0400 Subject: [PATCH 05/22] Format json --- client/python/gradio_client/types.json | 222 +++++++++++++++---------- 1 file changed, 133 insertions(+), 89 deletions(-) diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 1f1ee9d5e6cd9..6ba436b544792 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -1,90 +1,134 @@ { - "SimpleSerializable": { - "type": {}, - "description": "any valid json" - }, - "StringSerializable": { - "type": "string" - }, - "ListStringSerializable": { - "type": "array", - "items": { - "type": "string" - } - }, - "BooleanSerializable": { - "type": "boolean" - }, - "NumberSerializable": { - "type": "number" - }, - "ImgSerializable": { - "type": "string", - "description": "base64 representation of an image" - }, - "FileSerializable": { - "oneOf": [ - { - "type": "string", - "description": "filepath or URL to file" - }, - { - "type": "object", - "properties": { - "name": {"type": "string", "description": "name of file"}, - "data": {"type": "string", "description": "base64 representation of file"}, - "size": {"type": "integer", "description": "size of image in bytes"}, - "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, - "orig_name": {"type": "string", "description": "original name of the file"} - }, - "required": ["name", "data"] - }, - {"type": "array", - "items": { - "anyOf": [ - { - "type": "string", - "description": "filepath or URL to file" - }, - { - "type": "object", - "properties": { - "name": {"type": "string", "description": "name of file"}, - "data": {"type": "string", "description": "base64 representation of file"}, - "size": {"type": "integer", "description": "size of image in bytes"}, - "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, - "orig_name": {"type": "string", "description": "original name of the file"} - }, - "required": ["name", "data"] - } - ] - }} - ] - }, - "JSONSerializable": { - "type": {}, - "description": "any valid json" - }, - "GallerySerializable": { - "type": "array", - "items": { - "type": "array", - "items": false, - "maxSize": 2, - "minSize": 2, - "prefixItems": [{ - "type": "object", - "properties": { - "name": {"type": "string", "description": "name of file"}, - "data": {"type": "string", "description": "base64 representation of file"}, - "size": {"type": "integer", "description": "size of image in bytes"}, - "is_file": {"type": "boolean", "description": "true if the file has been uploaded to the server"}, - "orig_name": {"type": "string", "description": "original name of the file"} - }, - "required": ["name", "data"] - }, - {"oneOf": [{"type": "string", "description": "caption of image"}, {"type": "null"}]} - ] - } - } -} \ No newline at end of file + "SimpleSerializable": { + "type": {}, + "description": "any valid json" + }, + "StringSerializable": { + "type": "string" + }, + "ListStringSerializable": { + "type": "array", + "items": { + "type": "string" + } + }, + "BooleanSerializable": { + "type": "boolean" + }, + "NumberSerializable": { + "type": "number" + }, + "ImgSerializable": { + "type": "string", + "description": "base64 representation of an image" + }, + "FileSerializable": { + "oneOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": { "type": "string", "description": "name of file" }, + "data": { + "type": "string", + "description": "base64 representation of file" + }, + "size": { + "type": "integer", + "description": "size of image in bytes" + }, + "is_file": { + "type": "boolean", + "description": "true if the file has been uploaded to the server" + }, + "orig_name": { + "type": "string", + "description": "original name of the file" + } + }, + "required": ["name", "data"] + }, + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": { "type": "string", "description": "name of file" }, + "data": { + "type": "string", + "description": "base64 representation of file" + }, + "size": { + "type": "integer", + "description": "size of image in bytes" + }, + "is_file": { + "type": "boolean", + "description": "true if the file has been uploaded to the server" + }, + "orig_name": { + "type": "string", + "description": "original name of the file" + } + }, + "required": ["name", "data"] + } + ] + } + } + ] + }, + "JSONSerializable": { + "type": {}, + "description": "any valid json" + }, + "GallerySerializable": { + "type": "array", + "items": { + "type": "array", + "items": false, + "maxSize": 2, + "minSize": 2, + "prefixItems": [ + { + "type": "object", + "properties": { + "name": { "type": "string", "description": "name of file" }, + "data": { + "type": "string", + "description": "base64 representation of file" + }, + "size": { + "type": "integer", + "description": "size of image in bytes" + }, + "is_file": { + "type": "boolean", + "description": "true if the file has been uploaded to the server" + }, + "orig_name": { + "type": "string", + "description": "original name of the file" + } + }, + "required": ["name", "data"] + }, + { + "oneOf": [ + { "type": "string", "description": "caption of image" }, + { "type": "null" } + ] + } + ] + } + } +} From 2581fb53bb1d4d78f3228cf17f7a4c84ae52781c Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Tue, 2 May 2023 13:13:47 -0400 Subject: [PATCH 06/22] Modify dropdown and slider choices --- gradio/components.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index 0b5c70a138266..1080dc956eca6 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -843,12 +843,9 @@ def __init__( self.cleared_value = self.value def api_info(self) -> Dict[str, Tuple[str, str]]: - description = f"numeric value between {self.minimum} and {self.maximum}" return { - "raw_input": ("int | float", description), - "raw_output": ("int | float", description), - "serialized_input": ("int | float", description), - "serialized_output": ("int | float", description), + "type": "number", + "description": f"numeric value between {self.minimum} and {self.maximum}", } def example_inputs(self) -> Dict[str, Any]: @@ -1486,17 +1483,14 @@ def __init__( def api_info(self) -> Dict[str, Tuple[str, str]]: if self.multiselect: - type = "List[str]" - description = f"List of options from: {self.choices}" + type = { + "type": "array", + "items": {"type": "string"}, + "description": f"List of options from: {self.choices}", + } else: - type = "str" - description = f"Option from: {self.choices}" - return { - "raw_input": (type, description), - "raw_output": (type, description), - "serialized_input": (type, description), - "serialized_output": (type, description), - } + type = {"type": "string", "description": f"Option from: {self.choices}"} + return type def example_inputs(self) -> Dict[str, Any]: if self.multiselect: From b3a164929fd01e4ce725aca7c60d5a955b2c359d Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Tue, 2 May 2023 16:28:18 -0400 Subject: [PATCH 07/22] Fix impl --- client/python/gradio_client/client.py | 54 +++++++++++++++++--- client/python/gradio_client/serializing.py | 27 +++++++++- client/python/gradio_client/utils.py | 58 ++++++++++++++++++++++ gradio/utils.py | 4 ++ 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index e86d5f47d6430..fd2bdebac32fc 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -30,7 +30,13 @@ from gradio_client import serializing, utils from gradio_client.documentation import document, set_documentation_group from gradio_client.serializing import Serializable -from gradio_client.utils import Communicator, JobStatus, Status, StatusUpdate +from gradio_client.utils import ( + Communicator, + JobStatus, + Status, + StatusUpdate, + json_schema_to_python_type, +) set_documentation_group("py-client") @@ -393,10 +399,7 @@ def view_api( } """ - if self.serialize: - api_info_url = urllib.parse.urljoin(self.src, utils.API_INFO_URL) - else: - api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) + api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) r = requests.get(api_info_url, headers=self.headers) # Versions of Gradio older than 3.26 returned format of the API info @@ -409,7 +412,7 @@ def view_api( else: fetch = requests.post( utils.SPACE_FETCHER_URL, - json={"serialize": self.serialize, "config": json.dumps(self.config)}, + json={"config": json.dumps(self.config)}, ) if fetch.ok: info = fetch.json()["api"] @@ -424,11 +427,34 @@ def view_api( human_info += f"Named API endpoints: {num_named_endpoints}\n" for api_name, endpoint_info in info["named_endpoints"].items(): + if self.serialize: + inferred_fn_index = self._infer_fn_index(api_name, None) + for v, s in zip( + endpoint_info["parameters"], + self.endpoints[inferred_fn_index].serializers, + ): + v["type"] = s.serialized_info() + for v, s in zip( + endpoint_info["returns"], + self.endpoints[inferred_fn_index].deserializers, + ): + v["type"] = s.serialized_info() human_info += self._render_endpoints_info(api_name, endpoint_info) if all_endpoints: human_info += f"\nUnnamed API endpoints: {num_unnamed_endpoints}\n" for fn_index, endpoint_info in info["unnamed_endpoints"].items(): + if self.serialize: + for v, s in zip( + endpoint_info["parameters"], + self.endpoints[int(fn_index)].serializers, + ): + v["type"] = s.serialized_info() + for v, s in zip( + endpoint_info["returns"], + self.endpoints[int(fn_index)].deserializers, + ): + v["type"] = s.serialized_info() # When loading from json, the fn_indices are read as strings # because json keys can only be strings human_info += self._render_endpoints_info(int(fn_index), endpoint_info) @@ -473,13 +499,25 @@ def _render_endpoints_info( human_info += " Parameters:\n" if endpoints_info["parameters"]: for info in endpoints_info["parameters"]: - human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {info['type_python']} ({info['type_description']})\n" + desc = ( + f" ({info['type']['description']})" + if "description" in info["type"] + else "" + ) + type_ = json_schema_to_python_type(info["type"]) + human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {type_}{desc} \n" else: human_info += " - None\n" human_info += " Returns:\n" if endpoints_info["returns"]: for info in endpoints_info["returns"]: - human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {info['type_python']} ({info['type_description']})\n" + desc = ( + f" ({info['type']['description']})" + if "description" in info["type"] + else "" + ) + type_ = json_schema_to_python_type(info["type"]) + human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {type_}{desc} \n" else: human_info += " - None\n" diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index d363e57ee4afb..c9b45efe8cc9e 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -13,6 +13,13 @@ 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]. @@ -124,6 +131,9 @@ def example_inputs(self) -> Dict[str, Any]: 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 or URL to image"} + def api_info(self) -> Dict[str, List[str]]: return serializer_types["ImgSerializable"] @@ -176,6 +186,9 @@ def deserialize( class FileSerializable(Serializable): """Expects a dict with base64 representation of object as input/output which is serialized to a filepath.""" + def serialized_info(self): + return {"type": "string", "description": "filepath or URL to file"} + def api_info(self) -> Dict[str, List[str]]: return serializer_types["FileSerializable"] @@ -292,6 +305,9 @@ def deserialize( class VideoSerializable(FileSerializable): + def serialized_info(self): + return {"type": "string", "description": "filepath or URL to video file"} + def api_info(self) -> Dict[str, List[str]]: return serializer_types["FileSerializable"] @@ -328,6 +344,9 @@ def deserialize( class JSONSerializable(Serializable): + def serialized_info(self): + return {"type": "string", "description": "filepath to JSON file"} + def api_info(self) -> Dict[str, List[str]]: return serializer_types["JSONSerializable"] @@ -375,8 +394,14 @@ def deserialize( class GallerySerializable(Serializable): + def serialized_info(self): + return { + "type": "str", + "description": "path to directory with images and a file associating images with captions called captions.json", + } + def api_info(self) -> Dict[str, List[str]]: - return serializer_types["JSONSerializable"] + return serializer_types["GallerySerializable"] def example_inputs(self) -> Dict[str, Any]: return { diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index b5f298a91601f..6eeddaef29846 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -486,3 +486,61 @@ def synchronize_async(func: Callable, *args, **kwargs) -> Any: **kwargs: """ return fsspec.asyn.sync(fsspec.asyn.get_loop(), func, *args, **kwargs) # type: ignore + + +class APIInfoParseError(ValueError): + pass + + +def get_type(schema: Dict): + if "type" in schema: + return schema["type"] + elif schema.get("oneOf"): + return "oneOf" + elif schema.get("anyOf"): + return "anyOf" + else: + raise APIInfoParseError(f"Cannot parse type for {schema}") + + +def json_schema_to_python_type(schema: Any) -> str: + """Convert the json schema into a python type hint""" + type_ = get_type(schema) + if type_ == {}: + if "json" in schema["description"]: + return "Dict[Any, Any]" + else: + return "Any" + elif type_ == "null": + return "None" + elif type_ == "integer": + return "int" + elif type_ == "string": + return "str" + elif type_ == "boolean": + return "bool" + elif type_ == "number": + return "Union[int, float]" + elif type_ == "array": + items = schema.get("items") + if "prefixItems" in items: + elements = ", ".join( + [json_schema_to_python_type(i) for i in items["prefixItems"]] + ) + return f"Tuple[{elements}]" + else: + elements = json_schema_to_python_type(items) + return f"List[{elements}]" + elif type_ == "object": + des = ", ".join( + [ + f"{n}: {json_schema_to_python_type(v)} ({v.get('description')})" + for n, v in schema["properties"].items() + ] + ) + return f"Dict({des})" + elif type_ in ["oneOf", "anyOf"]: + desc = ", ".join([json_schema_to_python_type(i) for i in schema[type_]]) + return f"Union[{desc}]" + else: + raise APIInfoParseError(f"Cannot parse schema {schema}") diff --git a/gradio/utils.py b/gradio/utils.py index ac9400cbe81cc..cf61eee35972e 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -40,6 +40,7 @@ import httpx import matplotlib import requests +from gradio_client.serializing import Serializable from markdown_it import MarkdownIt from mdit_py_plugins.dollarmath.index import dollarmath_plugin from mdit_py_plugins.footnote.index import footnote_plugin @@ -976,6 +977,9 @@ def get_class_that_defined_method(meth: Callable): and getattr(meth.__self__, "__class__", None) ): for cls in inspect.getmro(meth.__self__.__class__): + # Find the first serializer defined in gradio_client that + if issubclass(cls, Serializable) and "gradio_client" in cls.__module__: + return cls if meth.__name__ in cls.__dict__: return cls meth = getattr(meth, "__func__", meth) # fallback to __qualname__ parsing From 37c188b58d4b2e4242e2006a5e4a75dc59ecc152 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 11:25:09 -0400 Subject: [PATCH 08/22] Lint --- client/python/gradio_client/client.py | 17 ++++-- client/python/gradio_client/serializing.py | 60 ++++++++++++++-------- gradio/blocks.py | 7 ++- gradio/components.py | 9 ++-- 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index fd2bdebac32fc..0e7d91b05f71d 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -426,6 +426,9 @@ def view_api( human_info = "Client.predict() Usage Info\n---------------------------\n" human_info += f"Named API endpoints: {num_named_endpoints}\n" + # if serialize=True, use the serialized info from the serializer + # unless the component tells us it does not have different serialized + # data format, e.g. has_serialized_info=False in config for api_name, endpoint_info in info["named_endpoints"].items(): if self.serialize: inferred_fn_index = self._infer_fn_index(api_name, None) @@ -433,12 +436,14 @@ def view_api( endpoint_info["parameters"], self.endpoints[inferred_fn_index].serializers, ): - v["type"] = s.serialized_info() + if v["has_serialized_info"]: + v["type"] = s.serialized_info() for v, s in zip( endpoint_info["returns"], self.endpoints[inferred_fn_index].deserializers, ): - v["type"] = s.serialized_info() + if v["has_serialized_info"]: + v["type"] = s.serialized_info() human_info += self._render_endpoints_info(api_name, endpoint_info) if all_endpoints: @@ -449,12 +454,14 @@ def view_api( endpoint_info["parameters"], self.endpoints[int(fn_index)].serializers, ): - v["type"] = s.serialized_info() + if v["has_serialized_info"]: + v["type"] = s.serialized_info() for v, s in zip( endpoint_info["returns"], self.endpoints[int(fn_index)].deserializers, ): - v["type"] = s.serialized_info() + if v["has_serialized_info"]: + v["type"] = s.serialized_info() # When loading from json, the fn_indices are read as strings # because json keys can only be strings human_info += self._render_endpoints_info(int(fn_index), endpoint_info) @@ -475,7 +482,7 @@ def reset_session(self) -> None: def _render_endpoints_info( self, name_or_index: str | int, - endpoints_info: Dict[str, List[Dict[str, str]]], + endpoints_info: Dict[str, List[Dict[str, Any]]], ) -> str: parameter_names = [p["label"] for p in endpoints_info["parameters"]] parameter_names = [utils.sanitize_parameter_names(p) for p in parameter_names] diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index c9b45efe8cc9e..2cef426f27c74 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -66,8 +66,11 @@ def deserialize( class SimpleSerializable(Serializable): """General class that does not perform any serialization or deserialization.""" - def api_info(self) -> Dict[str, str | List[str]]: - return serializer_types["SimpleSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["SimpleSerializable"], + "serialized_info": False, + } def example_inputs(self) -> Dict[str, Any]: return { @@ -79,8 +82,11 @@ def example_inputs(self) -> Dict[str, Any]: class StringSerializable(Serializable): """Expects a string as input/output but performs no serialization.""" - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["StringSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["StringSerializable"], + "serialized_info": False, + } def example_inputs(self) -> Dict[str, Any]: return { @@ -92,8 +98,11 @@ def example_inputs(self) -> Dict[str, Any]: class ListStringSerializable(Serializable): """Expects a list of strings as input/output but performs no serialization.""" - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["ListStringSerializer"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["ListStringSerializer"], + "serialized_info": False, + } def example_inputs(self) -> Dict[str, Any]: return { @@ -105,10 +114,13 @@ def example_inputs(self) -> Dict[str, Any]: class BooleanSerializable(Serializable): """Expects a boolean as input/output but performs no serialization.""" - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["BooleanSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["BooleanSerializable"], + "serialized_info": False, + } - def example_inputs(self) -> Dict[str, Any]: + def example_inputs(self) -> Dict[str, Dict | bool]: return { "raw": True, "serialized": True, @@ -118,8 +130,11 @@ def example_inputs(self) -> Dict[str, Any]: class NumberSerializable(Serializable): """Expects a number (int/float) as input/output but performs no serialization.""" - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["NumberSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["NumberSerializable"], + "serialized_info": False, + } def example_inputs(self) -> Dict[str, Any]: return { @@ -134,8 +149,8 @@ class ImgSerializable(Serializable): def serialized_info(self): return {"type": "string", "description": "filepath or URL to image"} - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["ImgSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return {"info": serializer_types["ImgSerializable"], "serialized_info": True} def example_inputs(self) -> Dict[str, Any]: return { @@ -189,8 +204,8 @@ class FileSerializable(Serializable): def serialized_info(self): return {"type": "string", "description": "filepath or URL to file"} - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["FileSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return {"info": serializer_types["FileSerializable"], "serialized_info": True} def example_inputs(self) -> Dict[str, Any]: return { @@ -308,8 +323,8 @@ class VideoSerializable(FileSerializable): def serialized_info(self): return {"type": "string", "description": "filepath or URL to video file"} - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["FileSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return {"info": serializer_types["FileSerializable"], "serialized_info": True} def example_inputs(self) -> Dict[str, Any]: return { @@ -347,8 +362,8 @@ class JSONSerializable(Serializable): def serialized_info(self): return {"type": "string", "description": "filepath to JSON file"} - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["JSONSerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return {"info": serializer_types["JSONSerializable"], "serialized_info": True} def example_inputs(self) -> Dict[str, Any]: return { @@ -400,8 +415,11 @@ def serialized_info(self): "description": "path to directory with images and a file associating images with captions called captions.json", } - def api_info(self) -> Dict[str, List[str]]: - return serializer_types["GallerySerializable"] + def api_info(self) -> Dict[str, Dict | bool]: + return { + "info": serializer_types["GallerySerializable"], + "serialized_info": True, + } def example_inputs(self) -> Dict[str, Any]: return { diff --git a/gradio/blocks.py b/gradio/blocks.py index 5b14c862e0a2d..6e33148373e30 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -502,10 +502,12 @@ def get_api_info(config: Dict, serialize: bool = True): assert isinstance(serializer, serializing.Serializable) info = serializer.api_info() example = serializer.example_inputs()["raw"] + breakpoint() dependency_info["parameters"].append( { "label": label, - "type": info, + "type": info["info"], + "has_serialized_info": info["serialized_info"], "component": type.capitalize(), "example_input": example, } @@ -535,7 +537,8 @@ def get_api_info(config: Dict, serialize: bool = True): dependency_info["returns"].append( { "label": label, - "type": info, + "type": info["info"], + "has_serialized_info": info["serialized_info"], "component": type.capitalize(), } ) diff --git a/gradio/components.py b/gradio/components.py index 1080dc956eca6..e51d9b6284350 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -844,8 +844,11 @@ def __init__( def api_info(self) -> Dict[str, Tuple[str, str]]: return { - "type": "number", - "description": f"numeric value between {self.minimum} and {self.maximum}", + "info": { + "type": "number", + "description": f"numeric value between {self.minimum} and {self.maximum}", + }, + "serialized_info": False, } def example_inputs(self) -> Dict[str, Any]: @@ -1490,7 +1493,7 @@ def api_info(self) -> Dict[str, Tuple[str, str]]: } else: type = {"type": "string", "description": f"Option from: {self.choices}"} - return type + return {"info": type, "serialized_info": False} def example_inputs(self) -> Dict[str, Any]: if self.multiselect: From 67724b5055a9c5fed957fd83798d0ba84c7feee0 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 12:44:54 -0400 Subject: [PATCH 09/22] Add tests --- client/python/gradio_client/serializing.py | 2 +- client/python/gradio_client/types.json | 2 +- client/python/test/test_serializing.py | 14 ++++++++++- client/python/test/test_utils.py | 28 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index 2cef426f27c74..33f5aab656e9c 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -100,7 +100,7 @@ class ListStringSerializable(Serializable): def api_info(self) -> Dict[str, Dict | bool]: return { - "info": serializer_types["ListStringSerializer"], + "info": serializer_types["ListStringSerializable"], "serialized_info": False, } diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 6ba436b544792..bc0756bb6f9be 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -1,7 +1,7 @@ { "SimpleSerializable": { "type": {}, - "description": "any valid json" + "description": "any valid value" }, "StringSerializable": { "type": "string" diff --git a/client/python/test/test_serializing.py b/client/python/test/test_serializing.py index a1cef1accfa04..e23adf728fa50 100644 --- a/client/python/test/test_serializing.py +++ b/client/python/test/test_serializing.py @@ -1,12 +1,24 @@ import os import tempfile +import pytest from gradio import components -from gradio_client.serializing import COMPONENT_MAPPING, FileSerializable +from gradio_client.serializing import COMPONENT_MAPPING, FileSerializable, Serializable from gradio_client.utils import encode_url_or_file_to_base64 +@pytest.mark.parametrize("serializer_class", Serializable.__subclasses__()) +def test_duplicate(serializer_class): + if "gradio_client" not in serializer_class.__module__: + pytest.skip(f"{serializer_class} not defined in gradio_client") + serialzier = serializer_class() + info = serialzier.api_info() + assert "info" in info and "serialized_info" in info + if "serialized_info" in info: + assert serialzier.serialized_info() + + def test_check_component_fallback_serializers(): for component_name, class_type in COMPONENT_MAPPING.items(): if component_name == "dataset": # cannot be instantiated without parameters diff --git a/client/python/test/test_utils.py b/client/python/test/test_utils.py index f3b5f09bf9bbb..05d48f2cf19f3 100644 --- a/client/python/test/test_utils.py +++ b/client/python/test/test_utils.py @@ -1,3 +1,4 @@ +import importlib.resources import json import tempfile from copy import deepcopy @@ -9,6 +10,8 @@ from gradio_client import media_data, utils +types = json.loads(importlib.resources.read_text("gradio_client", "types.json")) + def test_encode_url_or_file_to_base64(): output_base64 = utils.encode_url_or_file_to_base64( @@ -120,3 +123,28 @@ def test_sleep_successful(mock_post): def test_sleep_unsuccessful(mock_post): with pytest.raises(utils.SpaceDuplicationError): utils.set_space_timeout("gradio/calculator") + + +@pytest.mark.parametrize("schema", types) +def test_json_schema_to_python_type(schema): + if schema == "SimpleSerializable": + answer = "Any" + elif schema == "StringSerializable": + answer = "str" + elif schema == "ListStringSerializable": + answer = "List[str]" + elif schema == "BooleanSerializable": + answer = "bool" + elif schema == "NumberSerializable": + answer = "Union[int, float]" + elif schema == "ImgSerializable": + answer = "str" + elif schema == "FileSerializable": + answer = "Union[str, Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), List[Union[str, Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file))]]]" + elif schema == "JSONSerializable": + answer = "Dict[Any, Any]" + elif schema == "GallerySerializable": + answer = "Tuple[Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), Union[str, None]]" + else: + raise ValueError(f"This test has not been modified to check {schema}") + assert utils.json_schema_to_python_type(types[schema]) == answer From b603caf006803bebc80d5796394c955135cf734f Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 12:48:31 -0400 Subject: [PATCH 10/22] Fix lint --- gradio/components.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index e51d9b6284350..69888613fb009 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -842,7 +842,7 @@ def __init__( NeighborInterpretable.__init__(self) self.cleared_value = self.value - def api_info(self) -> Dict[str, Tuple[str, str]]: + def api_info(self) -> Dict[str, Dict | bool]: return { "info": { "type": "number", @@ -1484,7 +1484,7 @@ def __init__( self.cleared_value = self.value or ([] if multiselect else "") - def api_info(self) -> Dict[str, Tuple[str, str]]: + def api_info(self) -> Dict[str, Dict | bool]: if self.multiselect: type = { "type": "array", From 95e176baf7a6d678073a81e289c71cfc0a5d3957 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 12:52:46 -0400 Subject: [PATCH 11/22] remove breakpoint --- gradio/blocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gradio/blocks.py b/gradio/blocks.py index 6e33148373e30..28c7aba59fdae 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -502,7 +502,6 @@ def get_api_info(config: Dict, serialize: bool = True): assert isinstance(serializer, serializing.Serializable) info = serializer.api_info() example = serializer.example_inputs()["raw"] - breakpoint() dependency_info["parameters"].append( { "label": label, From 205a3e49b5bfad15bca8b5db44024cfda6be0387 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 18:25:20 -0400 Subject: [PATCH 12/22] Tests passing locally --- client/python/gradio_client/client.py | 3 +- client/python/gradio_client/utils.py | 2 +- client/python/test/test_client.py | 86 +++++++------- gradio/test_data/blocks_configs.py | 160 ++++++++------------------ 4 files changed, 100 insertions(+), 151 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 0e7d91b05f71d..27892e47184ee 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -412,7 +412,8 @@ def view_api( else: fetch = requests.post( utils.SPACE_FETCHER_URL, - json={"config": json.dumps(self.config)}, + # Serialize has no effect + json={"config": json.dumps(self.config), "serialize": self.serialize}, ) if fetch.ok: info = fetch.json()["api"] diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index 6eeddaef29846..983cab7375c7b 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -29,7 +29,7 @@ CONFIG_URL = "/config" API_INFO_URL = "/info" RAW_API_INFO_URL = "/info?serialize=False" -SPACE_FETCHER_URL = "https://gradio-space-api-fetcher.hf.space/api" +SPACE_FETCHER_URL = "https://gradio-space-api-fetcher-dev.hf.space/api" RESET_URL = "/reset" SPACE_URL = "https://hf.space/{}" diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index a1742f86b9fbf..fbdf87034af39 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -14,7 +14,7 @@ from huggingface_hub.utils import RepositoryNotFoundError from gradio_client import Client -from gradio_client.serializing import SimpleSerializable +from gradio_client.serializing import Serializable from gradio_client.utils import Communicator, ProgressUnit, Status, StatusUpdate os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1" @@ -485,22 +485,22 @@ def test_numerical_to_label_space(self): "parameters": [ { "label": "Sex", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, { "label": "Age", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, { "label": "Fare (british pounds)", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -508,8 +508,11 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type_python": "str", - "type_description": "filepath to JSON file", + "type": { + "type": "string", + "description": "filepath to JSON file", + }, + "has_serialized_info": True, "component": "Label", } ], @@ -518,22 +521,22 @@ def test_numerical_to_label_space(self): "parameters": [ { "label": "Sex", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, { "label": "Age", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, { "label": "Fare (british pounds)", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -541,8 +544,11 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type_python": "str", - "type_description": "filepath to JSON file", + "type": { + "type": "string", + "description": "filepath to JSON file", + }, + "has_serialized_info": True, "component": "Label", } ], @@ -551,22 +557,22 @@ def test_numerical_to_label_space(self): "parameters": [ { "label": "Sex", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, { "label": "Age", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, { "label": "Fare (british pounds)", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -574,8 +580,11 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type_python": "str", - "type_description": "filepath to JSON file", + "type": { + "type": "string", + "description": "filepath to JSON file", + }, + "has_serialized_info": True, "component": "Label", } ], @@ -588,8 +597,7 @@ def test_numerical_to_label_space(self): def test_serializable_in_mapping(self, calculator_demo): with connect(calculator_demo) as client: assert all( - isinstance(c, SimpleSerializable) - for c in client.endpoints[0].serializers + isinstance(c, Serializable) for c in client.endpoints[0].serializers ) @pytest.mark.flaky @@ -604,8 +612,8 @@ def test_private_space(self): "parameters": [ { "label": "x", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Textbox", "example_input": "Howdy!", } @@ -613,8 +621,8 @@ def test_private_space(self): "returns": [ { "label": "output", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Textbox", } ], @@ -631,22 +639,22 @@ def test_fetch_old_version_space(self): "parameters": [ { "label": "num1", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Number", "example_input": 5, }, { "label": "operation", - "type_python": "str", - "type_description": "string value", + "type": {"type": "string"}, + "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, { "label": "num2", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Number", "example_input": 5, }, @@ -654,8 +662,8 @@ def test_fetch_old_version_space(self): "returns": [ { "label": "output", - "type_python": "int | float", - "type_description": "numeric value", + "type": {"type": "number"}, + "has_serialized_info": False, "component": "Number", } ], diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py index 1cdf72b1fbaea..c468d0dd7ac6c 100644 --- a/gradio/test_data/blocks_configs.py +++ b/gradio/test_data/blocks_configs.py @@ -13,13 +13,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -34,12 +29,10 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", + "serializer": "ListStringSerializable", "api_info": { - "raw_input": ["List[str]", "list of string values"], - "raw_output": ["List[str]", "list of string values"], - "serialized_input": ["List[str]", "list of string values"], - "serialized_output": ["List[str]", "list of string values"], + "info": {"type": "array", "items": {"type": "string"}}, + "serialized_info": False, }, "example_inputs": {"raw": "Covid", "serialized": "Covid"}, }, @@ -76,10 +69,11 @@ }, "serializer": "ImgSerializable", "api_info": { - "raw_input": ["str", "base64 representation of image"], - "raw_output": ["str", "base64 representation of image"], - "serialized_input": ["str", "filepath or URL to image"], - "serialized_output": ["str", "filepath or URL to image"], + "info": { + "type": "string", + "description": "base64 representation of an image", + }, + "serialized_info": True, }, "example_inputs": { "raw": "data:image/png;base64,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", @@ -92,13 +86,8 @@ "props": {"show_label": True, "name": "json", "visible": True, "style": {}}, "serializer": "JSONSerializable", "api_info": { - "raw_input": [ - "str | Dict | List", - "JSON-serializable object or a string", - ], - "raw_output": ["Dict | List", "dictionary- or list-like object"], - "serialized_input": ["str", "filepath to JSON file"], - "serialized_output": ["str", "filepath to JSON file"], + "info": {"type": {}, "description": "any valid json"}, + "serialized_info": True, }, "example_inputs": {"raw": {"a": 1, "b": 2}, "serialized": None}, }, @@ -113,13 +102,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -154,10 +138,11 @@ }, "serializer": "ImgSerializable", "api_info": { - "raw_input": ["str", "base64 representation of image"], - "raw_output": ["str", "base64 representation of image"], - "serialized_input": ["str", "filepath or URL to image"], - "serialized_output": ["str", "filepath or URL to image"], + "info": { + "type": "string", + "description": "base64 representation of an image", + }, + "serialized_info": True, }, "example_inputs": { "raw": "data:image/png;base64,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", @@ -170,13 +155,8 @@ "props": {"show_label": True, "name": "json", "visible": True, "style": {}}, "serializer": "JSONSerializable", "api_info": { - "raw_input": [ - "str | Dict | List", - "JSON-serializable object or a string", - ], - "raw_output": ["Dict | List", "dictionary- or list-like object"], - "serialized_input": ["str", "filepath to JSON file"], - "serialized_output": ["str", "filepath to JSON file"], + "info": {"type": {}, "description": "any valid json"}, + "serialized_info": True, }, "example_inputs": {"raw": {"a": 1, "b": 2}, "serialized": None}, }, @@ -191,13 +171,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -213,13 +188,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -353,13 +323,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -374,12 +339,10 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", + "serializer": "ListStringSerializable", "api_info": { - "raw_input": ["List[str]", "list of string values"], - "raw_output": ["List[str]", "list of string values"], - "serialized_input": ["List[str]", "list of string values"], - "serialized_output": ["List[str]", "list of string values"], + "info": {"type": "array", "items": {"type": "string"}}, + "serialized_info": False, }, "example_inputs": {"raw": "Covid", "serialized": "Covid"}, }, @@ -416,10 +379,11 @@ }, "serializer": "ImgSerializable", "api_info": { - "raw_input": ["str", "base64 representation of image"], - "raw_output": ["str", "base64 representation of image"], - "serialized_input": ["str", "filepath or URL to image"], - "serialized_output": ["str", "filepath or URL to image"], + "info": { + "type": "string", + "description": "base64 representation of an image", + }, + "serialized_info": True, }, "example_inputs": { "raw": "data:image/png;base64,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", @@ -432,13 +396,8 @@ "props": {"show_label": True, "name": "json", "visible": True, "style": {}}, "serializer": "JSONSerializable", "api_info": { - "raw_input": [ - "str | Dict | List", - "JSON-serializable object or a string", - ], - "raw_output": ["Dict | List", "dictionary- or list-like object"], - "serialized_input": ["str", "filepath to JSON file"], - "serialized_output": ["str", "filepath to JSON file"], + "info": {"type": {}, "description": "any valid json"}, + "serialized_info": True, }, "example_inputs": {"raw": {"a": 1, "b": 2}, "serialized": None}, }, @@ -453,13 +412,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -494,10 +448,11 @@ }, "serializer": "ImgSerializable", "api_info": { - "raw_input": ["str", "base64 representation of image"], - "raw_output": ["str", "base64 representation of image"], - "serialized_input": ["str", "filepath or URL to image"], - "serialized_output": ["str", "filepath or URL to image"], + "info": { + "type": "string", + "description": "base64 representation of an image", + }, + "serialized_info": True, }, "example_inputs": { "raw": "data:image/png;base64,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", @@ -510,13 +465,8 @@ "props": {"show_label": True, "name": "json", "visible": True, "style": {}}, "serializer": "JSONSerializable", "api_info": { - "raw_input": [ - "str | Dict | List", - "JSON-serializable object or a string", - ], - "raw_output": ["Dict | List", "dictionary- or list-like object"], - "serialized_input": ["str", "filepath to JSON file"], - "serialized_output": ["str", "filepath to JSON file"], + "info": {"type": {}, "description": "any valid json"}, + "serialized_info": True, }, "example_inputs": {"raw": {"a": 1, "b": 2}, "serialized": None}, }, @@ -531,13 +481,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { @@ -553,13 +498,8 @@ "visible": True, "style": {}, }, - "serializer": "Serializable", - "api_info": { - "raw_input": ["str", "string value"], - "raw_output": ["str", "string value"], - "serialized_input": ["str", "string value"], - "serialized_output": ["str", "string value"], - }, + "serializer": "StringSerializable", + "api_info": {"info": {"type": "string"}, "serialized_info": False}, "example_inputs": {"raw": "Howdy!", "serialized": "Howdy!"}, }, { From 30cef95282db076206201ff3ae94254dfd20837f Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Wed, 3 May 2023 20:16:50 -0400 Subject: [PATCH 13/22] Format code --- client/python/gradio_client/client.py | 50 +++++-------------- client/python/gradio_client/serializing.py | 2 +- client/python/test/test_client.py | 57 +++++++++++++++++++--- gradio/blocks.py | 24 ++++++++- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 27892e47184ee..aaffdfd03151f 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -35,7 +35,6 @@ JobStatus, Status, StatusUpdate, - json_schema_to_python_type, ) set_documentation_group("py-client") @@ -399,13 +398,16 @@ def view_api( } """ - api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) + if self.serialize: + api_info_url = urllib.parse.urljoin(self.src, utils.API_INFO_URL) + else: + api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) r = requests.get(api_info_url, headers=self.headers) # Versions of Gradio older than 3.26 returned format of the API info # from the /info endpoint if ( - version.parse(self.config.get("version", "2.0")) >= version.Version("3.26") + version.parse(self.config.get("version", "2.0")) > version.Version("3.28.1") and r.ok ): info = r.json() @@ -427,42 +429,12 @@ def view_api( human_info = "Client.predict() Usage Info\n---------------------------\n" human_info += f"Named API endpoints: {num_named_endpoints}\n" - # if serialize=True, use the serialized info from the serializer - # unless the component tells us it does not have different serialized - # data format, e.g. has_serialized_info=False in config for api_name, endpoint_info in info["named_endpoints"].items(): - if self.serialize: - inferred_fn_index = self._infer_fn_index(api_name, None) - for v, s in zip( - endpoint_info["parameters"], - self.endpoints[inferred_fn_index].serializers, - ): - if v["has_serialized_info"]: - v["type"] = s.serialized_info() - for v, s in zip( - endpoint_info["returns"], - self.endpoints[inferred_fn_index].deserializers, - ): - if v["has_serialized_info"]: - v["type"] = s.serialized_info() human_info += self._render_endpoints_info(api_name, endpoint_info) if all_endpoints: human_info += f"\nUnnamed API endpoints: {num_unnamed_endpoints}\n" for fn_index, endpoint_info in info["unnamed_endpoints"].items(): - if self.serialize: - for v, s in zip( - endpoint_info["parameters"], - self.endpoints[int(fn_index)].serializers, - ): - if v["has_serialized_info"]: - v["type"] = s.serialized_info() - for v, s in zip( - endpoint_info["returns"], - self.endpoints[int(fn_index)].deserializers, - ): - if v["has_serialized_info"]: - v["type"] = s.serialized_info() # When loading from json, the fn_indices are read as strings # because json keys can only be strings human_info += self._render_endpoints_info(int(fn_index), endpoint_info) @@ -508,11 +480,11 @@ def _render_endpoints_info( if endpoints_info["parameters"]: for info in endpoints_info["parameters"]: desc = ( - f" ({info['type']['description']})" - if "description" in info["type"] + f" ({info['python_type']['description']})" + if info["python_type"].get("description") else "" ) - type_ = json_schema_to_python_type(info["type"]) + type_ = info["python_type"]["type"] human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {type_}{desc} \n" else: human_info += " - None\n" @@ -520,11 +492,11 @@ def _render_endpoints_info( if endpoints_info["returns"]: for info in endpoints_info["returns"]: desc = ( - f" ({info['type']['description']})" - if "description" in info["type"] + f" ({info['python_type']['description']})" + if info["python_type"].get("description") else "" ) - type_ = json_schema_to_python_type(info["type"]) + type_ = info["python_type"]["type"] human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {type_}{desc} \n" else: human_info += " - None\n" diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index 33f5aab656e9c..4503401d452ce 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -411,7 +411,7 @@ def deserialize( class GallerySerializable(Serializable): def serialized_info(self): return { - "type": "str", + "type": "string", "description": "path to directory with images and a file associating images with captions called captions.json", } diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index fbdf87034af39..b9aa39848f5ab 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -486,6 +486,7 @@ def test_numerical_to_label_space(self): { "label": "Sex", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", @@ -493,6 +494,10 @@ def test_numerical_to_label_space(self): { "label": "Age", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -500,6 +505,10 @@ def test_numerical_to_label_space(self): { "label": "Fare (british pounds)", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -508,8 +517,9 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type": { - "type": "string", + "type": {"type": {}, "description": "any valid json"}, + "python_type": { + "type": "str", "description": "filepath to JSON file", }, "has_serialized_info": True, @@ -522,6 +532,7 @@ def test_numerical_to_label_space(self): { "label": "Sex", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", @@ -529,6 +540,10 @@ def test_numerical_to_label_space(self): { "label": "Age", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -536,6 +551,10 @@ def test_numerical_to_label_space(self): { "label": "Fare (british pounds)", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -544,8 +563,9 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type": { - "type": "string", + "type": {"type": {}, "description": "any valid json"}, + "python_type": { + "type": "str", "description": "filepath to JSON file", }, "has_serialized_info": True, @@ -558,6 +578,7 @@ def test_numerical_to_label_space(self): { "label": "Sex", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", @@ -565,6 +586,10 @@ def test_numerical_to_label_space(self): { "label": "Age", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -572,6 +597,10 @@ def test_numerical_to_label_space(self): { "label": "Fare (british pounds)", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Slider", "example_input": 5, @@ -580,8 +609,9 @@ def test_numerical_to_label_space(self): "returns": [ { "label": "output", - "type": { - "type": "string", + "type": {"type": {}, "description": "any valid json"}, + "python_type": { + "type": "str", "description": "filepath to JSON file", }, "has_serialized_info": True, @@ -613,6 +643,7 @@ def test_private_space(self): { "label": "x", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Textbox", "example_input": "Howdy!", @@ -622,6 +653,7 @@ def test_private_space(self): { "label": "output", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Textbox", } @@ -640,6 +672,10 @@ def test_fetch_old_version_space(self): { "label": "num1", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Number", "example_input": 5, @@ -647,6 +683,7 @@ def test_fetch_old_version_space(self): { "label": "operation", "type": {"type": "string"}, + "python_type": {"type": "str", "description": ""}, "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", @@ -654,6 +691,10 @@ def test_fetch_old_version_space(self): { "label": "num2", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Number", "example_input": 5, @@ -663,6 +704,10 @@ def test_fetch_old_version_space(self): { "label": "output", "type": {"type": "number"}, + "python_type": { + "type": "Union[int, float]", + "description": "", + }, "has_serialized_info": False, "component": "Number", } diff --git a/gradio/blocks.py b/gradio/blocks.py index 28c7aba59fdae..dec1c4edc4466 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -20,6 +20,7 @@ from gradio_client import serializing from gradio_client import utils as client_utils from gradio_client.documentation import document, set_documentation_group +from packaging import version from typing_extensions import Literal from gradio import ( @@ -468,6 +469,9 @@ def get_api_info(config: Dict, serialize: bool = True): """ api_info = {"named_endpoints": {}, "unnamed_endpoints": {}} mode = config.get("mode", None) + after_new_format = version.parse(config.get("version", "2.0")) > version.Version( + "3.28.1" + ) for d, dependency in enumerate(config["dependencies"]): dependency_info = {"parameters": [], "returns": []} @@ -494,18 +498,26 @@ def get_api_info(config: Dict, serialize: bool = True): # The config has the most specific API info (taking into account the parameters # of the component), so we use that if it exists. Otherwise, we fallback to the # Serializer's API info. - if component.get("api_info"): + serializer = serializing.COMPONENT_MAPPING[type]() + if component.get("api_info") and after_new_format: info = component["api_info"] example = component["example_inputs"]["serialized"] else: - serializer = serializing.COMPONENT_MAPPING[type]() assert isinstance(serializer, serializing.Serializable) info = serializer.api_info() example = serializer.example_inputs()["raw"] + python_info = info["info"] + if serialize and info["serialized_info"]: + python_info = serializer.serialized_info() + python_type = client_utils.json_schema_to_python_type(python_info) dependency_info["parameters"].append( { "label": label, "type": info["info"], + "python_type": { + "type": python_type, + "description": python_info.get("description", ""), + }, "has_serialized_info": info["serialized_info"], "component": type.capitalize(), "example_input": example, @@ -533,10 +545,18 @@ def get_api_info(config: Dict, serialize: bool = True): serializer = serializing.COMPONENT_MAPPING[type]() assert isinstance(serializer, serializing.Serializable) info = serializer.api_info() + python_info = info["info"] + if serialize and info["serialized_info"]: + python_info = serializer.serialized_info() + python_type = client_utils.json_schema_to_python_type(python_info) dependency_info["returns"].append( { "label": label, "type": info["info"], + "python_type": { + "type": python_type, + "description": python_info.get("description", ""), + }, "has_serialized_info": info["serialized_info"], "component": type.capitalize(), } From c0413450192cfc1453bd0ee2958d1e4cd0c2fb2b Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 11:36:35 -0400 Subject: [PATCH 14/22] Address comments --- client/python/gradio_client/client.py | 7 +++---- client/python/gradio_client/utils.py | 4 ++-- client/python/test/test_client.py | 3 +-- client/python/test/test_serializing.py | 8 +++----- gradio/blocks.py | 4 +--- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 8c5811d2e7123..8e89104478ce9 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -404,17 +404,16 @@ def view_api( api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) r = requests.get(api_info_url, headers=self.headers) - # Versions of Gradio older than 3.26 returned format of the API info + # Versions of Gradio older than 3.28 returned format of the API info # from the /info endpoint if ( - version.parse(self.config.get("version", "2.0")) > version.Version("3.28.1") + version.parse(self.config.get("version", "2.0")) > version.Version("3.28.3") and r.ok ): info = r.json() else: fetch = requests.post( utils.SPACE_FETCHER_URL, - # Serialize has no effect json={"config": json.dumps(self.config), "serialize": self.serialize}, ) if fetch.ok: @@ -455,7 +454,7 @@ def reset_session(self) -> None: def _render_endpoints_info( self, name_or_index: str | int, - endpoints_info: dict[str, list[dict[str, str]]], + endpoints_info: dict[str, list[dict[str, Any]]], ) -> str: parameter_names = [p["label"] for p in endpoints_info["parameters"]] parameter_names = [utils.sanitize_parameter_names(p) for p in parameter_names] diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index ced50c3b2ddf4..9528a67bc1b5d 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -29,7 +29,7 @@ CONFIG_URL = "/config" API_INFO_URL = "/info" RAW_API_INFO_URL = "/info?serialize=False" -SPACE_FETCHER_URL = "https://gradio-space-api-fetcher-dev.hf.space/api" +SPACE_FETCHER_URL = "https://gradio-space-api-fetcher-v2.hf.space/api" RESET_URL = "/reset" SPACE_URL = "https://hf.space/{}" @@ -493,7 +493,7 @@ class APIInfoParseError(ValueError): pass -def get_type(schema: Dict): +def get_type(schema: dict): if "type" in schema: return schema["type"] elif schema.get("oneOf"): diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index 72a6fc07d1487..2497ab19a27af 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -632,8 +632,7 @@ def test_numerical_to_label_space(self): def test_serializable_in_mapping(self, calculator_demo): with connect(calculator_demo) as client: assert all( - isinstance(c, Serializable) - for c in client.endpoints[0].serializers + isinstance(c, Serializable) for c in client.endpoints[0].serializers ) @pytest.mark.flaky diff --git a/client/python/test/test_serializing.py b/client/python/test/test_serializing.py index c4a1f60a2d433..e0d14fb856a1f 100644 --- a/client/python/test/test_serializing.py +++ b/client/python/test/test_serializing.py @@ -12,11 +12,9 @@ def test_duplicate(serializer_class): if "gradio_client" not in serializer_class.__module__: pytest.skip(f"{serializer_class} not defined in gradio_client") - serialzier = serializer_class() - info = serialzier.api_info() - assert "info" in info and "serialized_info" in info - if "serialized_info" in info: - assert serialzier.serialized_info() + serializer = serializer_class() + assert serializer.api_info() + assert serializer.serialized_info() def test_check_component_fallback_serializers(): diff --git a/gradio/blocks.py b/gradio/blocks.py index cd50890437123..ef6786562075a 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -470,7 +470,7 @@ def get_api_info(config: dict, serialize: bool = True): api_info = {"named_endpoints": {}, "unnamed_endpoints": {}} mode = config.get("mode", None) after_new_format = version.parse(config.get("version", "2.0")) > version.Version( - "3.28.1" + "3.28.3" ) for d, dependency in enumerate(config["dependencies"]): @@ -518,7 +518,6 @@ def get_api_info(config: dict, serialize: bool = True): "type": python_type, "description": python_info.get("description", ""), }, - "has_serialized_info": info["serialized_info"], "component": type.capitalize(), "example_input": example, } @@ -557,7 +556,6 @@ def get_api_info(config: dict, serialize: bool = True): "type": python_type, "description": python_info.get("description", ""), }, - "has_serialized_info": info["serialized_info"], "component": type.capitalize(), } ) From e8d3102fb6f085545892c77470c30b16cc6bcefd Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 12:27:39 -0400 Subject: [PATCH 15/22] Use union + fix tests --- client/python/gradio_client/serializing.py | 19 ++++++++---- client/python/gradio_client/utils.py | 6 ++-- client/python/test/test_client.py | 36 ++++++---------------- client/python/test/test_serializing.py | 6 ++-- client/python/test/test_utils.py | 6 ++-- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index c6316558f7792..9bb6b3c220749 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -13,6 +13,13 @@ 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]. @@ -59,7 +66,7 @@ def deserialize( class SimpleSerializable(Serializable): """General class that does not perform any serialization or deserialization.""" - def api_info(self) -> dict[str, str | list[str]]: + def api_info(self) -> dict[str, bool | dict]: return { "info": serializer_types["SimpleSerializable"], "serialized_info": False, @@ -75,7 +82,7 @@ def example_inputs(self) -> dict[str, Any]: class StringSerializable(Serializable): """Expects a string as input/output but performs no serialization.""" - def api_info(self) -> dict[str, list[str]]: + def api_info(self) -> dict[str, bool | dict]: return { "info": serializer_types["StringSerializable"], "serialized_info": False, @@ -91,7 +98,7 @@ def example_inputs(self) -> dict[str, Any]: class ListStringSerializable(Serializable): """Expects a list of strings as input/output but performs no serialization.""" - def api_info(self) -> dict[str, list[str]]: + def api_info(self) -> dict[str, bool | dict]: return { "info": serializer_types["ListStringSerializable"], "serialized_info": False, @@ -107,7 +114,7 @@ def example_inputs(self) -> dict[str, Any]: class BooleanSerializable(Serializable): """Expects a boolean as input/output but performs no serialization.""" - def api_info(self) -> dict[str, list[str]]: + def api_info(self) -> dict[str, bool | dict]: return { "info": serializer_types["BooleanSerializable"], "serialized_info": False, @@ -123,7 +130,7 @@ def example_inputs(self) -> dict[str, Any]: class NumberSerializable(Serializable): """Expects a number (int/float) as input/output but performs no serialization.""" - def api_info(self) -> dict[str, list[str]]: + def api_info(self) -> dict[str, bool | dict]: return { "info": serializer_types["NumberSerializable"], "serialized_info": False, @@ -142,7 +149,7 @@ class ImgSerializable(Serializable): def serialized_info(self): return {"type": "string", "description": "filepath or URL to image"} - def api_info(self) -> dict[str, dict | bool]: + def api_info(self) -> dict[str, bool | dict]: return {"info": serializer_types["ImgSerializable"], "serialized_info": True} def example_inputs(self) -> dict[str, Any]: diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py index 9528a67bc1b5d..a9bcec285aa6e 100644 --- a/client/python/gradio_client/utils.py +++ b/client/python/gradio_client/utils.py @@ -521,7 +521,7 @@ def json_schema_to_python_type(schema: Any) -> str: elif type_ == "boolean": return "bool" elif type_ == "number": - return "Union[int, float]" + return "int | float" elif type_ == "array": items = schema.get("items") if "prefixItems" in items: @@ -541,7 +541,7 @@ def json_schema_to_python_type(schema: Any) -> str: ) return f"Dict({des})" elif type_ in ["oneOf", "anyOf"]: - desc = ", ".join([json_schema_to_python_type(i) for i in schema[type_]]) - return f"Union[{desc}]" + desc = " | ".join([json_schema_to_python_type(i) for i in schema[type_]]) + return desc else: raise APIInfoParseError(f"Cannot parse schema {schema}") diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index 2497ab19a27af..f6908590abc40 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -492,7 +492,6 @@ def test_numerical_to_label_space(self): "label": "Sex", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, @@ -500,10 +499,9 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -511,10 +509,9 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -527,7 +524,6 @@ def test_numerical_to_label_space(self): "type": "str", "description": "filepath to JSON file", }, - "has_serialized_info": True, "component": "Label", } ], @@ -538,7 +534,6 @@ def test_numerical_to_label_space(self): "label": "Sex", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, @@ -546,10 +541,9 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -557,10 +551,9 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -573,7 +566,6 @@ def test_numerical_to_label_space(self): "type": "str", "description": "filepath to JSON file", }, - "has_serialized_info": True, "component": "Label", } ], @@ -584,7 +576,6 @@ def test_numerical_to_label_space(self): "label": "Sex", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, @@ -592,10 +583,9 @@ def test_numerical_to_label_space(self): "label": "Age", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -603,10 +593,9 @@ def test_numerical_to_label_space(self): "label": "Fare (british pounds)", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Slider", "example_input": 5, }, @@ -619,7 +608,6 @@ def test_numerical_to_label_space(self): "type": "str", "description": "filepath to JSON file", }, - "has_serialized_info": True, "component": "Label", } ], @@ -649,7 +637,6 @@ def test_private_space(self): "label": "x", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Textbox", "example_input": "Howdy!", } @@ -659,7 +646,6 @@ def test_private_space(self): "label": "output", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Textbox", } ], @@ -678,10 +664,9 @@ def test_fetch_old_version_space(self): "label": "num1", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Number", "example_input": 5, }, @@ -689,7 +674,6 @@ def test_fetch_old_version_space(self): "label": "operation", "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, - "has_serialized_info": False, "component": "Radio", "example_input": "Howdy!", }, @@ -697,10 +681,9 @@ def test_fetch_old_version_space(self): "label": "num2", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Number", "example_input": 5, }, @@ -710,10 +693,9 @@ def test_fetch_old_version_space(self): "label": "output", "type": {"type": "number"}, "python_type": { - "type": "Union[int, float]", + "type": "int | float", "description": "", }, - "has_serialized_info": False, "component": "Number", } ], diff --git a/client/python/test/test_serializing.py b/client/python/test/test_serializing.py index e0d14fb856a1f..4576b3076572d 100644 --- a/client/python/test/test_serializing.py +++ b/client/python/test/test_serializing.py @@ -13,8 +13,10 @@ def test_duplicate(serializer_class): if "gradio_client" not in serializer_class.__module__: pytest.skip(f"{serializer_class} not defined in gradio_client") serializer = serializer_class() - assert serializer.api_info() - assert serializer.serialized_info() + info = serializer.api_info() + assert "info" in info and "serialized_info" in info + if "serialized_info" in info: + assert serializer.serialized_info() def test_check_component_fallback_serializers(): diff --git a/client/python/test/test_utils.py b/client/python/test/test_utils.py index 05d48f2cf19f3..a95d7ed2eff94 100644 --- a/client/python/test/test_utils.py +++ b/client/python/test/test_utils.py @@ -136,15 +136,15 @@ def test_json_schema_to_python_type(schema): elif schema == "BooleanSerializable": answer = "bool" elif schema == "NumberSerializable": - answer = "Union[int, float]" + answer = "int | float" elif schema == "ImgSerializable": answer = "str" elif schema == "FileSerializable": - answer = "Union[str, Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), List[Union[str, Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file))]]]" + answer = "str | Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)) | List[str | Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file))]" elif schema == "JSONSerializable": answer = "Dict[Any, Any]" elif schema == "GallerySerializable": - answer = "Tuple[Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), Union[str, None]]" + answer = "Tuple[Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), str | None]" else: raise ValueError(f"This test has not been modified to check {schema}") assert utils.json_schema_to_python_type(types[schema]) == answer From f9e8d19b1caec8738fda687f15ccf342e4e52bbd Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 17:27:16 -0400 Subject: [PATCH 16/22] handle multiple file case --- client/python/gradio_client/client.py | 2 +- client/python/gradio_client/serializing.py | 24 +++++++- client/python/gradio_client/types.json | 65 ++++++++++++++++++++++ client/python/test/conftest.py | 11 ++++ client/python/test/test_client.py | 22 ++++++++ client/python/test/test_utils.py | 13 +++++ gradio/blocks.py | 11 ++++ gradio/components.py | 12 ++++ 8 files changed, 158 insertions(+), 2 deletions(-) diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 8e89104478ce9..699eb2823f7da 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -404,7 +404,7 @@ def view_api( api_info_url = urllib.parse.urljoin(self.src, utils.RAW_API_INFO_URL) r = requests.get(api_info_url, headers=self.headers) - # Versions of Gradio older than 3.28 returned format of the API info + # Versions of Gradio older than 3.28.3 returned format of the API info # from the /info endpoint if ( version.parse(self.config.get("version", "2.0")) > version.Version("3.28.3") diff --git a/client/python/gradio_client/serializing.py b/client/python/gradio_client/serializing.py index 9bb6b3c220749..9f496f3a5e947 100644 --- a/client/python/gradio_client/serializing.py +++ b/client/python/gradio_client/serializing.py @@ -202,10 +202,32 @@ class FileSerializable(Serializable): """Expects a dict with base64 representation of object as input/output which is serialized to a filepath.""" 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 or URL to 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 or URL to file"}, + } + + def _multiple_file_api_info(self): + return { + "info": serializer_types["MultipleFileSerializable"], + "serialized_info": True, + } + def api_info(self) -> dict[str, dict | bool]: - return {"info": serializer_types["FileSerializable"], "serialized_info": True} + return self._single_file_api_info() def example_inputs(self) -> dict[str, Any]: return { diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index bc0756bb6f9be..37ef7dacad1b0 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -87,6 +87,71 @@ } ] }, + "SingleFileSerializable": { + "oneOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": { "type": "string", "description": "name of file" }, + "data": { + "type": "string", + "description": "base64 representation of file" + }, + "size": { + "type": "integer", + "description": "size of image in bytes" + }, + "is_file": { + "type": "boolean", + "description": "true if the file has been uploaded to the server" + }, + "orig_name": { + "type": "string", + "description": "original name of the file" + } + }, + "required": ["name", "data"] + } + ] + }, + "MultipleFileSerializable": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + "description": "filepath or URL to file" + }, + { + "type": "object", + "properties": { + "name": { "type": "string", "description": "name of file" }, + "data": { + "type": "string", + "description": "base64 representation of file" + }, + "size": { + "type": "integer", + "description": "size of image in bytes" + }, + "is_file": { + "type": "boolean", + "description": "true if the file has been uploaded to the server" + }, + "orig_name": { + "type": "string", + "description": "original name of the file" + } + }, + "required": ["name", "data"] + } + ] + } + }, "JSONSerializable": { "type": {}, "description": "any valid json" diff --git a/client/python/test/conftest.py b/client/python/test/conftest.py index 8049ad9d5d24e..ddcf4a1a8b7a4 100644 --- a/client/python/test/conftest.py +++ b/client/python/test/conftest.py @@ -157,3 +157,14 @@ def show(n): list_btn.click(show, num, out) return demo.queue() + + +@pytest.fixture +def file_io_demo(): + demo = gr.Interface( + lambda x: print("foox"), + [gr.File(file_count="multiple"), "file"], + [gr.File(file_count="multiple"), "file"], + ) + + return demo diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index f6908590abc40..a82b0c2887287 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -711,6 +711,28 @@ def test_unnamed_endpoints_use_fn_index(self, count_generator_demo): assert "fn_index=0" in info assert "api_name" not in info + def test_file_io(self, file_io_demo): + with connect(file_io_demo) as client: + info = client.view_api(return_format="dict") + inputs = info["named_endpoints"]["/predict"]["parameters"] + outputs = info["named_endpoints"]["/predict"]["returns"] + assert inputs[0]["python_type"] == { + "type": "List[str]", + "description": "List of filepath(s) or URL(s) to files", + } + assert inputs[1]["python_type"] == { + "type": "str", + "description": "filepath or URL to file", + } + assert outputs[0]["python_type"] == { + "type": "List[str]", + "description": "List of filepath(s) or URL(s) to files", + } + assert outputs[1]["python_type"] == { + "type": "str", + "description": "filepath or URL to file", + } + class TestEndpoints: def test_upload(self): diff --git a/client/python/test/test_utils.py b/client/python/test/test_utils.py index a95d7ed2eff94..b6a7717bc362b 100644 --- a/client/python/test/test_utils.py +++ b/client/python/test/test_utils.py @@ -11,6 +11,11 @@ from gradio_client import media_data, utils types = json.loads(importlib.resources.read_text("gradio_client", "types.json")) +types["MultipleFile"] = { + "type": "array", + "items": {"type": "string", "description": "filepath or URL to file"}, +} +types["SingleFile"] = {"type": "string", "description": "filepath or URL to file"} def test_encode_url_or_file_to_base64(): @@ -145,6 +150,14 @@ def test_json_schema_to_python_type(schema): answer = "Dict[Any, Any]" elif schema == "GallerySerializable": answer = "Tuple[Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file)), str | None]" + elif schema == "SingleFileSerializable": + answer = "str | Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file))" + elif schema == "MultipleFileSerializable": + answer = "List[str | Dict(name: str (name of file), data: str (base64 representation of file), size: int (size of image in bytes), is_file: bool (true if the file has been uploaded to the server), orig_name: str (original name of the file))]" + elif schema == "SingleFile": + answer = "str" + elif schema == "MultipleFile": + answer = "List[str]" else: raise ValueError(f"This test has not been modified to check {schema}") assert utils.json_schema_to_python_type(types[schema]) == answer diff --git a/gradio/blocks.py b/gradio/blocks.py index ef6786562075a..6e796cc3be20c 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -509,6 +509,12 @@ def get_api_info(config: dict, serialize: bool = True): python_info = info["info"] if serialize and info["serialized_info"]: python_info = serializer.serialized_info() + if ( + isinstance(serializer, serializing.FileSerializable) + and component["props"].get("file_count", "single") != "single" + ): + python_info = serializer._multiple_file_serialized_info() + python_type = client_utils.json_schema_to_python_type(python_info) dependency_info["parameters"].append( { @@ -547,6 +553,11 @@ def get_api_info(config: dict, serialize: bool = True): python_info = info["info"] if serialize and info["serialized_info"]: python_info = serializer.serialized_info() + if ( + isinstance(serializer, serializing.FileSerializable) + and component["props"].get("file_count", "single") != "single" + ): + python_info = serializer._multiple_file_serialized_info() python_type = client_utils.json_schema_to_python_type(python_info) dependency_info["returns"].append( { diff --git a/gradio/components.py b/gradio/components.py index a473aa6722478..bc45926cd2172 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -2785,6 +2785,18 @@ def as_example(self, input_data: str | list | None) -> str: else: return Path(input_data).name + def api_info(self) -> dict[str, dict | bool]: + if self.file_count == "single": + return self._single_file_api_info() + else: + return self._multiple_file_api_info() + + def serialized_info(self): + if self.file_count == "single": + return self._single_file_serialized_info() + else: + return self._multiple_file_serialized_info() + @document("style") class Dataframe(Changeable, Selectable, IOComponent, JSONSerializable): From 114bab78a1866f25241284fb82eb16cc642378c8 Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 17:56:56 -0400 Subject: [PATCH 17/22] Add serializer to info payload --- client/python/test/test_client.py | 18 ++++++++++++++++++ gradio/blocks.py | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/client/python/test/test_client.py b/client/python/test/test_client.py index a82b0c2887287..495bbfa38073d 100644 --- a/client/python/test/test_client.py +++ b/client/python/test/test_client.py @@ -494,6 +494,7 @@ def test_numerical_to_label_space(self): "python_type": {"type": "str", "description": ""}, "component": "Radio", "example_input": "Howdy!", + "serializer": "StringSerializable", }, { "label": "Age", @@ -504,6 +505,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, { "label": "Fare (british pounds)", @@ -514,6 +516,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, ], "returns": [ @@ -525,6 +528,7 @@ def test_numerical_to_label_space(self): "description": "filepath to JSON file", }, "component": "Label", + "serializer": "JSONSerializable", } ], }, @@ -536,6 +540,7 @@ def test_numerical_to_label_space(self): "python_type": {"type": "str", "description": ""}, "component": "Radio", "example_input": "Howdy!", + "serializer": "StringSerializable", }, { "label": "Age", @@ -546,6 +551,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, { "label": "Fare (british pounds)", @@ -556,6 +562,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, ], "returns": [ @@ -567,6 +574,7 @@ def test_numerical_to_label_space(self): "description": "filepath to JSON file", }, "component": "Label", + "serializer": "JSONSerializable", } ], }, @@ -578,6 +586,7 @@ def test_numerical_to_label_space(self): "python_type": {"type": "str", "description": ""}, "component": "Radio", "example_input": "Howdy!", + "serializer": "StringSerializable", }, { "label": "Age", @@ -588,6 +597,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, { "label": "Fare (british pounds)", @@ -598,6 +608,7 @@ def test_numerical_to_label_space(self): }, "component": "Slider", "example_input": 5, + "serializer": "NumberSerializable", }, ], "returns": [ @@ -609,6 +620,7 @@ def test_numerical_to_label_space(self): "description": "filepath to JSON file", }, "component": "Label", + "serializer": "JSONSerializable", } ], }, @@ -639,6 +651,7 @@ def test_private_space(self): "python_type": {"type": "str", "description": ""}, "component": "Textbox", "example_input": "Howdy!", + "serializer": "StringSerializable", } ], "returns": [ @@ -647,6 +660,7 @@ def test_private_space(self): "type": {"type": "string"}, "python_type": {"type": "str", "description": ""}, "component": "Textbox", + "serializer": "StringSerializable", } ], } @@ -669,6 +683,7 @@ def test_fetch_old_version_space(self): }, "component": "Number", "example_input": 5, + "serializer": "NumberSerializable", }, { "label": "operation", @@ -676,6 +691,7 @@ def test_fetch_old_version_space(self): "python_type": {"type": "str", "description": ""}, "component": "Radio", "example_input": "Howdy!", + "serializer": "StringSerializable", }, { "label": "num2", @@ -686,6 +702,7 @@ def test_fetch_old_version_space(self): }, "component": "Number", "example_input": 5, + "serializer": "NumberSerializable", }, ], "returns": [ @@ -697,6 +714,7 @@ def test_fetch_old_version_space(self): "description": "", }, "component": "Number", + "serializer": "NumberSerializable", } ], } diff --git a/gradio/blocks.py b/gradio/blocks.py index 6e796cc3be20c..1d717e19e1ff9 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -516,6 +516,7 @@ def get_api_info(config: dict, serialize: bool = True): python_info = serializer._multiple_file_serialized_info() python_type = client_utils.json_schema_to_python_type(python_info) + serializer_name = serializing.COMPONENT_MAPPING[type].__name__ dependency_info["parameters"].append( { "label": label, @@ -526,6 +527,7 @@ def get_api_info(config: dict, serialize: bool = True): }, "component": type.capitalize(), "example_input": example, + "serializer": serializer_name, } ) @@ -559,6 +561,7 @@ def get_api_info(config: dict, serialize: bool = True): ): python_info = serializer._multiple_file_serialized_info() python_type = client_utils.json_schema_to_python_type(python_info) + serializer_name = serializing.COMPONENT_MAPPING[type].__name__ dependency_info["returns"].append( { "label": label, @@ -568,6 +571,7 @@ def get_api_info(config: dict, serialize: bool = True): "description": python_info.get("description", ""), }, "component": type.capitalize(), + "serializer": serializer_name, } ) From 09421eb0fdeb29985eb8652ed083bb75e1565c8c Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 17:58:19 -0400 Subject: [PATCH 18/22] lint --- client/python/gradio_client/types.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/python/gradio_client/types.json b/client/python/gradio_client/types.json index 37ef7dacad1b0..8fe876126f600 100644 --- a/client/python/gradio_client/types.json +++ b/client/python/gradio_client/types.json @@ -88,7 +88,7 @@ ] }, "SingleFileSerializable": { - "oneOf": [ + "oneOf": [ { "type": "string", "description": "filepath or URL to file" @@ -116,7 +116,7 @@ }, "required": ["name", "data"] } - ] + ] }, "MultipleFileSerializable": { "type": "array", From 1c6dca0eec0981400aceb046eec0398c7574b81d Mon Sep 17 00:00:00 2001 From: freddyaboulton Date: Fri, 5 May 2023 18:02:07 -0400 Subject: [PATCH 19/22] Add to CHANGELOG --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 310cfb6f689f4..b1dae99ada1b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,10 @@ ## New Features: -No changes to highlight. +- Returning language agnostic types in the `/info` route by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4039](https://github.com/gradio-app/gradio/pull/4039) ## Bug Fixes: - - Fixed bug where type hints in functions caused the event handler to crash by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4068](https://github.com/gradio-app/gradio/pull/4068) - Fix dropdown default value not appearing by [@aliabid94](https://github.com/aliabid94) in [PR 4072](https://github.com/gradio-app/gradio/pull/4072). - Soft theme label color fix by [@aliabid94](https://github.com/aliabid94) in [PR 4070](https://github.com/gradio-app/gradio/pull/4070) From 9a2263164373d07138e9010a63213b17213165d4 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 5 May 2023 18:10:18 -0500 Subject: [PATCH 20/22] grc version --- client/python/gradio_client/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/python/gradio_client/version.txt b/client/python/gradio_client/version.txt index 845639eef26c0..0ea3a944b399d 100644 --- a/client/python/gradio_client/version.txt +++ b/client/python/gradio_client/version.txt @@ -1 +1 @@ -0.1.4 +0.2.0 From faa003c0fbe7ef4a340a6477c50cb5f6a10878c7 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 5 May 2023 18:12:32 -0500 Subject: [PATCH 21/22] requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4f1439c402d22..0788c693f7243 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ aiohttp altair>=4.2.0 fastapi ffmpy -gradio_client>=0.1.3 +gradio_client>=0.2.0 httpx huggingface_hub>=0.13.0 Jinja2 From fceafef0da5e435edffa8561e1ccef32f7d58c7c Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Fri, 5 May 2023 18:57:52 -0500 Subject: [PATCH 22/22] fix test --- test/test_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_components.py b/test/test_components.py index a1b472e482032..bea6d2fbcc7e9 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -955,7 +955,7 @@ def test_component_functions(self): x_file["is_example"] = True assert file_input.preprocess(x_file) is not None - zero_size_file = {"name": "document.txt", "size": 0, "data": "data:"} + zero_size_file = {"name": "document.txt", "size": 0, "data": ""} temp_file = file_input.preprocess(zero_size_file) assert os.stat(temp_file.name).st_size == 0