How do I write code that allows different session to have different states?
Currently, all my sessions are sychronized / share their states without explicitly coding it that way.
A minimum example:
import panel as pn
pn.extension()
dashboard = pn.Column('# Dashboard',
pn.Tabs(pn.Spacer(), pn.Spacer(), pn.Spacer())
)
server = pn.serve(dashboard, start=True, show=True)
If I now open multiple sessions, even across clients (so different IPs), once I click a tab in one session, all other sessions switch the tab as well.
How do I get to a situation where sessions behave independently?
Adding a subtle point to this topic that I learned the hard way. Regardless of servable(), if using parameterized classes as parameters, you should initialize them in the init and not in the parameter declaration if you want them to be session-independent.
import param
import panel as pn
class SomeParameter(param.Parameterized):
data = param.DataFrame()
class SomeClass1(param.Parameterized):
# Initializing SomeParameter at class level will make param1 shared across all sessions
param1 = param.Parameter(SomeParameter())
class SomeClass2(param.Parameterized):
param1 = param.Parameter()
def __init__(self, **params):
# This will make param1 session independent
param1 = SomeParameter()
super().__init__(**params)
Adding a subtle point to this topic that I learned the hard way. Regardless of servable() , if using parameterized classes as parameters, you should initialize them in the init and not in the parameter declaration if you want them to be session-independent.
@maximlt : I can replicate it with the following code:
import panel as pn
import param
from panel.viewable import ServableMixin
class TestViewerApp(param.Parameterized):
view: pn.template.BaseTemplate
# Parameters
input_widget = pn.widgets.TextInput(
name="Input",
placeholder="Enter Some text ...",
)
output_widget = pn.widgets.StaticText(name="Output", value="")
def __init__(
self,
**params,
):
super().__init__(**params)
self._init_view()
def _init_view(self) -> None:
self.view = pn.template.BootstrapTemplate()
self.view.main.append(self.input_widget)
self.view.main.append(self.output_widget)
@param.depends("input_widget.value", watch=True)
def _update_output(self) -> None:
inpt = self.input_widget.value
if inpt is not None:
self.output_widget.value = f"Input: {inpt!r}"
def session_test_app() -> ServableMixin:
"""A simple test app to check if the platform client is running."""
return TestViewerApp().view
print(f"test_panel.py {__name__=!r}")
if __name__ == "__main__":
# Run with `python test_panel.py`
pn.serve(
{"session_test": session_test_app},
)
else:
# Run with `panel serve test_panel.py`
session_test_app().servable()
Running this with panel serve test_panel.py doesn’t result in shared state.
Running this with python test_panel.py results in application with shared state (see video):
Traceback (most recent call last):
File "test_panel.py", line 6, in <module>
class TestViewerApp(param.Parameterized):
File "./env/lib/python3.11/site-packages/param/parameterized.py", line 3313, in __init__
deps, dynamic_deps = _params_depended_on(minfo, dynamic=False)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "./env/lib/python3.11/site-packages/param/parameterized.py", line 681, in _params_depended_on
ddeps, ddynamic_deps = (minfo.cls if minfo.inst is None else minfo.inst).param._spec_to_obj(d, dynamic, intermediate)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "./env/lib/python3.11/site-packages/param/parameterized.py", line 2860, in _spec_to_obj
raise AttributeError(f"Attribute {attr!r} could not be resolved on {src}.")
AttributeError: Attribute 'intermediary' could not be resolved on <class '__main__.TestViewerApp'>.
The problem in your previous post is that you depend on "intermediary". And that is not a parameter on the class. You define self.intermediary = param.String(default="") in __init__ but that is not an expected/ a supported way of using param.
You should refactor your app taking into account some principles
Define param Parameters on your Parameterized class not in the __init__ method.
Define your state in your parameters. Use specific widgets (.from_param) to get widgets that can display and change the parameters.
Keep your widgets, templates etc. private with _ unless you specifically want to expose them to other users.