Coverage for /Users/martin/prj/git/benchman_pre/src/benchman/reporter.py: 0%
74 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-12-24 08:16 +0100
« prev ^ index » next coverage.py v7.6.4, created at 2024-12-24 08:16 +0100
1"""Convert Datasets into human-readable representation."""
3from abc import ABC, abstractmethod
4from io import TextIOBase
5from pathlib import Path
7import tablib
9from benchman.dataset import Dataset
10from benchman.util import FileOrStdout, logger
13class Reporter(ABC):
14 """Abstract class for reporting Datasets."""
16 def __init__(self, dataset: Dataset):
17 self.dataset: Dataset = dataset
19 @abstractmethod
20 def report(self, *, out: Path | str | TextIOBase | None, **kwargs) -> None:
21 """Return a human-readable representation of the Dataset."""
23 def get_description_info(self) -> dict[str, str | list[str]]:
24 return self.dataset.get_description_info()
26 def _mock_class_suffix(self, classes: set[str]) -> str:
27 """Return a string representation of the classes."""
28 # return f" ({','.join(classes)})"
29 cl = []
30 for c in classes:
31 s = ""
32 if c.startswith("name-"):
33 s += "n"
34 # continue
35 elif c.startswith("variant-"):
36 s += "v"
37 continue
38 elif c.startswith("row-"):
39 s += "r"
41 if c.endswith("-best"):
42 s += "++"
43 elif c.endswith("-good"):
44 s += "+"
45 elif c.endswith("-bad"):
46 s += "-"
47 elif c.endswith("-worst"):
48 s += "--"
50 if s:
51 cl.append(s)
53 if cl:
54 return f' ({",".join(cl)})'
55 return ""
57 def to_tablib(self) -> tablib.Dataset:
58 tds = tablib.Dataset(
59 title=self.dataset.name,
60 headers=self.dataset.header_titles,
61 )
62 for row in self.dataset.rows:
63 # cells = [f"{c.value}{self._mock_class_suffix(c.classes)}" for c in row]
64 # cells = [f"{c.value} ({','.join(c.classes)})" for c in row]
65 cells = [c.value for c in row]
66 try:
67 tds.append(cells)
68 except Exception as e:
69 logger.error(f"Error {e!r} adding row: {cells}")
70 logger.error(f"{tds.headers=}")
71 raise
72 return tds
75class TablibReporter(Reporter):
76 """Text representation of a Dataset."""
78 # See https://tablib.readthedocs.io/en/stable/formats.html
79 # fmt: off
80 cli_formats = [
81 "fancy_grid", "github", "grid", "html", "jira", "latex_booktabs",
82 "latex_raw", "latex", "mediawiki", "moinmoin", "orgtbl", "pipe", "plain",
83 "presto", "psql", "rst", "simple", "textile", "tsv", "youtrack",
84 ]
85 format_map = {
86 "html": "cli.html",
87 "markdown": "cli.pipe",
88 "csv": "cli.csv",
89 "json": "cli.json",
90 "yaml": "cli.yaml",
91 "df": "cli.df",
92 }
93 # fmt: on
95 def report(
96 self,
97 *,
98 format: str = "cli.pipe",
99 out: Path | str | TextIOBase | None = None,
100 **kwargs,
101 ) -> None:
102 """Write a human-readable representation of the Dataset.
104 The heavy lifting is done by `tablib.Dataset.export()`.
105 """
106 format = self.format_map.get(format, format)
108 export, format = format.split(".", 1)
110 assert export == "cli", f"Invalid format: {format}"
111 assert format in self.cli_formats, f"Invalid cli format: {format}"
113 info = self.get_description_info()
115 tds = self.to_tablib()
117 if isinstance(out, (Path, str)):
118 logger.info(f"Writing to {Path(out).absolute()}")
120 with FileOrStdout(out) as file:
122 def wl(s: str = "") -> None:
123 file.write(f"{s}\n")
125 wl(f"\n# {info["title"]}")
127 if info["subtitle"]:
128 wl("\n> " + "\n> ".join(info["subtitle"]))
129 wl()
131 # The table itself:
132 wl(str(tds.export(export, tablefmt=format)))
134 wl()
135 for s in info["legend"]:
136 wl(s)
138 for s in info["warnings"] or []:
139 wl(s)
140 wl()