gradio/test/test_events.py
Abubakar Abid 5622331da7
Extend pyright to cover tests as well (#8856)
* add type to test

* ignore certain demos

* notebooks

* type test_video

* more typing

* more typing

* more typing

* add changeset

* more typing

* more

* more

* files

* ds

* ds

* plots

* audio
push

* annotated

* utils

* routes

* iface

* server

* restore

* external

* dep

* components

* chat interface

* fixes

* blocks

* blocks

* blocks

* blocks

* fixes

* fixes

* format

* fix

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
2024-07-21 19:55:18 -07:00

190 lines
5.9 KiB
Python

import ast
import inspect
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
import gradio as gr
class TestEvent:
def test_clear_event(self):
def fn_img_cleared():
print("image cleared")
with gr.Blocks() as demo:
img = gr.Image(
type="pil", label="Start by uploading an image", elem_id="input_image"
)
img.clear(fn_img_cleared, [], [])
assert "dependencies" in demo.config
assert demo.config["dependencies"][0]["targets"][0][1] == "clear"
def test_event_data(self):
with gr.Blocks() as demo:
text = gr.Textbox()
gallery = gr.Gallery()
def fn_img_index(evt: gr.SelectData):
return evt.index
gallery.select(fn_img_index, None, text)
app, _, _ = demo.launch(prevent_thread_lock=True)
client = TestClient(app)
resp = client.post(
f"{demo.local_url}run/predict",
json={"fn_index": 0, "data": [], "event_data": {"index": 1, "value": None}},
)
assert resp.status_code == 200
assert resp.json()["data"][0] == "1"
def test_consecutive_events(self):
def double(x):
return x + x
def reverse(x):
return x[::-1]
def clear():
return ""
with gr.Blocks() as child:
txt1 = gr.Textbox()
txt2 = gr.Textbox()
txt3 = gr.Textbox()
txt1.submit(double, txt1, txt2).then(reverse, txt2, txt3).success(
clear, None, txt1
)
with gr.Blocks() as parent:
txt0 = gr.Textbox()
txt0.submit(lambda x: x, txt0, txt0)
child.render()
assert "dependencies" in parent.config
assert parent.config["dependencies"][1]["trigger_after"] is None
assert parent.config["dependencies"][2]["trigger_after"] == 1
assert parent.config["dependencies"][3]["trigger_after"] == 2
assert not parent.config["dependencies"][2]["trigger_only_on_success"]
assert parent.config["dependencies"][3]["trigger_only_on_success"]
def test_on_listener(self):
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
def greet(name):
return "Hello " + name + "!"
gr.on(
triggers=[name.submit, greet_btn.click, demo.load],
fn=greet,
inputs=name,
outputs=output,
)
with gr.Row():
num1 = gr.Slider(1, 10)
num2 = gr.Slider(1, 10)
num3 = gr.Slider(1, 10)
output = gr.Number(label="Sum")
@gr.on(inputs=[num1, num2, num3], outputs=output)
def sum(a, b, c):
return a + b + c
assert "dependencies" in demo.config
assert demo.config["dependencies"][0]["targets"] == [
(name._id, "submit"),
(greet_btn._id, "click"),
(demo._id, "load"),
]
assert demo.config["dependencies"][1]["targets"] == [
(num1._id, "change"),
(num2._id, "change"),
(num3._id, "change"),
]
def test_load_chaining(self):
calls = 0
def increment():
nonlocal calls
calls += 1
return str(calls)
with gr.Blocks() as demo:
out = gr.Textbox(label="Call counter")
demo.load(increment, inputs=None, outputs=out).then(
increment, inputs=None, outputs=out
)
assert "dependencies" in demo.config
assert demo.config["dependencies"][0]["targets"][0][1] == "load"
assert demo.config["dependencies"][0]["trigger_after"] is None
assert demo.config["dependencies"][1]["targets"][0][1] == "then"
assert demo.config["dependencies"][1]["trigger_after"] == 0
def test_load_chaining_reuse(self):
calls = 0
def increment():
nonlocal calls
calls += 1
return str(calls)
with gr.Blocks() as demo:
out = gr.Textbox(label="Call counter")
demo.load(increment, inputs=None, outputs=out).then(
increment, inputs=None, outputs=out
)
with gr.Blocks() as demo2:
demo.render()
assert "dependencies" in demo2.config
assert demo2.config["dependencies"][0]["targets"][0][1] == "load"
assert demo2.config["dependencies"][0]["trigger_after"] is None
assert demo2.config["dependencies"][1]["targets"][0][1] == "then"
assert demo2.config["dependencies"][1]["trigger_after"] == 0
class TestEventErrors:
def test_event_defined_invalid_scope(self):
with gr.Blocks() as demo:
textbox = gr.Textbox()
textbox.blur(lambda x: x + x, textbox, textbox)
with pytest.raises(AttributeError):
demo.load(lambda: "hello", None, textbox)
with pytest.raises(AttributeError):
textbox.change(lambda x: x + x, textbox, textbox)
def test_event_pyi_file_matches_source_code():
"""Test that the template used to create pyi files (search INTERFACE_TEMPLATE in component_meta) matches the source code of EventListener._setup."""
code = (
Path(__file__).parent / ".." / "gradio" / "components" / "button.pyi"
).read_text()
mod = ast.parse(code)
segment = None
for node in ast.walk(mod):
if isinstance(node, ast.FunctionDef) and node.name == "click":
segment = ast.get_source_segment(code, node)
# This would fail if Button no longer has a click method
assert segment
sig = inspect.signature(gr.Button.click)
for param in sig.parameters.values():
if param.name == "block":
continue
assert param.name in segment