Need to call one @poll to get GUI updates of reactive.Value

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 :wink: . 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