I have a dashboard that gets node count, etc. from slurm and displays that as a button grid, and is refreshed every n seconds. I want the ability to click on any of the buttons and have something happen, e.g. get additional info, run a script, etc.
The problem is that interactivity with the buttons in the grid does not work in a while loop.
In the following test code, if event_loop = False
, grid button clicks work as expected, but there is no update (no loop).
If event_loop = True
, the grid is updated, but the grid buttons no longer work.
from ipywidgets import GridspecLayout, Button, Layout, Output
from IPython.display import display, clear_output
import time
nodes = [1, 2, 3, 4, 5, 6]
rows = 3
cols = 3
event_loop = False
button_go = Button(
description='Go',
disabled=False,
ayout={'width': '150px'}
)
output_command = Output()
output_command.append_stdout('')
@output_command.capture()
def button_go_clicked(event):
print(event)
if event_loop:
while event_loop:
update_grid(rows, cols, nodes)
time.sleep(10)
else:
update_grid(rows, cols, nodes)
button_go.on_click(button_go_clicked)
def create_dynamic_grid(rows, cols, nodes):
grid = GridspecLayout(rows, cols)
node = 0
for i in range(rows):
for j in range(cols):
if node < len(nodes):
if nodes[node]:
button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto'))
grid[i, j] = button_grid
grid[i, j].on_click(button_grid_clicked)
node += 1
return grid
output_grid = Output()
def update_grid(rows, cols, nodes):
with output_grid:
output_grid.clear_output()
new_grid = create_dynamic_grid(rows, cols, nodes)
display(new_grid)
@output_command.capture()
def button_grid_clicked(btn):
print(btn)
display(button_go,
output_grid,
output_command)
I need some way to 1. immediately interrupt/pause the loop when clicking a grid button, and 2. maintain/resume the loop refresh time of n seconds.
After searching around on google, I found two interesting methods and integrated them. The first is from 'Kill a loop with Button Jupyter Notebook?'
The second was from a google Generative AI from the prompt 'python immediately break from sleep', which generated the following:
import time
import threading
def sleeper(seconds, event):
while seconds > 0 and not event.is_set():
time.sleep(min(seconds, 1))
seconds -= 1
if event.is_set():
print("Sleep interrupted")
else:
print("Slept for", seconds, "seconds")
def main():
sleep_event = threading.Event()
sleep_thread = threading.Thread(target=sleeper, args=(5, sleep_event))
sleep_thread.start()
input("Press Enter to interrupt sleep...\n")
sleep_event.set()
sleep_thread.join()
print("Continuing main thread")
if __name__ == "__main__":
main()
I have attempted to integrate these into my test code, my apologies if it's a bit of a jumble. The results are that when event_loop = True
the grid is updated constantly, and if event_loop = False
, no update.
from ipywidgets import GridspecLayout, Button, Output, Layout
from IPython.display import display, clear_output
import asyncio, threading, time
nodes = [1, 2, 3, 4, 5, 6]
rows = 3
cols = 3
event_loop = False
sleep_event = None
sleep_thread = None
button_go = Button(
description='Go',
disabled=False,
layout={'width': '150px'}
)
output_command = Output()
output_command.append_stdout('')
async def function():
while event_loop:
print('function() event_loop: ', event_loop)
print('done')
def sleeper(seconds, event):
while seconds > 0 and not event.is_set():
print('sleeping: ', event)
print('sleep_time: ', seconds)
time.sleep(seconds)
if event.is_set():
print('sleep interrupted: ', event)
def set_sleeper(sleep_time):
global sleep_event
global sleep_thread
sleep_event = threading.Event()
sleep_thread = threading.Thread(target=sleeper, args=(sleep_time, sleep_event))
sleep_thread.start()
return sleep_event
@output_command.capture()
def button_go_clicked(event):
loop = asyncio.get_event_loop()
loop.create_task(function())
print('button_go_clicked event_loop: ', event_loop)
se = set_sleeper(10)
print('set_sleeper: ', se)
if event_loop:
while event_loop:
update_grid(rows, cols, nodes) # grid updates constantly in free run
# time.sleep(10) # <-- stop free run, but looking for the correct way to sleep here AND be able to interrupt on_click
else:
update_grid(rows, cols, nodes)
button_go.on_click(button_go_clicked)
def create_dynamic_grid(rows, cols, nodes):
grid = GridspecLayout(rows, cols)
node = 0
for i in range(rows):
for j in range(cols):
if node < len(nodes):
if nodes[node]:
button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto'))
grid[i, j] = button_grid
grid[i, j].on_click(button_grid_clicked)
node += 1
return grid
output_grid = Output()
def update_grid(rows, cols, nodes):
with output_grid:
output_grid.clear_output()
new_grid = create_dynamic_grid(rows, cols, nodes)
display(new_grid)
@output_command.capture()
def button_grid_clicked(btn):
print(btn)
if event_loop:
global sleep_event
global sleep_thread
print('interrupting sleep...\n')
sleep_event.set()
sleep_thread.join()
print('continuing thread')
display(button_go,
output_grid,
output_command)
My anticipation was that set_sleeper(10)
would act as the refresh time (10 seconds).
Any suggestions on how I can make this work as desired?
Regards