|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -from typing import Iterable, Sequence, Any |
4 | | -from tabulate import tabulate |
| 3 | +from typing import Any |
5 | 4 |
|
6 | | - |
7 | | -def _trim_cell(x: Any, max_width: int | None) -> Any: |
8 | | - """Truncate long cell strings with … (does not wrap).""" |
9 | | - if max_width is None: |
10 | | - return x |
11 | | - s = "" if x is None else str(x) |
12 | | - if len(s) <= max_width: |
13 | | - return s |
14 | | - if max_width <= 1: |
15 | | - return "…" |
16 | | - return s[: max_width - 1] + "…" |
| 5 | +import pandas as pd |
17 | 6 |
|
18 | 7 |
|
19 | 8 | def format_table( |
20 | 9 | columns: list[str], |
21 | 10 | rows: list[tuple], |
22 | 11 | *, |
23 | | - tablefmt: str = "pretty", |
24 | 12 | max_rows: int = 20, |
25 | 13 | max_cols: int = 12, |
26 | 14 | max_cell_width: int | None = 60, |
27 | | - **tabulate_kwargs, |
28 | 15 | ) -> str: |
29 | | - """ |
30 | | - pandas-like summary output for tabulate: |
31 | | - - If too many columns: show left + '…' + right (inserts an ellipsis column) |
32 | | - - If too many rows: show head + ellipsis row + tail |
33 | | - """ |
34 | | - n_cols = len(columns) |
35 | | - if n_cols == 0: |
36 | | - return tabulate([], headers=[], tablefmt="pretty", **tabulate_kwargs) |
37 | | - |
38 | | - # ---- column trimming ---- |
39 | | - use_col_ellipsis = n_cols > max_cols and max_cols >= 2 |
40 | | - if use_col_ellipsis: |
41 | | - left = max_cols // 2 |
42 | | - right = max_cols - left |
43 | | - left_idx = list(range(left)) |
44 | | - right_idx = list(range(n_cols - right, n_cols)) |
45 | | - keep_idx = left_idx + right_idx |
46 | | - |
47 | | - out_columns = columns[:left] + ["…"] + columns[-right:] |
| 16 | + """Render a dataframe-like table using pandas output formatting.""" |
| 17 | + if not columns: |
| 18 | + return "" |
48 | 19 |
|
49 | | - out_rows = [] |
50 | | - for r in rows: |
51 | | - r_list = list(r) |
52 | | - kept = ( |
53 | | - [r_list[i] for i in keep_idx[:left]] |
54 | | - + ["…"] |
55 | | - + [r_list[i] for i in keep_idx[left:]] |
56 | | - ) |
57 | | - out_rows.append(tuple(kept)) |
58 | | - else: |
59 | | - out_columns = ( |
60 | | - columns[:max_cols] if (n_cols > max_cols and max_cols >= 1) else columns |
61 | | - ) |
62 | | - keep_idx = list(range(len(out_columns))) |
63 | | - out_rows = [tuple(r[i] for i in keep_idx) for r in rows] |
| 20 | + df = pd.DataFrame(list(rows), columns=columns) |
64 | 21 |
|
65 | | - # ---- row trimming ---- |
66 | | - n_rows = len(out_rows) |
67 | | - use_row_ellipsis = n_rows > max_rows and max_rows >= 2 |
68 | | - if use_row_ellipsis: |
69 | | - top = max_rows // 2 |
70 | | - bottom = max_rows - top |
71 | | - head = out_rows[:top] |
72 | | - tail = out_rows[-bottom:] |
73 | | - ellipsis_row = tuple("…" for _ in out_columns) |
74 | | - out_rows = head + [ellipsis_row] + tail |
75 | | - else: |
76 | | - out_rows = out_rows[:max_rows] |
| 22 | + display_max_rows = None if max_rows is None or max_rows <= 0 else max_rows |
| 23 | + display_max_cols = None if max_cols is None or max_cols <= 0 else max_cols |
77 | 24 |
|
78 | | - # ---- cell trimming ---- |
| 25 | + option_kwargs: dict[str, Any] = { |
| 26 | + "display.max_rows": display_max_rows, |
| 27 | + "display.max_columns": display_max_cols, |
| 28 | + "display.width": 0, |
| 29 | + "display.expand_frame_repr": False, |
| 30 | + } |
79 | 31 | if max_cell_width is not None: |
80 | | - out_rows = [tuple(_trim_cell(v, max_cell_width) for v in r) for r in out_rows] |
| 32 | + option_kwargs["display.max_colwidth"] = max_cell_width |
81 | 33 |
|
82 | | - return tabulate(out_rows, headers=out_columns, tablefmt=tablefmt, **tabulate_kwargs) |
| 34 | + with pd.option_context(*sum(option_kwargs.items(), ())): |
| 35 | + return df.to_string(index=False) |
0 commit comments