Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Language Agnostic Typing in /info route #4039

Merged
merged 28 commits into from
May 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
46f8b7e
First commit
freddyaboulton Apr 26, 2023
61e1f1f
All serializers
freddyaboulton Apr 26, 2023
7a021e0
Remove output type
freddyaboulton Apr 26, 2023
47b3913
Merge branch 'main' into json-schema-for-clients
freddyaboulton May 2, 2023
c658d54
Add route
freddyaboulton May 2, 2023
cdf53d3
Merge main
freddyaboulton May 2, 2023
41dcd56
Format json
freddyaboulton May 2, 2023
2581fb5
Modify dropdown and slider choices
freddyaboulton May 2, 2023
b3a1649
Fix impl
freddyaboulton May 2, 2023
37c188b
Lint
freddyaboulton May 3, 2023
67724b5
Add tests
freddyaboulton May 3, 2023
7b9bbf8
Merge branch 'main' into json-schema-for-clients
freddyaboulton May 3, 2023
b603caf
Fix lint
freddyaboulton May 3, 2023
95e176b
remove breakpoint
freddyaboulton May 3, 2023
205a3e4
Tests passing locally
freddyaboulton May 3, 2023
30cef95
Format code
freddyaboulton May 4, 2023
7dce123
Merge branch 'main' into json-schema-for-clients
abidlabs May 4, 2023
5f029be
merge main
freddyaboulton May 5, 2023
c041345
Address comments
freddyaboulton May 5, 2023
e8d3102
Use union + fix tests
freddyaboulton May 5, 2023
fdfe9f3
Merge branch 'json-schema-for-clients' of github.com:gradio-app/gradi…
freddyaboulton May 5, 2023
f9e8d19
handle multiple file case
freddyaboulton May 5, 2023
114bab7
Add serializer to info payload
freddyaboulton May 5, 2023
09421eb
lint
freddyaboulton May 5, 2023
1c6dca0
Add to CHANGELOG
freddyaboulton May 5, 2023
9a22631
grc version
abidlabs May 5, 2023
faa003c
requirements
abidlabs May 5, 2023
fceafef
fix test
abidlabs May 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 24 additions & 7 deletions client/python/gradio_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
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,
)

set_documentation_group("py-client")

Expand Down Expand Up @@ -399,17 +404,17 @@ 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.3 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.3")
and r.ok
):
info = r.json()
else:
fetch = requests.post(
utils.SPACE_FETCHER_URL,
json={"serialize": self.serialize, "config": json.dumps(self.config)},
json={"config": json.dumps(self.config), "serialize": self.serialize},
)
if fetch.ok:
info = fetch.json()["api"]
Expand Down Expand Up @@ -449,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]
Expand All @@ -473,13 +478,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['python_type']['description']})"
if info["python_type"].get("description")
else ""
abidlabs marked this conversation as resolved.
Show resolved Hide resolved
)
type_ = info["python_type"]["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['python_type']['description']})"
if info["python_type"].get("description")
else ""
abidlabs marked this conversation as resolved.
Show resolved Hide resolved
)
type_ = info["python_type"]["type"]
human_info += f" - [{info['component']}] {utils.sanitize_parameter_names(info['label'])}: {type_}{desc} \n"
else:
human_info += " - None\n"

Expand Down
153 changes: 73 additions & 80 deletions client/python/gradio_client/serializing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@
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:
def serialized_info(self):
"""
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This docstring is out of date

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].
Expand Down Expand Up @@ -57,12 +66,10 @@ 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 {
"raw_input": ["Any", ""],
"raw_output": ["Any", ""],
"serialized_input": ["Any", ""],
"serialized_output": ["Any", ""],
"info": serializer_types["SimpleSerializable"],
"serialized_info": False,
}

def example_inputs(self) -> dict[str, Any]:
Expand All @@ -75,12 +82,10 @@ 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 {
"raw_input": ["str", "string value"],
"raw_output": ["str", "string value"],
"serialized_input": ["str", "string value"],
"serialized_output": ["str", "string value"],
"info": serializer_types["StringSerializable"],
"serialized_info": False,
}

def example_inputs(self) -> dict[str, Any]:
Expand All @@ -93,12 +98,10 @@ 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 {
"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": serializer_types["ListStringSerializable"],
"serialized_info": False,
}

def example_inputs(self) -> dict[str, Any]:
Expand All @@ -111,12 +114,10 @@ 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 {
"raw_input": ["bool", "boolean value"],
"raw_output": ["bool", "boolean value"],
"serialized_input": ["bool", "boolean value"],
"serialized_output": ["bool", "boolean value"],
"info": serializer_types["BooleanSerializable"],
"serialized_info": False,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get rid of serialized_info

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool I was wondering about that as well. Why some components have it and why others don't

}

def example_inputs(self) -> dict[str, Any]:
Expand All @@ -129,12 +130,10 @@ 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 {
"raw_input": ["int | float", "numeric value"],
"raw_output": ["int | float", "numeric value"],
"serialized_input": ["int | float", "numeric value"],
"serialized_output": ["int | float", "numeric value"],
"info": serializer_types["NumberSerializable"],
"serialized_info": False,
}

def example_inputs(self) -> dict[str, Any]:
Expand All @@ -147,13 +146,11 @@ def example_inputs(self) -> dict[str, Any]:
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"],
}
def serialized_info(self):
return {"type": "string", "description": "filepath or URL to image"}

def api_info(self) -> dict[str, bool | dict]:
return {"info": serializer_types["ImgSerializable"], "serialized_info": True}

def example_inputs(self) -> dict[str, Any]:
return {
Expand Down Expand Up @@ -204,20 +201,34 @@ def deserialize(
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]]:
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 {
"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"],
"info": serializer_types["MultipleFileSerializable"],
"serialized_info": True,
}

def api_info(self) -> dict[str, dict | bool]:
return self._single_file_api_info()

def example_inputs(self) -> dict[str, Any]:
return {
"raw": {"is_file": False, "data": media_data.BASE64_FILE},
Expand Down Expand Up @@ -331,19 +342,11 @@ 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"],
}
def serialized_info(self):
return {"type": "string", "description": "filepath or URL to video file"}

def api_info(self) -> dict[str, dict | bool]:
return {"info": serializer_types["FileSerializable"], "serialized_info": True}

def example_inputs(self) -> dict[str, Any]:
return {
Expand Down Expand Up @@ -378,13 +381,11 @@ 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"],
}
def serialized_info(self):
return {"type": "string", "description": "filepath to JSON file"}

def api_info(self) -> dict[str, dict | bool]:
return {"info": serializer_types["JSONSerializable"], "serialized_info": True}

def example_inputs(self) -> dict[str, Any]:
return {
Expand Down Expand Up @@ -430,24 +431,16 @@ def deserialize(


class GallerySerializable(Serializable):
def api_info(self) -> dict[str, list[str]]:
def serialized_info(self):
return {
"type": "string",
"description": "path to directory with images and a file associating images with captions called captions.json",
}

def api_info(self) -> dict[str, dict | bool]:
return {
"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",
],
"info": serializer_types["GallerySerializable"],
"serialized_info": True,
}

def example_inputs(self) -> dict[str, Any]:
Expand Down
Loading