# Copyright 2021-2024 The DeepCAVE Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# noqa: D400
"""
# Dynamic
This module provides a plugin class for a dynamic plugin.
Register and handle callbacks.
## Classes
- DynamicPlugin: This class provides a dynamic plugin object.
"""
from abc import ABC
from typing import Any, List
from dash.dependencies import Input, Output
from dash.development.base_component import Component
from deepcave import interactive
from deepcave.plugins import Plugin
[docs]
class DynamicPlugin(Plugin, ABC):
"""
Provide a dynamic plugin object.
Register and handle callbacks.
Properties
----------
outputs : List[Tuple[str, str]]
The registered outputs.
inputs : List[Tuple[str, str, bool, Any]]
The registered inputs.
id : str
The identifier of the Plugin.
logger : Logger
The logger for debugging information.
use_cache : bool
Defines whether to use the cache.
raw_outputs : Dict[str, Any]
A dictionary of the raw outputs.
"""
use_cache = True
def __init__(self) -> None:
super().__init__()
[docs]
@interactive
def register_callbacks(self) -> None:
"""
Register basic callbacks for the plugin.
Following callbacks are registered:
- If inputs changes, the changes are pasted back.
This is in particular interest if input dependencies are used.
- Raw data dialog to display raw data.
- Callback to be redirected to the config if clicked on it.
"""
super().register_callbacks()
from deepcave import app, c, rc
outputs = []
for id, attribute in self.outputs:
outputs.append(Output(self.get_internal_output_id(id), attribute))
inputs = [Input(self.get_internal_id("update-button"), "n_clicks")]
for id, attribute, _, _ in self.inputs:
inputs.append(Input(self.get_internal_input_id(id), attribute))
# Register updates from inputs
@app.callback(outputs, inputs) # type: ignore
def plugin_output_update(_: Any, *inputs_list: str) -> Any:
"""
Update the outputs.
Parameters
----------
*inputs_list
Input values from user.
Returns
-------
Any
The raw outputs.
"""
# Map the list `inputs_list` to a dict s.t.
# it's easier to access them.
inputs = self._list_to_dict(list(inputs_list), input=True)
# The Optional[str] would need tobe changed to a str as return type annotation
# of the function _dict_as_key.
inputs_key = self._dict_as_key(inputs, remove_filters=True)
cleaned_inputs = self._clean_inputs(inputs)
runs = self.get_selected_runs(inputs)
raw_outputs = {}
rc.clear()
for run in runs:
run_outputs = rc.get(run, self.id, inputs_key)
if run_outputs is None:
self.logger.debug(f"Process {run.name}.")
run_outputs = self.process(run, cleaned_inputs)
# Cache it
if self.use_cache:
rc.set(run, self.id, inputs_key, value=run_outputs)
else:
self.logger.debug(f"Found outputs from {run.name} in cache.")
raw_outputs[run.id] = run_outputs
# Save for modal
self.raw_outputs = raw_outputs
# Cache last inputs
c.set("last_inputs", self.id, value=inputs)
return self._process_raw_outputs(inputs, raw_outputs)
[docs]
@interactive
def __call__(self) -> List[Component]: # type: ignore
"""
Return the components for the plugin.
Basically, all blocks and elements of the plugin are stacked-up here.
Returns
-------
List[Component]
Layout as list of components.
"""
return super().__call__(False)