Skip to content

Run events every given number of seconds#2512

Merged
freddyaboulton merged 26 commits into
mainfrom
2050-streaming-data
Oct 28, 2022
Merged

Run events every given number of seconds#2512
freddyaboulton merged 26 commits into
mainfrom
2050-streaming-data

Conversation

@freddyaboulton
Copy link
Copy Markdown
Collaborator

@freddyaboulton freddyaboulton commented Oct 21, 2022

Description

Adds the every key word argument to the events so that they run every number of seconds.

Fixes: #2050

Demo

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()

live_demo

Checklist:

  • I have performed a self-review of my own code
  • I have added a short summary of my change to the CHANGELOG.md
  • My code follows the style guidelines of this project
  • I have commented my code in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

A note about the CHANGELOG

Hello 👋 and thank you for contributing to Gradio!

All pull requests must update the change log located in CHANGELOG.md, unless the pull request is labeled with the "no-changelog-update" label.

Please add a brief summary of the change to the Upcoming Release > Full Changelog section of the CHANGELOG.md file and include
a link to the PR (formatted in markdown) and a link to your github profile (if you like). For example, "* Added a cool new feature by [@myusername](link-to-your-github-profile) in [PR 11111](https://github.com/gradio-app/gradio/pull/11111)".

If you would like to elaborate on your change further, feel free to include a longer explanation in the other sections.
If you would like an image/gif/video showcasing your feature, it may be best to edit the CHANGELOG file using the
GitHub web UI since that lets you upload files directly via drag-and-drop.

@github-actions
Copy link
Copy Markdown
Contributor

All the demos for this PR have been deployed at https://huggingface.co/spaces/gradio-pr-deploys/pr-2512-all-demos

@abidlabs
Copy link
Copy Markdown
Member

abidlabs commented Oct 22, 2022

So cool! every and cancels are a nice combo!

@freddyaboulton freddyaboulton marked this pull request as ready for review October 27, 2022 17:22
@freddyaboulton freddyaboulton changed the title WIP: Run events every given number of seconds Run events every given number of seconds Oct 27, 2022
Comment thread gradio/utils.py
return continuous_fn


async def cancel_tasks(task_ids: List[str]):
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.

Refactoring some of the code from the cancels PR so that it's easier to use in other parts of the codebase

Comment thread gradio/blocks.py
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!

Comment thread gradio/blocks.py Outdated
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,

Comment thread gradio/utils.py
Comment on lines +693 to +704
def get_continuous_fn(fn, every):
def continuous_fn(*args):
called_before = False
while True:
output = fn(*args)
if called_before:
time.sleep(every)
else:
called_before = True
yield output

return continuous_fn
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.

Suggested change
def get_continuous_fn(fn, every):
def continuous_fn(*args):
called_before = False
while True:
output = fn(*args)
if called_before:
time.sleep(every)
else:
called_before = True
yield output
return continuous_fn
def get_continuous_fn(fn, every):
def continuous_fn(*args):
while True:
output = fn(*args)
yield output
time.sleep(every)
return continuous_fn

Also, would it be better to use asyncio.sleep here instead of time.sleep so that the queue worker is free to do something else in the meantime?

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.

Ah I see so you later avoid putting such events in the queue altogether because they don't get completed. So I'm not sure how much of a difference it makes to use asyncio.sleep but I do think it's probably better than having a background thread just sit there doing nothing

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.

Lol love this refactor! Thank you.

Yea originally I had this as an async function but the problem with that is that you only want to sleep after the first function call. Tricky to implement without a generator and we don't currently support async generators. Will think about it though (may add async generators to #2552 )

@abidlabs
Copy link
Copy Markdown
Member

abidlabs commented Oct 28, 2022

This looks great @freddyaboulton! Tested it and works well, and plays nicely with cancels.

One suggestion would be to add some more documentation about it (maybe in this Guide?) since this is a pretty big feature. I would also mention somewhere that if you define an event that runs every seconds, this does not take into account the runtime of the function itself -- so a 2 second prediction function running with every=5 actually runs every 7 seconds.

Very excited about the possibilities this opens up!

Copy link
Copy Markdown
Member

@pngwn pngwn left a comment

Choose a reason for hiding this comment

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

lgtm! Awesome work @freddyaboulton!

@freddyaboulton freddyaboulton merged commit a23eb53 into main Oct 28, 2022
@freddyaboulton freddyaboulton deleted the 2050-streaming-data branch October 28, 2022 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make it easier to work with streaming data

3 participants