Pane not updating. Problem with @depends decorator?

I’m experimenting with the capabilities of parametrized classes and rendered outputs.
In this case, my pane does not dynamically update when a parameter is changed, although the @depends decorator seems to be appropriately created.

The following code is meant to create 3 elements: a submit button that sends the value from prompt_input to prompt_input_dict and provides a corresponding UUID, a markdown pane displaying the prompt_input value, and a markdown pane of the latest inserted prompt_input into prompt_input_dict.

class ExpEnv(param.Parameterized):
  prompt_input = param.String()
  prompt_input_dict = param.Dict(default=OrderedDict())
  prompt_input_submit = param.Action(lambda self: self.param.trigger('prompt_input_submit'))

  @param.depends('prompt_input_submit', watch=True)
  def _input_dict_update(self):
    self.prompt_input_dict.update({uuid4(): self.prompt_input})

  @param.depends('prompt_input_dict')
  def view_prompts(self):
    md_list = [pn.pane.Markdown(f"K: V")]
    for k,v in self.prompt_input_dict.items():
      print('entered view_prompts loop')
      md_list.append(pn.pane.Markdown(f"{k} :{v}"))
    self.md_list = md_list
    # return pn.layout.Column(*md_list)
    return md_list[-1]

ee = ExpEnv()

When I programatically set ee.prompt_input = 'test1', this is the outcome for

pn.Row(ee.param.prompt_input_submit, pn.pane.Markdown(ee.param.prompt_input), ee.view_prompts)

:
image

test1 successfully dynamically populates the markdown in the middle when ee.prompt_input is set.
However, the third element is not updated after hitting the ‘Prompt input submit’ button, as the view_prompts method is not called although prompt_input_dict changes and now includes OrderedDict([(UUID('199cd339-4704-45a8-ab73-619587493e75'), 'test1')])

It is only when I manually rerun pn.Row(ee.param.prompt_input_submit, pn.pane.Markdown(ee.param.prompt_input), ee.view_prompts) that the uuid and input are displayed(and ‘entered view_prompts loop’ is printed):
image

I’m not sure what the issue here is or where my misunderstanding is coming from.

Thank you! Hope everyone is enjoying a wonderful New Year.

Figured it out!
I dug through the Param docs a bit more and found that the modification of mutable classes does not trigger the method call. https://param.holoviz.org/user_guide/Dependencies_and_Watchers.html#param-trigger

As a workaround, I’m attaching the trigger to the prompt_input_submit parameter.

  prompt_input_submit = param.Action(lambda self: self.param.trigger('prompt_input_submit', 'prompt_input_dict'))

However, I’m not certain if this is the optimal way to proceed.

New code for the class:

class ExpEnv(param.Parameterized):
  prompt_input = param.String()
  prompt_input_dict = param.Dict(default=OrderedDict())
  prompt_input_submit = param.Action(lambda self: self.param.trigger('prompt_input_submit', 'prompt_input_dict'))

  @param.depends('prompt_input_submit', watch=True)
  def _input_dict_update(self):
    self.prompt_input_dict.update({uuid4(): self.prompt_input})

  @param.depends('prompt_input_dict')
  def view_prompts(self):
    md_list = [pn.pane.Markdown(f"K: V")]
    for k,v in self.prompt_input_dict.items():
      print('entered view_prompts loop')
      md_list.append(pn.pane.Markdown(f"{k} :{v}"))
    self.md_list = md_list
    return pn.layout.Column(*md_list)
    # return md_list[-1]

Hi @dleybel, here’s an alternative implementation using param.Event instead of param.Action to declare the event/button.

Indeed Param has no way to know when a mutable container has been updated in place. Instead I’m manually triggering callbacks depending on prompt_input_dict by calling self.param.trigger('prompt_input_dict') right after updating it.

from uuid import uuid4
import param
import panel as pn

pn.extension()

class ExpEnv(param.Parameterized):
  prompt_input = param.String()
  prompt_input_dict = param.Dict(default=dict())
  prompt_input_submit = param.Event()

  @param.depends('prompt_input_submit', watch=True)
  def _input_dict_update(self):
    self.prompt_input_dict.update({uuid4(): self.prompt_input})
    self.param.trigger('prompt_input_dict')

  @param.depends('prompt_input_dict')
  def view_prompts(self):
    md_list = [pn.pane.Markdown(f"K: V")]
    for k,v in self.prompt_input_dict.items():
      print('entered view_prompts loop')
      md_list.append(pn.pane.Markdown(f"{k} :{v}"))
    self.md_list = md_list
    return pn.layout.Column(*md_list)
    # return md_list[-1]

ee = ExpEnv()
pn.Row(ee.param, ee.view_prompts)

image

1 Like

Thanks, @maximlt, I’ve modified my code to use that pattern instead. That’s definitely a cleaner way of going about triggering the update and makes the code easier to read and follow.

It seems that Event is the proper parameter to use, given that all it does is trigger itself.
Although, I’m a little confused as to why param.Action doesn’t trigger itself when combined with some arbitrary function like prompt_input_submit = param.Action(lambda x: True).

1 Like