Source code for paidiverpy.frontend.json_dump

"""This module provides functions to extract values from a Panel layout and convert them into a structured JSON-like dictionary."""

import re
from typing import Any
import panel as pn


[docs] def find_deep_layout(layout: pn.widgets.Widget, founds: list[pn.widgets.Widget]) -> list[pn.widgets.Widget]: """Recursively find all widgets and layouts in a Panel layout. Args: layout (pn.widgets.Widget): The Panel layout or widget to search. founds (list[pn.widgets.Widget]): A list to collect found widgets and layouts. Returns: list[pn.widgets.Widget]: A list of found widgets and layouts. """ if isinstance(layout, pn.param.ParamFunction): inner = getattr(layout, "_inner_layout", None) layout = inner if inner is not None else layout.object() if isinstance(layout, pn.param.ParamFunction | pn.widgets.Widget): founds.append(layout) elif isinstance(layout, pn.Column | pn.Row): for child in layout: find_deep_layout(child, founds) return founds
[docs] def check_valid_inputs(widget: pn.widgets.Widget, step: bool = False) -> bool: """Check if a widget is valid for extraction. Args: widget (pn.widgets.Widget): The widget to check. step (bool): If True, additional checks for step widgets are applied. Returns: bool: True if the widget is valid, False otherwise. """ if isinstance(widget, pn.widgets.Button): return False if not hasattr(widget, "name") or not hasattr(widget, "value"): return False if hasattr(widget, "disabled") and widget.disabled: return False if "Steps" in widget.name: return False if "type_selector" in widget.name and not step: return False return "Provide" not in widget.name
[docs] def parse_name(name: str) -> list[Any]: """Parse a widget name into a list of keys. Args: name (str): The name of the widget, which may contain dots and brackets. Returns: list: A list of keys parsed from the name, converting numeric parts to integers. """ return [int(part) if part.isdigit() else part for part in re.findall(r"\w+|\[\d+\]", name.replace("[", ".").replace("]", ""))]
[docs] def insert_nested(result: dict[str, Any], keys: list[Any], value: Any) -> None: # noqa: ANN401 """Insert a value into a nested dictionary structure based on keys. Args: result (dict): The dictionary to insert into. keys (list): A list of keys indicating the path to insert the value. value (Any): The value to insert. """ current = result for i, key in enumerate(keys): if isinstance(key, int): while len(current) <= key: current.append({}) current = current[key] elif i < len(keys) - 1: if key not in current: current[key] = [] if isinstance(keys[i + 1], int) else {} current = current[key] else: current[key] = value
[docs] def extract_values(widgets: list[pn.widgets.Widget], step: bool = False) -> dict[str, Any]: """Extract values from a list of widgets and return them as a structured dictionary. Args: widgets (list[pn.widgets.Widget]): A list of Panel widgets to extract values from. step (bool): If True, the extraction will consider step-specific widgets. Returns: dict: A dictionary containing the extracted values, structured by widget names. """ result = {} list_collections = {} for widget in widgets: if not check_valid_inputs(widget, step): continue if "type_selector" in widget.name and step: step = False keys = parse_name(widget.name) value = widget.value if any(isinstance(k, int) for k in keys): list_name = keys[0] list_index = keys[1] rest_keys = keys[2:] if list_name not in list_collections: list_collections[list_name] = {} if list_index not in list_collections[list_name]: list_collections[list_name][list_index] = {} insert_nested(list_collections[list_name][list_index], rest_keys, value) else: insert_nested(result, keys, value) for list_name, items in list_collections.items(): indexed_items = [items[i] for i in sorted(items) if items[i]] result[list_name] = indexed_items return result
[docs] def extract_json(layout: pn.widgets.Widget, step: bool = False) -> dict[str, Any]: """Extract JSON-like dictionary from a Panel layout or widget. Args: layout (pn.widgets.Widget): The Panel layout or widget to extract from. step (bool): If True, the extraction will consider step-specific widgets. Returns: dict: A dictionary containing the extracted values, structured by widget names. """ inputs = [] find_deep_layout(layout, inputs) json = extract_values(inputs, step) if step: step_name = json["steps"][0]["type_selector"].replace("Config", "").lower() step_params = json["steps"][0].copy() del step_params["type_selector"] json = {step_name: step_params} return json