Hi,
I'm currently creating an GUI for a device using shiny. The main management object starts it's own runloop using asyncio, basically knowing nothing of shiny. There are multiple hardware devices communicated with (serial and pyVISA). The GUI will register shiny.reactive.Value()
s to objects of interesset, so that the objects can signal, there's a change that should be reflected in the GUI.
Basically that's working well but there's one point that I do not get: for this to work I need to have at least one @shiny.reactive.poll
somewhere! If I do not have one, the first refresh works as expected, but later updates just hang (I see the reload symbol in the GUI, so the message, that there's an update ongoing is created).
And the GUI is only refreshed in the interval defined by the @poll
.
Currently I just added this to my main server.py:
import shiny
import shiny.reactive
from ui.elements import obj_a, objc_b, setup, system
from ui.root import DEVS
@shiny.reactive.poll(lambda: None, interval_secs=1)
def _() -> None:
return None
def server(input: shiny.Inputs, output: shiny.Outputs, session: shiny.Session):
for DEV in DEVS:
obj_b.server(DEV)
obj_a.server('obj_a_1')
obj_a.server('obj_a_2')
system.server('statusbar')
setup.server('setup')
system/server.py
is looking like this: (I think you get the idea, what SystemInfo
is doing . ReactiveUpdatable
is the interface to register a shiny.reactive.Value()
in object SystemInfo()
(which is a singleton). The object itself just has a generic attribute of Updatable
and thus doesn't know, that it sets a reactive Value.)
import shiny.module
import shiny.reactive
import shiny.render
import shiny.session
from psutil._common import bytes2human
from systeminfo import SystemInfo
from ui.utils.updatable import ReactiveUpdatable
@shiny.module.server
def server(
input: shiny.session.Inputs,
output: shiny.session.Outputs,
session: shiny.session.Session,
):
system_info = SystemInfo()
# fetch updatable, just for convenience
updatable: ReactiveUpdatable = system_info.updatable
# register reactive value for updates
updater = updatable.register_session_value(session)
@shiny.render.text
@shiny.reactive.event(updater)
def clock():
return system_info.clock.strftime('%Y-%m-%d %H:%M')
@shiny.render.text
@shiny.reactive.event(updater)
async def cpu():
_percent, _load, _temperature = system_info.cpu
return f'{_percent:.1f}%/{_load:.2f}/{_temperature}'
@shiny.render.text
@shiny.reactive.event(updater)
def memory():
_, _used, _total = system_info.memory
return f'{bytes2human(_used)}/{bytes2human(_total)}'
@shiny.render.text
@shiny.reactive.event(updater)
def disk():
_, _used, _total = system_info.disk
return f'{bytes2human(_used)}/{bytes2human(_total)}'
@shiny.render.text
@shiny.reactive.event(updater)
def process():
_cpu, _rss, _ = system_info.process_info
return f'{_cpu:.1f}%/{bytes2human(_rss)}'
Interesting to note is, that it doesn't matter where I define the @poll
and it doesn't need to do anything.
I know that I just could use poller to ask the objects for changes maybe with a special attribute that is queried with low costs, but I think this way around it's much more elegant and an object could even send an update faster if neccessary. Now updates in the GUI are only limited by the @poll
intervall
I think I might have a fundamental problem. What am I doing wrong?
Some system information:
- python 3.12
- shiny 1.2.1
Thanks for any sugesstions
Carsten