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