-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Use orjson to serialize dict including np.array #8041
Conversation
🪼 branch checks and previews
Install Gradio from this PR pip install https://gradio-builds.s3.amazonaws.com/4102b4bc0f1260dedcbbe6ec8d95666b7b615551/gradio-4.27.0-py3-none-any.whl Install Gradio Python Client from this PR pip install "gradio-client @ git+https://github.com/gradio-app/gradio@4102b4bc0f1260dedcbbe6ec8d95666b7b615551#subdirectory=client/python" |
🦄 change detectedThis Pull Request includes changes to the following packages.
With the following changelog entry.
Maintainers or the PR author can modify the PR title to modify this entry.
|
Confirmed that this PR does not degrade performance |
Thank you! Let me add tests and some more fixes. |
58a269a
to
d738dc0
Compare
When an error occurs at the gradio/client/js/src/client.ts Lines 317 to 321 in 5767310
As an example, you can reproduce the error by running the code below on the main branch and it feels like the app crashes silently while we can see the error log on the server-side terminal. import gradio as gr
import numpy as np
with gr.Blocks() as demo:
inp = gr.JSON(
value=np.array([1, 2, 3])
)
out = gr.JSON()
btn = gr.Button("Submit")
btn.click(lambda x: x, inp, out)
demo.launch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @whitphx and nice that it does not degrade performance. Just left a few comments on the demos
@@ -0,0 +1,21 @@ | |||
import { test, expect } from "@gradio/tootils"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need separate tests for Blocks and Interface since an Interface uses the same server as a Blocks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added them because each caused a different error actually.
- Interface: an error saying "Type is not JSON serializable: numpy.ndarray". This is fixed by https://github.com/gradio-app/gradio/pull/8041/files#diff-324a7165f5d5a8823a28b76f5653fa45f32c8144c82b2e528882c97c7eae534fR807-R812
- Blocks: in this case, there is no visually observable error but the app silently crashes because of an error at the
/info
endpoint. This is fixed by https://github.com/gradio-app/gradio/pull/8041/files#diff-324a7165f5d5a8823a28b76f5653fa45f32c8144c82b2e528882c97c7eae534fR410
@@ -1,6 +0,0 @@ | |||
import gradio as gr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh missed it, sorry.
Revert renaming.
Actually I am doubting why we even need orjson at all? The docstring of the json component's postprocess says |
Good point. import gradio as gr
import numpy as np
demo = gr.Interface(
fn=lambda x: x,
inputs=gr.JSON(value=np.array([1, 2, 3])),
outputs="json",
)
demo.launch() If we treat this as a spec following the docstring, not a bug, the changes in this PR will be reverted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,21 @@ | |||
import { test, expect } from "@gradio/tootils"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The json_component_blocks
demo does not exist so we should remove this test file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry forgot to rename this.
This should be json_component.spec.ts
instead of deleting.
…component.spec.ts
demo/json_component_interface/run.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems identical to the other demo, let's just delete this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are testing the same thing though. If we wanted to make sure the info route works, i think we can manually access that route with playwright?
We can change this to
|
I'm not sure if we should. |
Good point. I'm leaning towards (2) because I think it is clearer what the postprocessing function for each component is doing. But what do you mean by this:
Why would there be a regression in |
Pushed a commit to take the option (2).
It was an example, and I wanted to say, we have to take care of this problem about all the components whose def gendata():
return np.zeros((10, 10))
demo = gr.Interface(
fn=gendata,
inputs=None,
outputs="checkbox",
)
demo.launch() Anyway it's ok to patch each In other words, without the globally-applied fallback like the option (1), we have to take care of the |
Added a patch to |
Found #7415 did a similar discussion. |
Looks good @whitphx ! Thanks for making the fixes! Now that we do the json-handling in each component, we can use python unit tests as opposed to e2e tests? |
gradio/components/checkbox.py
Outdated
if isinstance(value, Iterable): | ||
# Handles the cases of NumPy arrays for instance, in the same way as lists. | ||
return len(value) > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this. It may technically be a breaking change to not include this, but the behavior when a numpy array is passed into a gr.Checkbox is very unclear and I think this addition will just confuse readers of the codebase.
In fact, when I test bool(np.array([]))
it returns False
with a warning that in future versions of numpy, it will return an error. So the breaking change is really on numpy's side. I think we can just return bool(value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may technically be a breaking change to not include this,
Exactly. This is what I meant as a "regression", and I added this commit as an example to clarify Checkbox's behavior in such edge cases.
The options are, (1) to cover as many cases as possible like this, or (2) to support only reasonable cases e.g. only bool
value in the case of checkbox and raise an error for other values.
This commit is (1) to avoid the regression, and bool(value)
as you wrote is (2). I think (2) is also a reasonable option.
Will take it.
Please note that bool(np.array([1,2]))
raises a ValueError
,
so if we take the option 2 (bool(value)
), the app below still crashes without a clear error message. We will admit it as a design.
def gendata():
return np.zeros((10, 10))
demo = gr.Interface(
fn=gendata,
inputs=None,
outputs="checkbox",
)
demo.launch()
# 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool
Looks good to me @whitphx. I agree with @freddyaboulton that it would be better if we could replace the e2e tests with python unit tests now for speed and remove the |
@@ -83,9 +84,18 @@ def postprocess(self, value: dict | list | str | None) -> dict | list | None: | |||
if value is None: | |||
return None | |||
if isinstance(value, str): | |||
return json.loads(value) | |||
return orjson.loads(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abidlabs @freddyaboulton Apart from this PR,
I found this implementation is different from the doc string which says
Expects a
str
filepath to a file containing valid JSON
while json.loads()
expects a JSON string, not a file path (but json.load()
does).
Which is the correct, the impl or the doc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's the implementation @whitphx !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I updated the docstring about it together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch @whitphx
…as they are not reasonable values to be interpreted as checkbox's value
Updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to merge @whitphx ! Thanks for all the hard work on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to create this as a separate file. We should refactor test_components.py
into separate component files to mirror the component .py files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tbh I missed test_components.py
😅
Will do the refactoring in a separate PR.
Reminder -> #8125
@@ -38,7 +39,7 @@ def __init__( | |||
): | |||
""" | |||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated this as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fixes @whitphx!
Description
gr.JSON
causes errors when the value includes NumPy arrays.There are two different cases:
TODO: