Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recursive nesting to HTML option table rendering #1586

Merged
merged 4 commits into from
Apr 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 44 additions & 14 deletions qiskit_ibm_runtime/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"<div style='width: {20*(1 + indent)}px; {spacer_style}'>{marker}</div>"

yield " <tr>"
yield f" <{tag} {style}>{spacer}{name}</{tag}>"
yield f" <{tag} {style}>{type(value).__name__ if is_section else repr(value)}</{tag}>"
yield " </tr>"


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."""
Expand Down Expand Up @@ -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 = "<table style='font-size: 14px; width: 300px;'>"
for key, value in asdict(self).items():
if isinstance(value, dict):
html_str += f"<tr><th style='text-align: left;'>{key}</th><td></td></tr>"
for subkey, subvalue in value.items():
html_str += (
f"<tr><td style='text-align: left; padding-left: 20px;'>{subkey}</td>"
f"<td>{subvalue}</td></tr>"
)
else:
html_str += f"<tr><th style='text-align: left;'>{key}</th><td>{value}</td></tr>"
return html_str + "</table>"
table_html = [f"<pre>{type(self).__name__}<{hex(id(self))}></pre>", "<table>"]
for row in _iter_all_fields(self):
table_html.extend(_make_data_row(*row))
table_html.append("</table>")
return "\n".join(table_html)


@primitive_dataclass
Expand Down