Odd behavior when combining pn.param.ParamMethod and pn.Tabs(dynamic=True)

Consider the following:

import time

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn
import param as pm

pn.extension()
pn.config.throttled = True


class A(pm.Parameterized):
    data = pm.DataFrame()
    x = pm.Integer(10, bounds=(1, 100))

    @pm.depends('x', watch=True, on_init=True)
    def update_data(self):
        self.data = pd.DataFrame({'x': list(range(self.x)), 'y': np.ones(self.x)})

    def view_data(self):
        time.sleep(3)
        return self.data.hvplot.line(x='x', y='y')

    def view(self):
        return pn.Tabs(
            ('Object', self),
            ('Data', pn.param.ParamMethod(self.view_data)),
            dynamic=True,
        )


a = pn.panel(A().view())

a.servable()

The program works fine if I remove either pn.param.ParamMethod or dynamic=True but when I have both then the program does not work as expected, as in, after switching between tabs, nothing is displayed. I did get an error as well which said something about circular dependency, but I’m currently unable to reproduce that error.

How can I get the benefits of dynamic tabs and also ParamMethod?
The reason for using ParamMethod is to get the loading_indicator functionality.

Thanks!

I have the same issue when using pn.Param to feed in some inputs together with pn.Tabs and dynamic=True. I cannot post the code right now, but I get the following error when I switch from tab 0 (the active one on startup) to tab 1 and back to tab 0. Tab 0 then becomes blank.

(03/15/2024 04:10:20 PM) - tornado.application - ERROR - ioloop:758 - Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x000001B760D9F690>>, <Task finished name='Task-113' coro=<ServerSession.with_document_locked() done, defined at C:\Mambaforge\envs\env\Lib\site-packages\bokeh\server\session.py:77> exception=SerializationError('circular reference')>)
Traceback (most recent call last):
  File "C:\Mambaforge\envs\env\Lib\site-packages\tornado\ioloop.py", line 738, in _run_callback
    ret = callback()
          ^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\tornado\ioloop.py", line 762, in _discard_future_result
    future.result()
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\server\session.py", line 94, in _needs_document_lock_wrapper
    result = func(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\server\session.py", line 226, in with_document_locked
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 485, in wrapper
    return invoke_with_curdoc(doc, invoke)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 443, in invoke_with_curdoc
    return f()
           ^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 484, in invoke
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 182, in remove_then_invoke
    return callback()
           ^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\panel\layout\base.py", line 114, in _update_model
    with hold(doc):
  File "C:\Mambaforge\envs\env\Lib\contextlib.py", line 144, in __exit__
    next(self.gen)
  File "C:\Mambaforge\envs\env\Lib\site-packages\panel\io\model.py", line 207, in hold
    doc.unhold()
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\document.py", line 776, in unhold
    self.callbacks.unhold()
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 431, in unhold
    self.trigger_on_change(event)
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 413, in trigger_on_change
    invoke_with_curdoc(doc, invoke_callbacks)
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 443, in invoke_with_curdoc
    return f()
           ^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 412, in invoke_callbacks
    cb(event)
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\callbacks.py", line 276, in <lambda>
    self._change_callbacks[receiver] = lambda event: event.dispatch(receiver)
                                                     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\events.py", line 353, in dispatch
    super().dispatch(receiver)
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\events.py", line 219, in dispatch
    cast(DocumentPatchedMixin, receiver)._document_patched(self)
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\server\session.py", line 252, in _document_patched
    self._pending_writes.append(connection.send_patch_document(event))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\server\connection.py", line 86, in send_patch_document
    msg = self.protocol.create('PATCH-DOC', [event])
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\protocol\__init__.py", line 131, in create
    return self._messages[msgtype].create(*args, **kwargs)  # type: ignore [attr-defined]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\protocol\messages\patch_doc.py", line 90, in create
    patch_json = PatchJson(events=serializer.encode(events))
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 276, in _encode
    return self._encode_list(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 322, in _encode_list
    return [self.encode(item) for item in obj]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 322, in <listcomp>
    return [self.encode(item) for item in obj]
            ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 260, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\document\events.py", line 369, in to_serializable
    new   = serializer.encode(self.new),
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 280, in _encode
    return self._encode_dict(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 339, in _encode_dict
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 339, in <listcomp>
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
                                ^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 276, in _encode
    return self._encode_list(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 322, in _encode_list
    return [self.encode(item) for item in obj]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 322, in <listcomp>
    return [self.encode(item) for item in obj]
            ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 260, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\model\model.py", line 525, in to_serializable
    super_rep = super().to_serializable(serializer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\has_props.py", line 406, in to_serializable
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\has_props.py", line 406, in <dictcomp>
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                       ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 280, in _encode
    return self._encode_dict(obj)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 339, in _encode_dict
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 339, in <listcomp>
    entries=[(self.encode(key), self.encode(val)) for key, val in obj.items()],
                                ^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 251, in encode
    return self._encode(obj)
           ^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 260, in _encode
    return obj.to_serializable(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\model\model.py", line 525, in to_serializable
    super_rep = super().to_serializable(serializer)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\has_props.py", line 406, in to_serializable
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\has_props.py", line 406, in <dictcomp>
    attributes = {key: serializer.encode(val) for key, val in properties.items()}
                       ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 247, in encode
    self.error("circular reference")
  File "C:\Mambaforge\envs\env\Lib\site-packages\bokeh\core\serialization.py", line 468, in error
    raise SerializationError(message)
bokeh.core.serialization.SerializationError: circular reference

I think this might have been fixed in Panel main; pip install panel==1.4.0rc2

Also, if you want, you can make it async so you can use yield to first show loading, then the desired output.

import asyncio
import time

import hvplot.pandas
import numpy as np
import pandas as pd
import panel as pn
import param as pm

pn.extension()
pn.config.throttled = True


class A(pm.Parameterized):
    data = pm.DataFrame()
    x = pm.Integer(10, bounds=(1, 100))

    @pm.depends('x', watch=True, on_init=True)
    def update_data(self):
        self.data = pd.DataFrame({'x': list(range(self.x)), 'y': np.ones(self.x)})

    async def view_data(self):
        yield "Loading..."
        await asyncio.sleep(2)
        yield self.data.hvplot.line(x='x', y='y')

    def view(self):
        return pn.Tabs(
            ('Object', self),
            ('Data', pn.param.ParamMethod(self.view_data)),
            dynamic=True,
        )


a = pn.panel(A().view())

a.servable()
2 Likes

Unfortunately the bug seems to persist, I still get the circular reference error. Please find the below code to reproduce the issue.

import logging

import panel as pn
import param


class Test(param.Parameterized):
    load_button = param.Action(label="Load..")
    extract_button = param.Action(label="Extract..")    

    def __init__(self, **params):
        super(Test, self).__init__(**params)

    def init_panel(self):
        debug_info = pn.widgets.Debugger(
            name="Log",
            level=logging.DEBUG,
            sizing_mode="stretch_both",
            logger_names=["panel.app"],
        )

        param = pn.Param(
            self.param,
            parameters=[
                "extract_button",
                "load_button",
                "progress",
            ],
            widgets={
                "extract_button": {"button_type": "primary"},
                "load_button": {"button_type": "primary"},
            },
        )

        tabs = pn.Tabs(
            ("Tab 1", param),
            ("Tab 2", debug_info),
            dynamic=True,
        )
        return pn.template.MaterialTemplate(
            title="app",
            main=[tabs],
        )


app = Test()
app.init_panel().show()
1 Like

Hi @contango

In your example the problem is not Param. Instead its the debug_info. If I replace debug_info with something else like the string "debug_info" it works.

import logging

import panel as pn
import param

pn.extension("terminal")


class Test(param.Parameterized):
    load_button = param.Action(label="Load..")
    extract_button = param.Action(label="Extract..")    

    def __init__(self, **params):
        super(Test, self).__init__(**params)

    def init_panel(self):
        debug_info = pn.widgets.Debugger(
            name="Log",
            level=logging.DEBUG,
            sizing_mode="stretch_both",
            logger_names=["panel.app"],
        )

        param = pn.Param(
            self.param,
            parameters=[
                "extract_button",
                "load_button",
                "progress",
            ],
            widgets={
                "extract_button": {"button_type": "primary"},
                "load_button": {"button_type": "primary"},
            },
        )

        tabs = pn.Tabs(
            ("Tab 1", param),
            ("Tab 2", "debug_info"),
            dynamic=True,
        )
        return pn.template.MaterialTemplate(
            title="app",
            main=[tabs],
        )


app = Test()
app.init_panel().servable()

I’ve reported the issue in #6546

1 Like