holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
410 stars 69 forks source link

Add .is_active parameter to reactive expressions #912

Closed MarcSkovMadsen closed 4 months ago

MarcSkovMadsen commented 4 months ago

I would like to start using reactive expressions as I start to realize they are a powerful extension of pn.bind.

One pattern I would often like to do is to disable components and/ or add loading indicators while a reactive expression is evaluating.

Context

I can do this manually by using another is_active reactive expression.

import panel as pn
from time import sleep
pn.extension()

is_active = pn.rx(False)

submit = pn.widgets.Button(name="Start the wind turbine", loading=is_active, disabled=is_active)

def run(clicked):
    is_active.rx.value = True
    sleep(0.5)
    print("done")
    is_active.rx.value = False

rx_run = pn.rx(run)(submit)

# Figure out how to watch

pn.Column(submit, rx_run).servable()

https://github.com/holoviz/param/assets/42288570/1754fd65-b1e3-471d-a26e-7759ee7515ab

Please note that this example also experiences the issue in https://github.com/holoviz/panel/issues/6426.

Request

Please make this pattern really simple to use by adding an .is_active parameter to reactive expressions. Something like

rx_run.rx.is_active

that I can bind to.

Bonus question:

Instead of changing the value of a reactive expression two times as in the example. Is it possible to use a context manager similar to

with some_parameterized.param.update(value=False):
   ...
philippjfr commented 4 months ago

This already exists and has to be documented, specifically it's rx_run.rx.updating()

MarcSkovMadsen commented 4 months ago

How would I combine .rx.updating() with .watch. I can't figure out how as .watch(...) just returns None. I.e. I don't get a handle on anything I can do .rx.updating() on.

import panel as pn
from time import sleep
pn.extension()

submit = pn.widgets.Button(name="Start the wind turbine")

def run(clicked):
    sleep(0.5)
    print("done")

rx_run = pn.rx(submit).rx.watch(run)
submit.loading=submit.disabled=rx_run.rx.running()

# Figure out how to watch

pn.Column(submit).servable()

image

UPDATE

After some experimentation I found I need to break it down into 3 seperate steps.

import panel as pn
from time import sleep
pn.extension()

def run(clicked):
    sleep(0.5)
    print("done")

submit = pn.widgets.Button(name="Start the wind turbine", )

rx_run = pn.rx(submit)
submit.loading=submit.disabled=rx_run.rx.updating()
rx_run.rx.watch(run)

pn.Column(submit).servable()

Hmmm. My thinking was that I would like to disable the Button while the run function is running. Instead I can disable it while its clicked.

MarcSkovMadsen commented 4 months ago

At the same time I am think whether the .rx .watch api is what you would expect. Why do you have to specify a function as argument? Why can't you just watch and execute the pipeline as in the below?

import panel as pn
from time import sleep
pn.extension()

is_active = pn.rx(False)

submit = pn.widgets.Button(name="Start the wind turbine", loading=is_active, disabled=is_active)

def run(clicked):
    is_active.rx.value = True
    sleep(0.5)
    print("done")
    is_active.rx.value = False

rx_run = pn.rx(run)(submit).rx.watch()

# Figure out how to watch

pn.Column(submit).servable()

image