from collections import OrderedDict
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, TypeVar
import click
if TYPE_CHECKING:
# Only importing these for type checking and documentation generation in order to
# speed up runtime startup.
from simdb.database.models import Simulation
else:
Config = TypeVar("Config")
def _flatten_dict(values: Dict) -> List[Tuple[str, str]]:
items = []
for k, v in values.items():
if isinstance(v, list):
for n, i in enumerate(v):
items.append((f"{k}[{n}]", i))
elif isinstance(v, dict):
for i in _flatten_dict(v):
items.append((f"{k}.{i[0]}", i[1]))
else:
items.append((k, v))
return items
def _format_meta_value(meta_value: Any, max_len: int) -> str:
"""
Format the meta value as a string, limiting list values to max_len.
"""
if isinstance(meta_value, dict) and "min" in meta_value and "max" in meta_value:
return f"[{meta_value['min']}, {meta_value['max']}]"
if isinstance(meta_value, list):
values = []
for i, v in enumerate(meta_value):
values.append(f"{v:.2f}")
if i >= max_len - 1:
values.append("...")
break
output = ", ".join(values)
return f"[{output}]"
return str(meta_value)
[docs]
def print_simulations(
simulations: List["Simulation"],
verbose: bool = False,
metadata_names: Optional[List[str]] = None,
show_uuid: bool = False,
) -> None:
"""
Print a table of simulations to the console.
By default, only the simulation alias is printed on each row. If verbose is True
then the simulation datetime and status are also printed and metadata_names allows
additional columns to be specified.
:param simulations: The simulations to print.
:param verbose: Whether to print a more verbose table.
:param metadata_names: Additional metadata fields to print as extra columns.
:param show_uuid: Whether to include UUID column.
:return: None
"""
if len(simulations) == 0:
click.echo("No simulations found")
return
lines = []
if show_uuid:
column_widths: Dict[str, int] = OrderedDict(alias=5, UUID=4)
else:
column_widths: Dict[str, int] = OrderedDict(alias=5)
if verbose:
column_widths["datetime"] = 8
column_widths["status"] = 6
for sim in simulations:
if show_uuid:
line = [sim.alias or "", str(sim.uuid)]
column_widths["alias"] = max(
column_widths["alias"], len(sim.alias) if sim.alias else 0
)
column_widths["UUID"] = max(column_widths["UUID"], len(str(sim.uuid)))
else:
line = [sim.alias or ""]
column_widths["alias"] = max(
column_widths["alias"], len(sim.alias) if sim.alias else 0
)
if verbose:
line.append(sim.datetime)
line.append(sim.status)
column_widths["datetime"] = max(
column_widths["datetime"], len(str(sim.datetime))
)
column_widths["status"] = max(column_widths["status"], len(str(sim.status)))
if metadata_names:
for name in metadata_names:
meta = sim.find_meta(name)
column_widths.setdefault(name, len(name))
if meta:
value = _format_meta_value(meta[0].value, 5)
line.append(value)
column_widths[name] = max(column_widths[name], len(value))
else:
line.append("")
if not lines:
lines.append(list(column_widths.keys()))
lines.append(line)
line_written = False
for line in lines:
for col, width in enumerate(column_widths.values()):
click.echo(f"{str(line[col]).ljust(width + 1)}", nl=False)
click.echo()
if not line_written:
click.echo("-" * (sum(column_widths.values()) + len(column_widths) - 1))
line_written = True
if (lines.__len__() - 1) == 100:
click.echo(
"\n...first 100 entries shown, use command $simdb remote [NAME] list -l 0 "
"to list all simulations.\n"
)
def _print_trace_sim(trace_data: dict, indentation: int):
spaces = " " * indentation
if "error" in trace_data:
error = trace_data["error"]
click.echo(f"{spaces}{error}")
return
uuid = trace_data["uuid"]
alias = trace_data["alias"]
status = trace_data.get("status", "unknown")
click.echo(f"{spaces}Simulation: {uuid}")
click.echo(f"{spaces} Alias: {alias}")
click.echo(f"{spaces} Status: {status}")
status_on_name = status + "_on"
if status_on_name in trace_data:
status_on = trace_data[status_on_name]
label = status_on_name.replace("_", " ").capitalize()
click.echo(f"{spaces}{label}: {status_on}")
if "replaces" in trace_data:
if "replaces_reason" in trace_data:
replaces_reason = trace_data["replaces_reason"]
click.echo(f"{spaces}Replaces: (reason: {replaces_reason})")
else:
click.echo(f"{spaces}Replaces:")
_print_trace_sim(trace_data["replaces"], indentation + 2)
[docs]
def print_trace(trace_data: dict) -> None:
"""
Print the simulation trace data to the console.
:param trace_data: A dictionary containing the simulation trace data.
:return: None
"""
if not trace_data:
click.echo("No simulations trace found")
return
_print_trace_sim(trace_data, 0)