Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
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
49 changes: 47 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
# Upcoming Release

## New Features:
No changes to highlight.

### Running Events Continuously
Gradio now supports the ability to run an event continuously on a fixed schedule. To use this feature,
pass `every=# of seconds` to the event definition. This will run the event every given number of seconds!

This can be used to:
* Create live visualizations that show the most up to date data
* Refresh the state of the frontend automatically in response to changes in the backend

Here is an example of a live plot that refreshes every half second:
```python
import math
import gradio as gr
import plotly.express as px
import numpy as np


plot_end = 2 * math.pi


def get_plot(period=1):
global plot_end
x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
y = np.sin(2*math.pi*period * x)
fig = px.line(x=x, y=y)
plot_end += 2 * math.pi
return fig


with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
gr.Markdown("Change the value of the slider to automatically update the plot")
period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
plot = gr.Plot(label="Plot (updates every half second)")

dep = demo.load(get_plot, None, plot, every=0.5)
period.change(get_plot, period, plot, every=0.5, cancels=[dep])

demo.queue().launch()
```

![live_demo](https://user-images.githubusercontent.com/41651716/198357377-633ce460-4e31-47bd-8202-1440cdd6fe19.gif)




## Bug Fixes:
* Apply appropriate alt text to all gallery images. [@camenduru](https://github.com/camenduru) in [PR 2358](https://github.com/gradio-app/gradio/pull/2538)
Expand All @@ -15,7 +60,7 @@ No changes to highlight.
No changes to highlight.

## Full Changelog:
No changes to highlight.
* Added the `every` keyword to event listeners that runs events on a fixed schedule by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2512](https://github.com/gradio-app/gradio/pull/2512)

## Contributors Shoutout:
No changes to highlight.
Expand Down
1 change: 1 addition & 0 deletions demo/live_dashboard/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plotly
42 changes: 42 additions & 0 deletions demo/live_dashboard/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import math
import gradio as gr
import datetime
import plotly.express as px
import numpy as np


def get_time():
return datetime.datetime.now()


plot_end = 2 * math.pi


def get_plot(period=1):
global plot_end
x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
y = np.sin(2*math.pi*period * x)
fig = px.line(x=x, y=y)
plot_end += 2 * math.pi
return fig


with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
c_time2 = gr.Textbox(label="Current Time refreshed every second")
gr.Markdown("Change the value of the slider to automatically update the plot")
period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
plot = gr.Plot(label="Plot (updates every half second)")
with gr.Column():
name = gr.Textbox(label="Enter your name")
greeting = gr.Textbox(label="Greeting")
button = gr.Button(value="Greet")
button.click(lambda s: f"Hello {s}", name, greeting)

demo.load(lambda: datetime.datetime.now(), None, c_time2, every=1)
dep = demo.load(get_plot, None, plot, every=0.5)
period.change(get_plot, period, plot, every=0.5, cancels=[dep])

if __name__ == "__main__":
demo.queue().launch()
32 changes: 27 additions & 5 deletions gradio/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@
set_documentation_group,
)
from gradio.exceptions import DuplicateBlockError
from gradio.utils import component_or_layout_class, delete_none, get_cancel_function
from gradio.utils import (
component_or_layout_class,
delete_none,
get_cancel_function,
get_continuous_fn,
)

set_documentation_group("blocks")

Expand Down Expand Up @@ -134,6 +139,7 @@ def set_event_trigger(
batch: bool = False,
max_batch_size: int = 4,
cancels: List[int] | None = None,
every: int | None = None,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
every: int | None = None,
every: float | None = None,

) -> Dict[str, Any]:
"""
Adds an event to the component's dependencies.
Expand Down Expand Up @@ -163,10 +169,22 @@ def set_event_trigger(
inputs = [inputs]
if not isinstance(outputs, list):
outputs = [outputs]

if Context.root_block is None:
raise AttributeError(
f"{event_name}() and other events can only be called within a Blocks context."
)
if every is not None and every <= 0:
raise ValueError("Parameter every must be positive or None")
if every and batch:
raise ValueError(
f"Cannot run {event_name} event in a batch and every {every} seconds. "
"Either batch is True or every is non-zero but not both."
)

if every:
fn = get_continuous_fn(fn, every)

Context.root_block.fns.append(BlockFunction(fn, preprocess, postprocess))
if api_name is not None:
api_name_ = utils.append_unique_suffix(
Expand All @@ -177,6 +195,7 @@ def set_event_trigger(
"api_name {} already exists, using {}".format(api_name, api_name_)
)
api_name = api_name_

dependency = {
"targets": [self._id] if not no_target else [],
"trigger": event_name,
Expand All @@ -188,6 +207,7 @@ def set_event_trigger(
"api_name": api_name,
"scroll_to_output": scroll_to_output,
"show_progress": show_progress,
"every": every,
"batch": batch,
"max_batch_size": max_batch_size,
"cancels": cancels or [],
Expand Down Expand Up @@ -885,7 +905,6 @@ async def process_api(
data = [self.postprocess_data(fn_index, o, state) for o in zip(*preds)]
data = list(zip(*data))
is_generating, iterator = None, None

else:
inputs = self.preprocess_data(fn_index, inputs, state)
iterator = iterators.get(fn_index, None) if iterators else None
Expand Down Expand Up @@ -983,8 +1002,9 @@ def load(
api_key: Optional[str] = None,
alias: Optional[str] = None,
_js: Optional[str] = None,
every: None | int = None,
Copy link
Copy Markdown
Member

@abidlabs abidlabs Oct 27, 2022

Choose a reason for hiding this comment

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

Unrelated to your PR, this event trigger is missing many arguments, such as batch. It would be good to add if possible (or we can create a separate issue for it)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll do that in a separate PR! Thank you for pointing this out!

**kwargs,
) -> Blocks | None:
) -> Blocks | Dict[str, Any] | None:
"""
For reverse compatibility reasons, this is both a class method and an instance
method, the two of which, confusingly, do two completely different things.
Expand All @@ -1002,6 +1022,7 @@ def load(
fn: Instance Method - Callable function
inputs: Instance Method - input list
outputs: Instance Method - output list
every: Run this event 'every' number of seconds. Interpreted in seconds. Queue must be enabled.
Example:
import gradio as gr
import datetime
Expand All @@ -1026,13 +1047,14 @@ def get_time():
kwargs["outputs"] = outputs
return external.load_blocks_from_repo(name, src, api_key, alias, **kwargs)
else:
self_or_cls.set_event_trigger(
return self_or_cls.set_event_trigger(
event_name="load",
fn=fn,
inputs=inputs,
outputs=outputs,
no_target=True,
js=_js,
no_target=True,
every=every,
)

def clear(self):
Expand Down
Loading