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}{tag}>"
+ yield f" <{tag} {style}>{type(value).__name__ if is_section else repr(value)}{tag}>"
+ 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"{key} | |
"
- for subkey, subvalue in value.items():
- html_str += (
- f"{subkey} | "
- f"{subvalue} |
"
- )
- else:
- html_str += f"{key} | {value} |
"
- return html_str + "
"
+ 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