-
Couldn't load subscription status.
- Fork 21
input task button component entry #127
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
e265f8b
19cfd85
bc2a1fb
f11624d
5569f94
4b6f2ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import asyncio # << | ||
| import datetime | ||
|
|
||
| from shiny import App, Inputs, Outputs, Session, reactive, render, ui | ||
|
|
||
| app_ui = ui.page_fluid( | ||
| ui.p("The time is ", ui.output_text("current_time", inline=True)), | ||
| ui.hr(), | ||
| ui.input_task_button("btn", "Square 5 slowly"), # << | ||
| ui.output_text("sq"), | ||
| ) | ||
|
|
||
|
|
||
| def server(input: Inputs, output: Outputs, session: Session): | ||
|
|
||
| @render.text | ||
| def current_time(): | ||
| reactive.invalidate_later(1) | ||
| return datetime.datetime.now().strftime("%H:%M:%S %p") | ||
|
|
||
| @ui.bind_task_button(button_id="btn") # << | ||
| @reactive.extended_task # << | ||
| async def sq_value(x): # << | ||
| await asyncio.sleep(2) # << | ||
| return x**2 # << | ||
|
|
||
| @reactive.effect # << | ||
| @reactive.event(input.btn) # << | ||
| def btn_click(): | ||
| sq_value(5) # << | ||
|
|
||
| @render.text | ||
| def sq(): | ||
| return f"5 squared is: {str(sq_value.result())}" | ||
|
|
||
|
|
||
| app = App(app_ui, server) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import asyncio | ||
| import datetime | ||
|
|
||
| from shiny import App, Inputs, Outputs, Session, reactive, render, ui | ||
|
|
||
| app_ui = ui.page_fluid( | ||
| ui.p("The time is ", ui.output_text("current_time", inline=True)), | ||
| ui.hr(), | ||
| ui.input_task_button("btn", "Square 5 slowly"), | ||
| ui.output_text("sq"), | ||
| ) | ||
|
|
||
|
|
||
| def server(input: Inputs, output: Outputs, session: Session): | ||
|
|
||
| @render.text | ||
| def current_time(): | ||
| reactive.invalidate_later(1) | ||
| return datetime.datetime.now().strftime("%H:%M:%S %p") | ||
|
|
||
| @ui.bind_task_button(button_id="btn") | ||
| @reactive.extended_task | ||
| async def sq_value(x): | ||
| await asyncio.sleep(2) | ||
| return x**2 | ||
|
|
||
| @reactive.effect | ||
| @reactive.event(input.btn) | ||
| def btn_click(): | ||
| sq_value(5) | ||
|
|
||
| @render.text | ||
| def sq(): | ||
| return f"5 squared is: {str(sq_value.result())}" | ||
|
|
||
|
|
||
| app = App(app_ui, server) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import asyncio # << | ||
| import datetime | ||
|
|
||
| from shiny import reactive | ||
| from shiny.express import render, input, ui | ||
|
|
||
|
|
||
| @render.text | ||
| def current_time(): | ||
| reactive.invalidate_later(1) | ||
| time_str = datetime.datetime.now().strftime("%H:%M:%S %p") | ||
| return f"The time is, {time_str}" | ||
|
|
||
|
|
||
| ui.hr() | ||
|
|
||
| ui.input_task_button("btn", "Square 5 slowly") # << | ||
|
|
||
|
|
||
| @ui.bind_task_button(button_id="btn") # << | ||
| @reactive.extended_task # << | ||
| async def sq_values(x): # << | ||
| await asyncio.sleep(2) # << | ||
| return x**2 # << | ||
|
|
||
|
|
||
| @reactive.effect # << | ||
| @reactive.event(input.btn) # << | ||
| def btn_click(): # << | ||
| sq_values(5) # << | ||
|
|
||
|
|
||
| @render.text | ||
| def sq(): | ||
| return str(sq_values.result()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import asyncio | ||
| from shiny import App, Inputs, Outputs, Session, reactive, ui | ||
|
|
||
| app_ui = ui.page_fluid( | ||
| ui.input_task_button("btn", "Add numbers slowly"), | ||
| ) | ||
|
|
||
|
|
||
| def server(input: Inputs, output: Outputs, session: Session): | ||
|
|
||
| @ui.bind_task_button(button_id="btn") | ||
| @reactive.extended_task | ||
| async def long_calculation(): | ||
| await asyncio.sleep(1) | ||
|
|
||
| @reactive.effect | ||
| @reactive.event(input.btn) | ||
| def btn_click(): | ||
| long_calculation() | ||
|
|
||
|
|
||
| app = App(app_ui, server) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import asyncio | ||
| import datetime | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
|
|
||
| from shiny import App, Inputs, Outputs, Session, reactive, render, ui | ||
|
|
||
| app_ui = ui.page_fluid( | ||
| ui.input_numeric("x", "x", value=5), | ||
| ui.input_task_button("btn", "Square number slowly"), | ||
| ui.output_text("sq"), | ||
| ui.hr(), | ||
| ui.p( | ||
| "While computing, the time updates and you can still interact with the histogram." | ||
| ), | ||
| ui.p("The time is ", ui.output_text("current_time", inline=True)), | ||
| ui.input_slider("n", "Number of observations", min=0, max=1000, value=500), | ||
| ui.output_plot("plot"), | ||
| ) | ||
|
|
||
|
|
||
| def server(input: Inputs, output: Outputs, session: Session): | ||
| @render.plot(alt="A histogram") | ||
| def plot(): | ||
| np.random.seed(19680801) | ||
| x = 100 + 15 * np.random.randn(input.n()) | ||
| fig, ax = plt.subplots() | ||
| ax.hist(x, bins=30, density=True) | ||
| return fig | ||
|
|
||
| @render.text | ||
| def current_time(): | ||
| reactive.invalidate_later(1) | ||
| return datetime.datetime.now().strftime("%H:%M:%S %p") | ||
|
|
||
| @ui.bind_task_button(button_id="btn") | ||
| @reactive.extended_task | ||
| async def sq_values(x): | ||
| await asyncio.sleep(5) | ||
| return x**2 | ||
|
|
||
| @reactive.effect | ||
| @reactive.event(input.btn) | ||
| def btn_click(): | ||
| sq_values(input.x()) | ||
|
|
||
| @render.text | ||
| def sq(): | ||
| return str(sq_values.result()) | ||
|
|
||
|
|
||
| app = App(app_ui, server) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import asyncio | ||
| import datetime | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
|
|
||
| from shiny import reactive | ||
| from shiny.express import render, input, ui | ||
|
|
||
| ui.input_numeric("x", "x", value=5), | ||
| ui.input_task_button("btn", "Square number slowly") | ||
|
|
||
|
|
||
| @ui.bind_task_button(button_id="btn") | ||
| @reactive.extended_task | ||
| async def sq_values(x): | ||
| await asyncio.sleep(5) | ||
| return x**2 | ||
|
|
||
|
|
||
| @reactive.effect | ||
| @reactive.event(input.btn) | ||
| def btn_click(): | ||
| sq_values(input.x()) | ||
|
|
||
|
|
||
| @render.text | ||
| def sq(): | ||
| return str(sq_values.result()) | ||
|
|
||
|
|
||
| ui.hr() | ||
|
|
||
| ui.p("While computing, the time updates and you can still interact with the histogram.") | ||
|
|
||
|
|
||
| @render.text | ||
| def current_time(): | ||
| reactive.invalidate_later(1) | ||
| dt_str = datetime.datetime.now().strftime("%H:%M:%S %p") | ||
| return f"The time is {dt_str}" | ||
|
|
||
|
|
||
| ui.input_slider("n", "Number of observations", min=0, max=1000, value=500), | ||
|
|
||
|
|
||
| @render.plot(alt="A histogram") | ||
| def plot(): | ||
| np.random.seed(19680801) | ||
| x = 100 + 15 * np.random.randn(input.n()) | ||
| fig, ax = plt.subplots() | ||
| ax.hist(x, bins=30, density=True) | ||
| return fig |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||
| --- | ||||||
| title: Input Task Button | ||||||
|
||||||
| title: Input Task Button | |
| title: Task Button |
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.
I'd like to massage the framing here a little bit, in particular to set up the UI and server steps, which are well differentiated below.
I think it's worth mentioning in the intro that input_task_button() is a drop-in replacement for input_action_button(). If used on its own, it will update when clicking on it starts a long-running computation to let users know that something is happening on the server.
But input_task_button() is even more effective when paired with an extended task, which is something that happens on the server. Extended tasks run asynchronously and are truly non-blocking. The rest of your app can keep working while the extended task runs. An async function becomes an extended task with @reactive.extended_task decorator and it can drive the button state with @ui.bind_task_button("btn_id"). (You still have to react to the button click event, using something like @reactive.event(input.btn_id).)
(This is just a brainstorm/outline, feel free to modify/expand/etc.)
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.
I think it might be better to put this first. If I were teaching this interactively, I'd start by adding ui.input_task_button() and then adding the @reactive.event(input.btn_id) that reacts to the button click, and then finally showing the extended task.
But here, in static prose, I think that framing loses the priority of @reactive.extended_task. It'd be better to talk about setting up the extended task and then secondarily to discuss that you still need to react to the button click event.
Also make sure that you mention the @reactive.extended_task and @ui.bind_task_button() decorators -- they're definitely more important than the standard @reactive.event() and @reactive.effect decorators.
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.
This app is a good technical demo -- see, it works! for people who already deeply understand reactivity.
I don't think it's a good example for actually demonstrating the value of the extended task. Most people will want to use extended tasks to kick off a long-running computation without preventing users from interacting with the rest of the app. In other words, there are fast tasks and slow tasks.
This example suffers from two problems:
I have three recommendations: