Coverage for databooks/tui.py: 93%
45 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-20 15:04 +0200
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-20 15:04 +0200
1"""Terminal user interface (TUI) helper functions and components."""
3from contextlib import AbstractContextManager, nullcontext
4from dataclasses import asdict
5from enum import Enum
6from io import StringIO
7from pathlib import Path
8from typing import Any, Dict, List, Optional, Union, overload
10from rich.columns import Columns
11from rich.console import Console
12from rich.rule import Rule
13from rich.theme import Theme
15from databooks.data_models.notebook import JupyterNotebook, NotebookMetadata
16from databooks.git_utils import DiffContents
18DATABOOKS_TUI = Theme({"in_count": "blue", "out_count": "orange3", "kernel": "bold"})
20ImgFmt = Enum("ImgFmt", {"html": "HTML", "svg": "SVG", "text": "TXT"})
23def nb2rich(
24 path: Path,
25 console: Console = Console(),
26) -> None:
27 """Show rich representation of notebook in terminal."""
28 notebook = JupyterNotebook.parse_file(path)
29 console.print(Rule(path.resolve().name), notebook)
32@overload
33def nbs2rich(
34 paths: List[Path],
35 *,
36 context: ImgFmt,
37 **kwargs: Any,
38) -> str: ...
41@overload
42def nbs2rich(
43 paths: List[Path],
44 *,
45 context: bool,
46 **kwargs: Any,
47) -> None: ...
50def nbs2rich(
51 paths: List[Path],
52 *,
53 context: Union[ImgFmt, bool] = False,
54 export_kwargs: Optional[Dict[str, Any]] = None,
55 **console_kwargs: Any,
56) -> Optional[str]:
57 """
58 Show rich representation of notebooks in terminal.
60 :param paths: notebook paths to print
61 :param context: specify context - `ImgFmt` to export outputs, `True` for `pager`
62 :param export_kwargs: keyword arguments for exporting prints (as a dictionary)
63 :param console_kwargs: keyword arguments to be passed to `Console`
64 :return: console output if `context` is `ImgFmt`, else `None`
65 """
66 if "record" in console_kwargs:
67 raise ValueError(
68 "Specify `record` parameter of console via `context` argument."
69 )
70 theme = console_kwargs.pop("theme", DATABOOKS_TUI)
71 if isinstance(context, ImgFmt):
72 console_kwargs["file"] = StringIO()
73 console = Console(record=isinstance(context, ImgFmt), theme=theme, **console_kwargs)
74 ctx_map: Dict[Union[ImgFmt, bool], AbstractContextManager] = {
75 True: console.pager(styles=True),
76 False: nullcontext(),
77 }
78 with ctx_map.get(context, nullcontext()):
79 for path in paths:
80 nb2rich(path, console=console)
81 if isinstance(context, ImgFmt):
82 return getattr(console, f"export_{context.name}")(**(export_kwargs or {}))
85def diff2rich(
86 diff: DiffContents,
87 *,
88 console: Console = Console(),
89) -> None:
90 """Show rich representation of notebook diff in terminal."""
91 a_nb, b_nb = (
92 (
93 JupyterNotebook.model_validate_json(c)
94 if c is not None
95 else JupyterNotebook(
96 nbformat=0, nbformat_minor=0, metadata=NotebookMetadata(), cells=[]
97 )
98 )
99 for c in (diff.a.contents, diff.b.contents)
100 )
101 cols = Columns(
102 [
103 Rule(
104 f"{ab}/{c['path'].resolve().name if c['path'] is not None else 'null'}"
105 )
106 for ab, c in asdict(diff).items()
107 if ab in ("a", "b")
108 ],
109 width=console.width // 2,
110 padding=(0, 0),
111 )
112 console.print(cols, a_nb - b_nb)
115@overload
116def diffs2rich(
117 diffs: List[DiffContents],
118 *,
119 context: ImgFmt,
120 **kwargs: Any,
121) -> str: ...
124@overload
125def diffs2rich(
126 diffs: List[DiffContents],
127 *,
128 context: bool,
129 **kwargs: Any,
130) -> None: ...
133def diffs2rich(
134 diffs: List[DiffContents],
135 *,
136 context: Union[ImgFmt, bool] = False,
137 export_kwargs: Optional[Dict[str, Any]] = None,
138 **console_kwargs: Any,
139) -> Optional[str]:
140 """
141 Show rich representation of notebook diff in terminal.
143 :param diffs: `databooks.git_utils.DiffContents` for rendering
144 :param context: specify context - `ImgFmt` to export outputs, `True` for `pager`
145 :param export_kwargs: keyword arguments for exporting prints (as a dictionary)
146 :param console_kwargs: keyword arguments to be passed to `Console`
147 :return: console output if `context` is `ImgFmt`, else `None`
148 """
149 theme = console_kwargs.pop("theme", DATABOOKS_TUI)
150 if isinstance(context, ImgFmt):
151 console_kwargs["file"] = StringIO()
152 console = Console(record=isinstance(context, ImgFmt), theme=theme, **console_kwargs)
153 ctx_map: Dict[Union[ImgFmt, bool], AbstractContextManager] = {
154 True: console.pager(styles=True),
155 False: nullcontext(),
156 }
157 with ctx_map.get(context, nullcontext()):
158 for diff in diffs:
159 diff2rich(diff, console=console)
160 if isinstance(context, ImgFmt):
161 return getattr(console, f"export_{context.name}")(**(export_kwargs or {}))