arrscope 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {arrscope-0.1.0 → arrscope-0.2.0}/PKG-INFO +1 -1
  2. {arrscope-0.1.0 → arrscope-0.2.0}/main.py +24 -0
  3. {arrscope-0.1.0 → arrscope-0.2.0}/pyproject.toml +1 -1
  4. arrscope-0.2.0/src/arrscope/_api.py +207 -0
  5. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/_config.py +2 -1
  6. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/adapters/_core.py +4 -1
  7. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/renderers/html.py +47 -2
  8. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/renderers/terminal.py +40 -2
  9. arrscope-0.1.0/src/arrscope/_api.py +0 -131
  10. {arrscope-0.1.0 → arrscope-0.2.0}/.gitignore +0 -0
  11. {arrscope-0.1.0 → arrscope-0.2.0}/README.md +0 -0
  12. {arrscope-0.1.0 → arrscope-0.2.0}/docs/ADR-001-visual-grammar.md +0 -0
  13. {arrscope-0.1.0 → arrscope-0.2.0}/docs/ADR-002-architecture.md +0 -0
  14. {arrscope-0.1.0 → arrscope-0.2.0}/docs/ADR-003-api-design.md +0 -0
  15. {arrscope-0.1.0 → arrscope-0.2.0}/docs/GLOSSARY.md +0 -0
  16. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/__init__.py +0 -0
  17. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/__main__.py +0 -0
  18. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/_format.py +0 -0
  19. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/_layout.py +0 -0
  20. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/_types.py +0 -0
  21. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/adapters/__init__.py +0 -0
  22. {arrscope-0.1.0 → arrscope-0.2.0}/src/arrscope/renderers/__init__.py +0 -0
  23. {arrscope-0.1.0 → arrscope-0.2.0}/test.ipynb +0 -0
  24. {arrscope-0.1.0 → arrscope-0.2.0}/tests/test_adapter.py +0 -0
  25. {arrscope-0.1.0 → arrscope-0.2.0}/tests/test_format.py +0 -0
  26. {arrscope-0.1.0 → arrscope-0.2.0}/tests/test_layout.py +0 -0
  27. {arrscope-0.1.0 → arrscope-0.2.0}/tests/test_stats.py +0 -0
  28. {arrscope-0.1.0 → arrscope-0.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrscope
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Beautiful n-dimensional array visualization for Python
5
5
  Project-URL: Homepage, https://github.com/vizarray/arrscope
6
6
  Project-URL: Repository, https://github.com/vizarray/arrscope
@@ -65,4 +65,28 @@ print(scope(np.random.rand(2, 3, 4), mode="heatmap"))
65
65
  print("\n1️⃣3️⃣ STATS OFF")
66
66
  print(scope(np.eye(5), stats=False))
67
67
 
68
+ print("\n1️⃣4️⃣ MOSAIC — 3×4×5 tiled (no tree)")
69
+ arr = np.arange(60).reshape(3, 4, 5).astype(float)
70
+ print(scope(arr, render_style="mosaic"))
71
+
72
+ print("\n1️⃣5️⃣ MOSAIC — 4D with named axes tiled side by side")
73
+ print(
74
+ scope(
75
+ np.random.rand(2, 3, 6, 6),
76
+ axes=["batch", "heads", "h", "w"],
77
+ grid=["h", "w"],
78
+ title="Mosaic: 2×3 attention heads",
79
+ render_style="mosaic",
80
+ )
81
+ )
82
+
83
+ print("\n1️⃣6️⃣ MOSAIC + HEATMAP — tiled grids with diverging colors")
84
+ print(
85
+ scope(
86
+ np.random.rand(2, 4, 5, 5),
87
+ render_style="mosaic",
88
+ mode="heatmap",
89
+ )
90
+ )
91
+
68
92
  print("\n Done!")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrscope"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Beautiful n-dimensional array visualization for Python"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1,207 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from arrscope._config import config
6
+ from arrscope._format import format_value
7
+ from arrscope._layout import build_layout, compute_stats
8
+ from arrscope._types import VizOutput
9
+ from arrscope.adapters import to_numpy
10
+ from arrscope.renderers import render_html, render_terminal
11
+
12
+ Mode = Literal["dtype", "heatmap", "sparsity"]
13
+ RenderStyle = Literal["tree", "mosaic"]
14
+
15
+
16
+ def scope(
17
+ arr,
18
+ *,
19
+ axes: list[str] | None = None,
20
+ grid: list[str | int] | None = None,
21
+ title: str | None = None,
22
+ max_width: int | None = None,
23
+ max_height: int | None = None,
24
+ fmt: str | None = None,
25
+ color: bool | None = None,
26
+ style: Literal["auto", "terminal", "html"] = "auto",
27
+ mode: Mode | None = None,
28
+ render_style: RenderStyle | None = None,
29
+ stats: bool | None = None,
30
+ ) -> VizOutput:
31
+ """Visualize an n-dimensional array in the terminal and/or Jupyter.
32
+
33
+ Renders a structural tree view with 2D value grids at the leaves.
34
+ Works with NumPy, PyTorch, TensorFlow, JAX, and tinygrad arrays.
35
+
36
+ Args:
37
+ arr: The array to visualize. Accepts np.ndarray, torch.Tensor,
38
+ tf.Tensor, jax.Array, tinygrad.Tensor, or any object
39
+ implementing ``__array__``.
40
+ axes: Names for each dimension, e.g. ``["batch", "heads", "h", "w"]``.
41
+ Used in headers and for ``grid`` axis selection by name.
42
+ grid: Which dimensions form the leaf 2D grid. Given as axis indices
43
+ (``[0, 1]``), axis names (``["h", "w"]``), or omitted to
44
+ default to the last two dimensions.
45
+ title: Optional heading displayed above the visualization.
46
+ max_height: Max tree rows before truncation. The first half and
47
+ last half of children are shown with ``... (N more)`` in between.
48
+ max_width: Max character width (terminal only). Not yet enforced.
49
+ fmt: Format spec for float values, e.g. ``".4f"``, ``".2e"``.
50
+ Auto-detected from value range when not set.
51
+ color: Enable or disable ANSI / CSS color output.
52
+ Defaults to ``True``.
53
+ style: Output target. ``"auto"`` detects Jupyter and renders
54
+ HTML there, ANSI elsewhere. ``"terminal"`` forces ANSI,
55
+ ``"html"`` forces HTML.
56
+ mode: Colorization strategy:
57
+
58
+ - ``"dtype"`` — Semantic colors by data type (blue=float,
59
+ green=int, magenta=bool, yellow=str, red=complex).
60
+ Negative values tinted red, positive tinted blue.
61
+ - ``"heatmap"`` — Diverging colormap (red → light → blue)
62
+ scaled to the array's global min/max range. Best for
63
+ spotting value patterns.
64
+ - ``"sparsity"`` — Zeros rendered as dim ``·``, non-zero
65
+ values in bold. Best for inspecting sparse or masked arrays.
66
+
67
+ render_style: Visual layout style:
68
+
69
+ - ``"tree"`` (default) — Hierarchical branch view with 2D
70
+ grids nested at the leaves. Each depth level gets a
71
+ distinct branch color for readability.
72
+ - ``"mosaic"`` — All 2D sub-slices tiled side by side in a
73
+ contact-sheet grid. Best for comparing slices in 3D/4D
74
+ arrays (batches, attention heads, channels).
75
+
76
+ stats: Show summary statistics below the tree (min, max, mean,
77
+ std, zeros %, NaN count). Defaults to ``True``.
78
+
79
+ Returns:
80
+ A ``VizOutput`` object with ``.ansi`` (str) and ``.html`` (str)
81
+ attributes. In Jupyter it auto-displays via ``_repr_html_``.
82
+ In the terminal, ``print()`` or ``str()`` renders the ANSI output.
83
+
84
+ Examples:
85
+ >>> import numpy as np
86
+ >>> from arrscope import scope
87
+ >>> scope(np.random.rand(3, 4, 5))
88
+
89
+ >>> scope(np.eye(5), title="Identity", mode="sparsity")
90
+
91
+ >>> scope(
92
+ ... np.random.rand(2, 8, 32, 32),
93
+ ... axes=["batch", "heads", "h", "w"],
94
+ ... mode="heatmap",
95
+ ... )
96
+ """
97
+ arr_np = to_numpy(arr)
98
+
99
+ color = config.color if color is None else color
100
+ max_width = max_width or config.max_width
101
+ max_height = max_height or config.max_height
102
+ mode = mode or config.mode
103
+ rs = render_style or config.render_style
104
+ show_stats = config.stats if stats is None else stats
105
+
106
+ if mode not in ("dtype", "heatmap", "sparsity"):
107
+ raise ValueError(f"Unknown mode '{mode}'. Expected one of: dtype, heatmap, sparsity")
108
+
109
+ if rs not in ("tree", "mosaic"):
110
+ raise ValueError(f"Unknown render_style '{rs}'. Expected one of: tree, mosaic")
111
+
112
+ grid_dims = _resolve_grid_dims(arr_np, axes, grid)
113
+
114
+ node = build_layout(
115
+ arr_np,
116
+ grid_dims=grid_dims,
117
+ max_height=max_height,
118
+ )
119
+
120
+ global_stats = compute_stats(arr_np) if (show_stats or mode == "heatmap") else None
121
+ if global_stats and show_stats:
122
+ node.stats = global_stats
123
+
124
+ use_html = _use_html(style)
125
+ use_terminal = _use_terminal(style)
126
+
127
+ output = VizOutput()
128
+
129
+ if use_html:
130
+ output.html = _wrap_html(
131
+ render_html(node, color=color, mode=mode, global_stats=global_stats, render_style=rs), title
132
+ )
133
+
134
+ if use_terminal:
135
+ output.ansi = _wrap_ansi(
136
+ render_terminal(node, color=color, mode=mode, global_stats=global_stats, render_style=rs), title
137
+ )
138
+
139
+ return output
140
+
141
+
142
+ def _resolve_grid_dims(
143
+ arr: np.ndarray,
144
+ axes: list[str] | None,
145
+ grid: list[str | int] | None,
146
+ ) -> list[int] | None:
147
+ if grid is None:
148
+ return None
149
+
150
+ if axes is None:
151
+ return [g if isinstance(g, int) else arr.ndim - 2 + (list(axes or []).index(g))
152
+ for g in grid]
153
+
154
+ name_to_idx = {name: i for i, name in enumerate(axes)}
155
+ resolved: list[int] = []
156
+ for g in grid:
157
+ if isinstance(g, str):
158
+ if g not in name_to_idx:
159
+ raise ValueError(
160
+ f"Axis '{g}' not found in axes={axes}. "
161
+ f"Available axes: {list(name_to_idx.keys())}"
162
+ )
163
+ resolved.append(name_to_idx[g])
164
+ else:
165
+ resolved.append(g)
166
+ return resolved
167
+
168
+
169
+ def _use_html(style: str) -> bool:
170
+ if style == "html":
171
+ return True
172
+ if style == "terminal":
173
+ return False
174
+ return _in_jupyter()
175
+
176
+
177
+ def _use_terminal(style: str) -> bool:
178
+ if style == "terminal":
179
+ return True
180
+ if style == "html":
181
+ return False
182
+ return True
183
+
184
+
185
+ def _in_jupyter() -> bool:
186
+ try:
187
+ from IPython import get_ipython
188
+
189
+ shell = get_ipython()
190
+ return shell is not None and "IPKernelApp" in shell.config
191
+ except (ImportError, AttributeError):
192
+ return False
193
+
194
+
195
+ def _wrap_html(body: str, title: str | None) -> str:
196
+ if title:
197
+ return f'<h3 style="font-family: sans-serif; margin: 4px 0;">{title}</h3>\n{body}'
198
+ return body
199
+
200
+
201
+ def _wrap_ansi(body: str, title: str | None) -> str:
202
+ if title:
203
+ from rich.text import Text
204
+
205
+ t = Text(title, style="bold")
206
+ return f"{t}\n{body}"
207
+ return body
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
 
3
3
 
4
4
  @dataclass
@@ -8,6 +8,7 @@ class Config:
8
8
  color: bool = True
9
9
  precision: int | None = None
10
10
  mode: str = "dtype"
11
+ render_style: str = "tree"
11
12
  stats: bool = True
12
13
 
13
14
 
@@ -23,11 +23,14 @@ def to_numpy(arr) -> np.ndarray:
23
23
  if "jax" in module:
24
24
  return np.asarray(arr)
25
25
 
26
+ if "tinygrad" in module:
27
+ return arr.numpy()
28
+
26
29
  try:
27
30
  return np.asarray(arr)
28
31
  except (TypeError, ValueError) as e:
29
32
  raise TypeError(
30
33
  f"Cannot convert {type(arr).__name__} to numpy array. "
31
34
  f"Supported types: np.ndarray, torch.Tensor, tf.Tensor, jax.Array, "
32
- f"and objects implementing __array__."
35
+ f"tinygrad.Tensor, and objects implementing __array__."
33
36
  ) from e
@@ -13,9 +13,14 @@ def render_html(
13
13
  color: bool = True,
14
14
  mode: str = "dtype",
15
15
  global_stats: dict | None = None,
16
+ render_style: str = "tree",
16
17
  ) -> str:
17
18
  css = _css()
18
- body = _render_node_html(node, 0, mode, global_stats)
19
+
20
+ if render_style == "mosaic":
21
+ body = _render_mosaic_html(node, mode, global_stats)
22
+ else:
23
+ body = _render_node_html(node, 0, mode, global_stats)
19
24
 
20
25
  stats_html = ""
21
26
  if global_stats and node.stats:
@@ -28,6 +33,32 @@ def render_html(
28
33
  </div>"""
29
34
 
30
35
 
36
+ def _collect_leaves(node: VizNode) -> list[VizNode]:
37
+ if node.grid_data is not None:
38
+ return [node]
39
+ leaves: list[VizNode] = []
40
+ for child in node.children:
41
+ leaves.extend(_collect_leaves(child))
42
+ return leaves
43
+
44
+
45
+ def _render_mosaic_html(
46
+ node: VizNode,
47
+ mode: str,
48
+ global_stats: dict | None,
49
+ ) -> str:
50
+ leaves = _collect_leaves(node)
51
+ if not leaves:
52
+ return "<span>(empty)</span>"
53
+
54
+ tiles: list[str] = []
55
+ for leaf in leaves:
56
+ if leaf.grid_data is not None:
57
+ tiles.append(_render_grid_html(leaf.grid_data, leaf.label, mode, global_stats))
58
+
59
+ return '<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start;">' + "".join(tiles) + "</div>"
60
+
61
+
31
62
  def _css() -> str:
32
63
  return """
33
64
  .arrscope-wrapper {
@@ -63,6 +94,13 @@ def _css() -> str:
63
94
  }
64
95
  .arrscope-row { display: flex; flex-wrap: wrap; gap: 12px; }
65
96
  .arrscope-tree { margin-left: 16px; }
97
+ .arrscope-level-0 { }
98
+ .arrscope-level-1 { border-left: 2px solid #00bcd4; padding-left: 8px; }
99
+ .arrscope-level-2 { border-left: 2px solid #4caf50; padding-left: 8px; }
100
+ .arrscope-level-3 { border-left: 2px solid #ffc107; padding-left: 8px; }
101
+ .arrscope-level-4 { border-left: 2px solid #e91e63; padding-left: 8px; }
102
+ .arrscope-level-5 { border-left: 2px solid #9c27b0; padding-left: 8px; }
103
+ .arrscope-level-6 { border-left: 2px solid #2196f3; padding-left: 8px; }
66
104
  .arrscope-ellipsis { color: #999; font-style: italic; margin: 2px 0; }
67
105
  .arrscope-header {
68
106
  font-weight: 600;
@@ -95,6 +133,12 @@ details.arrscope-details[open] > summary {
95
133
  .arrscope-shape { color: #777; }
96
134
  .arrscope-stats { color: #999; }
97
135
  .arrscope-header { border-bottom-color: #444; }
136
+ .arrscope-level-1 { border-left-color: #26c6da; }
137
+ .arrscope-level-2 { border-left-color: #66bb6a; }
138
+ .arrscope-level-3 { border-left-color: #ffd54f; }
139
+ .arrscope-level-4 { border-left-color: #f06292; }
140
+ .arrscope-level-5 { border-left-color: #ab47bc; }
141
+ .arrscope-level-6 { border-left-color: #42a5f5; }
98
142
  }
99
143
  """
100
144
 
@@ -140,7 +184,8 @@ def _render_node_html(
140
184
  parts.append(
141
185
  f"<summary>{html_mod.escape(node.label or _format_header(node))}</summary>"
142
186
  )
143
- parts.append('<div class="arrscope-tree">')
187
+ level_class = f"arrscope-level-{(depth + 1) % 7}"
188
+ parts.append(f'<div class="arrscope-tree {level_class}">')
144
189
 
145
190
  for child in node.children:
146
191
  parts.append(_render_node_html(child, depth + 1, mode, global_stats))
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import numpy as np
4
4
  from rich.box import SQUARE
5
5
  from rich.color import Color
6
+ from rich.columns import Columns
6
7
  from rich.style import Style
7
8
  from rich.table import Table
8
9
  from rich.text import Text
@@ -11,19 +12,25 @@ from rich.tree import Tree
11
12
  from arrscope._format import format_value
12
13
  from arrscope._types import VizNode
13
14
 
15
+ BRANCH_COLORS = ["grey50", "cyan", "green", "yellow", "magenta", "blue"]
16
+
14
17
 
15
18
  def render_terminal(
16
19
  node: VizNode,
17
20
  color: bool = True,
18
21
  mode: str = "dtype",
19
22
  global_stats: dict | None = None,
23
+ render_style: str = "tree",
20
24
  ) -> str:
21
25
  from rich.console import Console
22
26
 
23
27
  console = Console(width=120, color_system="truecolor" if color else None)
24
28
  parts: list = []
25
29
 
26
- body = _render_node(node, 0, color, mode, global_stats)
30
+ if render_style == "mosaic":
31
+ body = _render_mosaic(node, color, mode, global_stats)
32
+ else:
33
+ body = _render_node(node, 0, color, mode, global_stats)
27
34
  parts.append(body)
28
35
 
29
36
  if global_stats and node.stats:
@@ -35,6 +42,34 @@ def render_terminal(
35
42
  return capture.get()
36
43
 
37
44
 
45
+ def _collect_leaves(node: VizNode) -> list[VizNode]:
46
+ if node.grid_data is not None:
47
+ return [node]
48
+ leaves: list[VizNode] = []
49
+ for child in node.children:
50
+ leaves.extend(_collect_leaves(child))
51
+ return leaves
52
+
53
+
54
+ def _render_mosaic(
55
+ node: VizNode,
56
+ color: bool,
57
+ mode: str,
58
+ global_stats: dict | None,
59
+ ) -> Columns | Text:
60
+ leaves = _collect_leaves(node)
61
+ if not leaves:
62
+ return Text("(empty)")
63
+
64
+ tables: list[Table] = []
65
+ for leaf in leaves:
66
+ if leaf.grid_data is not None:
67
+ t = _render_grid(leaf.grid_data, leaf.label, color, mode, global_stats)
68
+ tables.append(t)
69
+
70
+ return Columns(tables, equal=True, expand=False)
71
+
72
+
38
73
  def _stats_text(stats: dict, color: bool) -> Text:
39
74
  dim = Style(dim=True, color="grey50") if color else Style(dim=True)
40
75
  t = Text()
@@ -76,9 +111,12 @@ def _render_node(
76
111
  t.stylize(Style(dim=True))
77
112
  return t
78
113
 
114
+ guide_color = BRANCH_COLORS[(depth) % len(BRANCH_COLORS)]
115
+ guide = Style(dim=True, color=guide_color) if color else Style(dim=True)
116
+
79
117
  tree = Tree(
80
118
  _make_header(node, color),
81
- guide_style=Style(dim=True, color="grey50") if color else Style(dim=True),
119
+ guide_style=guide,
82
120
  )
83
121
  for child in node.children:
84
122
  child_renderable = _render_node(child, depth + 1, color, mode, global_stats)
@@ -1,131 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from arrscope._config import config
4
- from arrscope._format import format_value
5
- from arrscope._layout import build_layout, compute_stats
6
- from arrscope._types import VizOutput
7
- from arrscope.adapters import to_numpy
8
- from arrscope.renderers import render_html, render_terminal
9
-
10
-
11
- def scope(
12
- arr,
13
- *,
14
- axes: list[str] | None = None,
15
- grid: list[str | int] | None = None,
16
- title: str | None = None,
17
- max_width: int | None = None,
18
- max_height: int | None = None,
19
- fmt: str | None = None,
20
- color: bool | None = None,
21
- style: str = "auto",
22
- mode: str | None = None,
23
- stats: bool | None = None,
24
- ) -> VizOutput:
25
- arr_np = to_numpy(arr)
26
-
27
- color = config.color if color is None else color
28
- max_width = max_width or config.max_width
29
- max_height = max_height or config.max_height
30
- mode = mode or config.mode
31
- show_stats = config.stats if stats is None else stats
32
-
33
- if mode not in ("dtype", "heatmap", "sparsity"):
34
- raise ValueError(f"Unknown mode '{mode}'. Expected one of: dtype, heatmap, sparsity")
35
-
36
- grid_dims = _resolve_grid_dims(arr_np, axes, grid)
37
-
38
- node = build_layout(
39
- arr_np,
40
- grid_dims=grid_dims,
41
- max_height=max_height,
42
- )
43
-
44
- global_stats = compute_stats(arr_np) if (show_stats or mode == "heatmap") else None
45
- if global_stats and show_stats:
46
- node.stats = global_stats
47
-
48
- use_html = _use_html(style)
49
- use_terminal = _use_terminal(style)
50
-
51
- output = VizOutput()
52
-
53
- if use_html:
54
- output.html = _wrap_html(
55
- render_html(node, color=color, mode=mode, global_stats=global_stats), title
56
- )
57
-
58
- if use_terminal:
59
- output.ansi = _wrap_ansi(
60
- render_terminal(node, color=color, mode=mode, global_stats=global_stats), title
61
- )
62
-
63
- return output
64
-
65
-
66
- def _resolve_grid_dims(
67
- arr: np.ndarray,
68
- axes: list[str] | None,
69
- grid: list[str | int] | None,
70
- ) -> list[int] | None:
71
- if grid is None:
72
- return None
73
-
74
- if axes is None:
75
- return [g if isinstance(g, int) else arr.ndim - 2 + (list(axes or []).index(g))
76
- for g in grid]
77
-
78
- name_to_idx = {name: i for i, name in enumerate(axes)}
79
- resolved: list[int] = []
80
- for g in grid:
81
- if isinstance(g, str):
82
- if g not in name_to_idx:
83
- raise ValueError(
84
- f"Axis '{g}' not found in axes={axes}. "
85
- f"Available axes: {list(name_to_idx.keys())}"
86
- )
87
- resolved.append(name_to_idx[g])
88
- else:
89
- resolved.append(g)
90
- return resolved
91
-
92
-
93
- def _use_html(style: str) -> bool:
94
- if style == "html":
95
- return True
96
- if style == "terminal":
97
- return False
98
- return _in_jupyter()
99
-
100
-
101
- def _use_terminal(style: str) -> bool:
102
- if style == "terminal":
103
- return True
104
- if style == "html":
105
- return False
106
- return True
107
-
108
-
109
- def _in_jupyter() -> bool:
110
- try:
111
- from IPython import get_ipython
112
-
113
- shell = get_ipython()
114
- return shell is not None and "IPKernelApp" in shell.config
115
- except (ImportError, AttributeError):
116
- return False
117
-
118
-
119
- def _wrap_html(body: str, title: str | None) -> str:
120
- if title:
121
- return f'<h3 style="font-family: sans-serif; margin: 4px 0;">{title}</h3>\n{body}'
122
- return body
123
-
124
-
125
- def _wrap_ansi(body: str, title: str | None) -> str:
126
- if title:
127
- from rich.text import Text
128
-
129
- t = Text(title, style="bold")
130
- return f"{t}\n{body}"
131
- return body
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes