Use orjson to serialize dict including np.array (#8041)

* Use orjson to serialize dict including np.array

* add changeset

* Update json_component demo and add an E2E test using it

* Rename demo/json_component -> demo/json_component_blocks

* Add json_component_interface demo and an E2E test using it

* Fix to await assertion promises

* Revert renaming of json_component demo

* add changeset

* Rename js/app/test/json_component_blocks.spec.ts -> js/app/test/json_component.spec.ts

* Revert changes in routes.py and add orjson to json_component.py

* Update gr.Checkbox.postprocess to ensure a bool value is returned

* add changeset

* Remove the if-block in gr.Checkbox.postprocess handling NumPy arrays as they are not reasonable values to be interpreted as checkbox's value

* Update gr.JSON's docstring

* Add test/components/test_json_component.py

* Remove JSON component E2E tests

* Update gr.JSON's docstring

* docstring

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
Yuichiro Tachibana (Tsuchiya) 2024-04-25 08:13:01 +01:00 committed by GitHub
parent e089e4cb4a
commit 937c858371
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 68 additions and 9 deletions

View File

@ -0,0 +1,5 @@
---
"gradio": patch
---
fix:Use orjson to serialize dict including np.array

View File

@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: json_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr \n", "\n", "with gr.Blocks() as demo:\n", " gr.JSON(value={\"Key 1\": \"Value 1\", \"Key 2\": {\"Key 3\": \"Value 2\", \"Key 4\": \"Value 3\"}, \"Key 5\": [\"Item 1\", \"Item 2\", \"Item 3\"]})\n", "\n", "demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: json_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import numpy as np\n", "\n", "with gr.Blocks() as demo:\n", " inp = gr.JSON(\n", " label=\"InputJSON\",\n", " value={\n", " \"Key 1\": \"Value 1\",\n", " \"Key 2\": {\"Key 3\": \"Value 2\", \"Key 4\": \"Value 3\"},\n", " \"Key 5\": [\"Item 1\", \"Item 2\", \"Item 3\"],\n", " \"Key 6\": 123,\n", " \"Key 7\": 123.456,\n", " \"Key 8\": True,\n", " \"Key 9\": False,\n", " \"Key 10\": None,\n", " \"Key 11\": np.array([1, 2, 3]),\n", " }\n", " )\n", " out = gr.JSON(label=\"OutputJSON\")\n", " btn = gr.Button(\"Submit\")\n", " btn.click(lambda x: x, inp, out)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -1,6 +1,25 @@
import gradio as gr
import gradio as gr
import numpy as np
with gr.Blocks() as demo:
gr.JSON(value={"Key 1": "Value 1", "Key 2": {"Key 3": "Value 2", "Key 4": "Value 3"}, "Key 5": ["Item 1", "Item 2", "Item 3"]})
inp = gr.JSON(
label="InputJSON",
value={
"Key 1": "Value 1",
"Key 2": {"Key 3": "Value 2", "Key 4": "Value 3"},
"Key 5": ["Item 1", "Item 2", "Item 3"],
"Key 6": 123,
"Key 7": 123.456,
"Key 8": True,
"Key 9": False,
"Key 10": None,
"Key 11": np.array([1, 2, 3]),
}
)
out = gr.JSON(label="OutputJSON")
btn = gr.Button("Submit")
btn.click(lambda x: x, inp, out)
demo.launch()
if __name__ == "__main__":
demo.launch()

View File

@ -95,4 +95,4 @@ class Checkbox(FormComponent):
Returns:
The same `bool` value that is set as the status of the checkbox
"""
return value
return bool(value)

View File

@ -5,6 +5,7 @@ from __future__ import annotations
import json
from typing import Any, Callable
import orjson
from gradio_client.documentation import document
from gradio.components.base import Component
@ -38,7 +39,7 @@ class JSON(Component):
):
"""
Parameters:
value: Default value. If callable, the function will be called whenever the app loads to set the initial value of the component.
value: Default value as a valid JSON `str` -- or a `list` or `dict` that can be serialized to a JSON string. If callable, the function will be called whenever the app loads to set the initial value of the component.
label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
show_label: if True, will display label.
@ -76,16 +77,25 @@ class JSON(Component):
def postprocess(self, value: dict | list | str | None) -> dict | list | None:
"""
Parameters:
value: Expects a `str` filepath to a file containing valid JSON -- or a `list` or `dict` that is valid JSON
value: Expects a valid JSON `str` -- or a `list` or `dict` that can be serialized to a JSON string. The `list` or `dict` value can contain numpy arrays.
Returns:
Returns the JSON as a `list` or `dict`.
"""
if value is None:
return None
if isinstance(value, str):
return json.loads(value)
return orjson.loads(value)
else:
return value
# Use orjson to convert NumPy arrays and datetime objects to JSON.
# This ensures a backward compatibility with the previous behavior.
# See https://github.com/gradio-app/gradio/pull/8041
return orjson.loads(
orjson.dumps(
value,
option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_PASSTHROUGH_DATETIME,
default=str,
)
)
def example_payload(self) -> Any:
return {"foo": "bar"}

View File

@ -0,0 +1,25 @@
import json
import numpy as np
import pytest
from gradio.components.json_component import JSON
class TestJSON:
@pytest.mark.parametrize(
"value, expected",
[
(None, None),
(True, True),
([1, 2, 3], [1, 2, 3]),
([np.array([1, 2, 3])], [[1, 2, 3]]),
({"foo": [1, 2, 3]}, {"foo": [1, 2, 3]}),
({"foo": np.array([1, 2, 3])}, {"foo": [1, 2, 3]}),
],
)
def test_postprocess_returns_json_serializable_value(self, value, expected):
json_component = JSON()
postprocessed_value = json_component.postprocess(value)
assert postprocessed_value == expected
assert json.loads(json.dumps(postprocessed_value)) == expected