Share param across entire Pipeline

Hello,

I’m trying to use Panel to build a Pipeline that effectively guides the user through generating a particular object. While I could just directly render the object’s param via Panel and get a bunch of widgets, in this case, I feel like it would be nicer to do it step by step.

For example, I have:

class ProjectBase(param.Parameterized):
    project_name = param.String(
        allow_None=False,
        doc="""The name of the project. This should probably closely correlate to the
        name of the paper you intent to publish afterwards, or a keyword which
        you easily understand.""",
        instantiate=False, per_instance=False,
    )
    project_base = param.String(
        default="/dev/null",
        doc="""This describes where the project is located on the machine, and
        should point to a directory which you want to use, either as a
        pathlib.Path object, or as a string which can be converted to a
        Path.""",
        instantiate=False, per_instance=False,
    )
    models = param.ListSelector(
        default=["AWIESM-2.1"],
        objects=["AWIESM-2.1", "AWIESM-2.2", "PISM-1.2"],
        doc="""A list of models you intend to use. This can either be a list of
        strings specifiying model names, or a list of Model objects. Strings of
        this list will be automatically converted into Model objects for you. """,
        instantiate=False, per_instance=False,
    )
    experiments = param.List(doc="Which experiments are in this project")
    using_internal_model_codes = param.Boolean(
        default=True,
        doc="Does this project include models from inside the project folder?",
        instantiate=False, per_instance=False,
    )
    using_internal_pool = param.Boolean(
        default=True,
        doc="Does this project use pool files in the project folder?",
        instantiate=False, per_instance=False,
    )
    using_internal_bc_dir = param.Boolean(
        default=True,
        doc="Does this project use boundary condition sets from inside the project folder?",
        instantiate=False, per_instance=False,
    )
    hpc_system = param.Selector(
        objects=["mistral.dkrz.de", "ollie.awi.de", socket.getfqdn()],
        doc="""This describes where the project is located on the machine, and
        should point to a directory which you want to use, either as a
        pathlib.Path object, or as a string which can be converted to a
        Path.""",
        instantiate=False, per_instance=False,
    )
    description = param.String(
        doc="""A long form description of what this project is about. If unset,
        defaults to an empty string, but you really should describe what you
        are trying to do with your science.""",
        instantiate=False, per_instance=False,
    )

Next, I build a few classes to set up various parts;

class ProjectWebUIConfigureMetadata(ProjectBase):
    ready = param.Boolean(default=False, precedence=-1)

    def __init__(self, **params):
        self._project_name_widget = pn.widgets.TextInput(
            name="Project Name", value="Sample"
        )
        self._project_desc_widget = pn.widgets.TextAreaInput(
            name="Description", height=400
        )
        super().__init__(**params)
        self._layout = pn.Row(self._project_name_widget, self._project_desc_widget)
        self._sync_widgets()

    @param.depends("project_name", "description", watch=True)
    def _sync_widgets(self):
        self._project_name_widget.value = self.project_name
        self._project_desc_widget.value = self.description

    @param.depends(
        "_project_name_widget.value", "_project_desc_widget.value", watch=True
    )
    def _sync_params(self):
        self.project_name = self._project_name_widget.value
        self.description = self._project_desc_widget.value

    @param.output
    def output(self):
        return (self.project_name, self.description)

    def panel(self):
        return self._layout

and

class ProjectWebUILocations(ProjectBase):
    ready = param.Boolean(default=False, precedence=-1)

    def __init__(self, **params):

        self._project_base_widget = pn.widgets.TextInput(
            name="Project Base Directory", value="/a/path/to/your/base"
        )
        self._model_codes_widget = pn.widgets.Toggle(
            name="Model Codes: In Project", value=True
        )
        self._pool_widget = pn.widgets.Toggle(name="Pool: In Project", value=True)
        self._bc_widget = pn.widgets.Toggle(
            name="Boundary Conditions: In Project", value=True
        )
        self._hpc_widget = pn.widgets.Select(
            name="HPC System",
            options=self.param.hpc_system.objects,
            value=self.hpc_system,
        )

        super().__init__(**params)

        self._layout = pn.Row(
            self._project_base_widget,
            pn.Column(self._model_codes_widget, self._pool_widget, self._bc_widget),
            self._hpc_widget,
        )
        self._sync_widgets()

    @param.depends(
        "project_base",
        "using_internal_model_codes",
        "using_internal_pool",
        "using_internal_bc_dir",
        "hpc_system",
        watch=True,
    )
    def _sync_widgets(self):
        self._project_base_widget.value = self.project_base

        self._model_codes_widget.value = self.using_internal_model_codes
        self._model_codes_widget.name = (
            "Model Codes: In Project"
            if self.using_internal_model_codes
            else "Model Codes: From Filesystem"
        )

        self._pool_widget.value = self.using_internal_pool
        self._pool_widget.name = (
            "Pool: In Project" if self.using_internal_pool else "Pool: From Filesystem"
        )

        self._bc_widget.value = self.using_internal_bc_dir
        self._bc_widget.name = (
            "Boundary Conditions: In Project"
            if self.using_internal_bc_dir
            else "Boundary Conditions: From Filesystem"
        )

        self._hpc_widget.value = self.hpc_system

    @param.depends(
        "_project_base_widget.value",
        "_model_codes_widget.value",
        "_pool_widget.value",
        "_bc_widget.value",
        "_hpc_widget.value",
        watch=True,
    )
    def _sync_params(self):
        self.project_base = self._project_base_widget.value
        self.using_internal_model_codes = self._model_codes_widget.value
        self.using_internal_pool = self._pool_widget.value
        self.using_internal_bc_dir = self._bc_widget.value
        self.hpc_system = self._hpc_widget.value

    @param.output
    def output(self):
        return (
            self.project_base,
            self.using_internal_model_codes,
            self.using_internal_pool,
            self.using_internal_bc_dir,
            self.hpc_system,
        )

    def panel(self):
        return self._layout

How do I modify the output method to just send along all the parameters to the next part of the pipeline? Is that possible?

Thanks for the help!
Paul

So, according to the docs:

Parameter inheritance like this lets you (a) define a Parameter only once, no matter how many subclasses it might be used in, and (b) control the value of that parameter conveniently across the entire set of subclasses and instances, as long as that attribute has not been set on those objects already. Using inheritance in this way is a very convenient mechanism for setting default values and other “global” parameters, whether before a program starts executing or during it.

An example here would be fantastic!