diff --git a/qiskit_ibm_runtime/options/options.py b/qiskit_ibm_runtime/options/options.py index daf24a326..87f7094f1 100644 --- a/qiskit_ibm_runtime/options/options.py +++ b/qiskit_ibm_runtime/options/options.py @@ -13,8 +13,8 @@ """Primitive options.""" from abc import abstractmethod -from typing import Optional, Union, ClassVar, Any -from dataclasses import dataclass, fields, field, asdict +from typing import Iterable, Optional, Tuple, Union, ClassVar, Any +from dataclasses import dataclass, fields, field, asdict, is_dataclass import copy import warnings @@ -39,6 +39,43 @@ from ..runtime_options import RuntimeOptions +def _make_data_row(indent: int, name: str, value: Any, is_section: bool) -> Iterable[str]: + """Yield HTML table rows to format an options entry.""" + tag = "th" if is_section else "td" + + weight = " font-weight: bold;" if is_section else "" + style = f"style='text-align: left; vertical-align: top;{weight}'" + + marker = "▸" if is_section else "" + spacer_style = "display: inline-block; text-align: right; margin-right: 10px;" + spacer = f"
{marker}
" + + yield " " + yield f" <{tag} {style}>{spacer}{name}" + yield f" <{tag} {style}>{type(value).__name__ if is_section else repr(value)}" + yield " " + + +def _iter_all_fields( + data_cls: Any, indent: int = 0, dict_form: Union[dict, None] = None +) -> Iterable[Tuple[int, str, Any, bool]]: + """Recursively iterate over a dataclass, yielding (indent, name, value, is_dataclass) fields.""" + # we pass dict_form through recursion simply to avoid calling asdict() more than once + dict_form = dict_form or asdict(data_cls) + + suboptions = [] + for name, val in dict_form.items(): + if is_dataclass(subopt := getattr(data_cls, name)): + suboptions.append((name, subopt)) + elif name != "_VERSION": + yield (indent, name, val, False) + + # put all of the nested options at the bottom + for name, subopt in suboptions: + yield (indent, name, subopt, True) + yield from _iter_all_fields(subopt, indent + 1, dict_form[name]) + + @dataclass class BaseOptions: """Base options class.""" @@ -74,18 +111,11 @@ def _get_runtime_options(options: dict) -> dict: def _repr_html_(self) -> str: """Return a string that formats this instance as an HTML table.""" - html_str = "" - for key, value in asdict(self).items(): - if isinstance(value, dict): - html_str += f"" - for subkey, subvalue in value.items(): - html_str += ( - f"" - f"" - ) - else: - html_str += f"" - return html_str + "
{key}
{subkey}{subvalue}
{key}{value}
" + table_html = [f"
{type(self).__name__}<{hex(id(self))}>
", ""] + for row in _iter_all_fields(self): + table_html.extend(_make_data_row(*row)) + table_html.append("
") + return "\n".join(table_html) @primitive_dataclass