Skip to content

Commit

Permalink
Introduce decorator syntax for event listeners (#5395)
Browse files Browse the repository at this point in the history
* changes

* add changeset

* changes

* changes

* Update fuzzy-numbers-repair.md

* Update fuzzy-numbers-repair.md

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
aliabid94 and gradio-pr-bot committed Sep 1, 2023
1 parent abf1c57 commit 55fed04
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 4 deletions.
20 changes: 20 additions & 0 deletions .changeset/fuzzy-numbers-repair.md
@@ -0,0 +1,20 @@
---
"gradio": minor
---

highlight:

#### Added the ability to attach event listeners via decorators

e.g.

```python
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")

@greet_btn.click(inputs=name, outputs=output)
def greet(name):
return "Hello " + name + "!"
```
1 change: 1 addition & 0 deletions demo/hello_blocks_decorator/run.ipynb
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: hello_blocks_decorator"]}, {"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", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", "\n", " @greet_btn.click(inputs=name, outputs=output)\n", " def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", " \n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
16 changes: 16 additions & 0 deletions demo/hello_blocks_decorator/run.py
@@ -0,0 +1,16 @@
import gradio as gr


with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")

@greet_btn.click(inputs=name, outputs=output)
def greet(name):
return "Hello " + name + "!"



if __name__ == "__main__":
demo.launch()
Binary file added demo/hello_blocks_decorator/screenshot.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion gradio/blocks.py
Expand Up @@ -1626,7 +1626,7 @@ def get_time():
every=every,
no_target=True,
)
return Dependency(self, dep, dep_index)
return Dependency(self, dep, dep_index, fn)

def clear(self):
"""Resets the layout of the Blocks object."""
Expand Down
40 changes: 37 additions & 3 deletions gradio/events.py
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

from functools import wraps
from typing import TYPE_CHECKING, Any, Callable, Literal, Sequence

from gradio_client.documentation import document, set_documentation_group
Expand Down Expand Up @@ -44,8 +45,9 @@ def __init__(self: Any):


class Dependency(dict):
def __init__(self, trigger, key_vals, dep_index):
def __init__(self, trigger, key_vals, dep_index, fn):
super().__init__(key_vals)
self.fn = fn
self.trigger = trigger
self.then = EventListenerMethod(
self.trigger,
Expand All @@ -66,6 +68,9 @@ def __init__(self, trigger, key_vals, dep_index):
Triggered after directly preceding event is completed, if it was successful.
"""

def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)


class EventListenerMethod:
"""
Expand All @@ -90,7 +95,7 @@ def __init__(

def __call__(
self,
fn: Callable | None,
fn: Callable | None | Literal["decorator"] = "decorator",
inputs: Component | Sequence[Component] | set[Component] | None = None,
outputs: Component | Sequence[Component] | None = None,
api_name: str | None | Literal[False] = None,
Expand Down Expand Up @@ -123,6 +128,35 @@ def __call__(
cancels: A list of other events to cancel when this listener is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method. Functions that have not yet run (or generators that are iterating) will be cancelled, but functions that are currently running will be allowed to finish.
every: Run this event 'every' number of seconds while the client connection is open. Interpreted in seconds. Queue must be enabled.
"""
if fn == "decorator":

def wrapper(func):
self.__call__(
func,
inputs,
outputs,
api_name,
status_tracker,
scroll_to_output,
show_progress,
queue,
batch,
max_batch_size,
preprocess,
postprocess,
cancels,
every,
_js,
)

@wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)

return inner

return Dependency(None, {}, None, wrapper)

if status_tracker:
warn_deprecation(
"The 'status_tracker' parameter has been deprecated and has no effect."
Expand Down Expand Up @@ -160,7 +194,7 @@ def __call__(
set_cancel_events(self.trigger, self.event_name, cancels)
if self.callback:
self.callback()
return Dependency(self.trigger, dep, dep_index)
return Dependency(self.trigger, dep, dep_index, fn)


@document("*change", inherit=True)
Expand Down
Expand Up @@ -13,6 +13,10 @@ $demo_hello_blocks
- Next come the Components. These are the same Components used in `Interface`. However, instead of being passed to some constructor, Components are automatically added to the Blocks as they are created within the `with` clause.
- Finally, the `click()` event listener. Event listeners define the data flow within the app. In the example above, the listener ties the two Textboxes together. The Textbox `name` acts as the input and Textbox `output` acts as the output to the `greet` method. This dataflow is triggered when the Button `greet_btn` is clicked. Like an Interface, an event listener can take multiple inputs or outputs.

You can also attach event listeners using decorators - skip the `fn` argument and assign `inputs` and `outputs` directly:

$code_hello_blocks_decorator

## Event Listeners and Interactivity

In the example above, you'll notice that you are able to edit Textbox `name`, but not Textbox `output`. This is because any Component that acts as an input to an event listener is made interactive. However, since Textbox `output` acts only as an output, Gradio determines that it should not be made interactive. You can override the default behavior and directly configure the interactivity of a Component with the boolean `interactive` keyword argument.
Expand Down

0 comments on commit 55fed04

Please sign in to comment.