How are parameter updates propagated?

Hi
I’m new to Panel and trying to wrap my head around how are the different update events propagated. Here is a small snippet, where changes to one param updates another.

Expected behavior

paramsOne - selectors “a” and “b”. Changing “a” changes the options for “b”
paramsTwo - selector “c”. Changing “b” on parent paramsOne changes options for “c”

class paramsOne(param.Parameterized):
    a = param.Selector(default=8, objects=list(range(0, 20)))
    b = param.Selector(default=5, objects=list(range(5, 10)))

    @param.depends("a", watch=True)
    def _updates(self):
        print("update paramsOne")
        self.param.b.objects = list(range(5, self.a))
        
    def view(self):
        return self.param

    
class paramsTwo(param.Parameterized):
    c = param.Selector(default=0, objects=list(range(0, 2)))
    
    def __init__(self, parent):
        self.parent = parent
        super().__init__()

    @param.depends("parent.b", watch=True)
    def _updates(self):
        print("update paramsTwo")
        self.param.c.objects = list(range(0, self.parent.b))

    def view(self):
        return self.param 


x = paramsOne()
y = paramsTwo(x)
pn.Row(x.view(), y.view())

On trying to run it

> x.b = 7
update paramsTwo

> x.a = 6
update paramsOne
# this triggers a change on x.b in the visible widget (setting it to 5),  but the second update is not called.

Presumably, even if the options for widget “b” have changed and the HTML displayed, its value has not. What would the correct way to model this behavior?

One solution seems to be changing the value of parameter b everytime the options available to it are updated.

class paramsOne(param.Parameterized):
....
    @param.depends("a", watch=True)
    def _updates(self):
        print("update paramsOne")
        self.param.b.objects = list(range(5, self.a))
        self.b = 5
...

But this won’t trigged a downstream update if the previous value of b was already 5.

Yet another method could be to make the widget “c” depend on both “a” and “b”,

class paramsTwo(param.Parameterized):
...
    @param.depends("parent.a", "parent.b", watch=True)
    def _updates(self):
        print("update paramsTwo")
        self.param.c.objects = list(range(0, self.parent.b))
...

Though this’d trigger 2 updates after a change to widget “a”, in fact the first update with (new-a, old-b), which might not be a valid set of parameters at all.

The solution to this seems to be triggering the watchers manually.

class paramsOne(param.Parameterized):
....
    @param.depends("a", watch=True)
    def _updates(self):
        print("update paramsOne")
        self.param.b.objects = list(range(5, self.a))
        self.b = 5
        self.trigger("b")
....

I’m curious why this isn’t the default behavior - wouldn’t changing the available options for a widget almost always mean you should trigger the downstream widgets?

Hello,
you are passing x to the y class. Why not subclass paramsTwo with paramsOne ?

class paramsTwo(paramsOne):
   def __init__(self, **params):
         super(paramsTwo, self).__init__(**params)

Cares must be taken on when you call super(). before or after initialization.
And _updates might also be overloaded

class paramsOneV2(param.Parameterized):
    a = param.Selector(default=8, objects=list(range(0, 20)))
    b = param.Selector(default=5, objects=list(range(5, 10)))
    
    @param.depends("a","b", watch=True)
    def _updates(self):
        print("update paramsOne")
        self.param.b.objects = list(range(5, self.a))
    
    def view(self):
        return self.param

    
class paramsTwoV2(paramsOneV2):
    c = param.Selector(default=0, objects=list(range(0, 2)))
    
    def __init__(self, **params):
        super(paramsTwoV2, self).__init__(**params)

    @param.depends("a","b", watch=True)
    def _updates(self):
        super()._updates()
        print("update paramsTwo")
        self.param.c.objects = list(range(0, self.b))

    def view(self):
        return self.param 
xV2 = paramsOneV2()
yV2 = paramsTwoV2()
pn.Row(xV2.view(), yV2.view())

Thank you for looking. I really like how @Marc creates his panels using parameterized classes but I wanted to keep the logic separated into multiple components, and only connected through composition when one of them depends on the other.

I don’t see inheritance using subclasses providing any benefit over putting all the selectors together in a single class. What do you think?