nbcat 0.9.4__tar.gz → 0.9.6__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 (35) hide show
  1. {nbcat-0.9.4 → nbcat-0.9.6}/PKG-INFO +26 -12
  2. nbcat-0.9.6/README.md +81 -0
  3. nbcat-0.9.6/docs/screenshot.png +0 -0
  4. {nbcat-0.9.4 → nbcat-0.9.6}/pyproject.toml +1 -2
  5. nbcat-0.9.6/src/nbcat/__init__.py +1 -0
  6. {nbcat-0.9.4 → nbcat-0.9.6}/src/nbcat/enums.py +7 -0
  7. {nbcat-0.9.4 → nbcat-0.9.6}/src/nbcat/main.py +23 -9
  8. nbcat-0.9.6/src/nbcat/markdown.py +51 -0
  9. {nbcat-0.9.4 → nbcat-0.9.6}/src/nbcat/schemas.py +25 -12
  10. {nbcat-0.9.4 → nbcat-0.9.6}/tests/test_render_cell.py +5 -5
  11. {nbcat-0.9.4 → nbcat-0.9.6}/uv.lock +1 -1
  12. nbcat-0.9.4/README.md +0 -67
  13. nbcat-0.9.4/src/nbcat/__init__.py +0 -1
  14. {nbcat-0.9.4 → nbcat-0.9.6}/.github/workflows/ci.yml +0 -0
  15. {nbcat-0.9.4 → nbcat-0.9.6}/.gitignore +0 -0
  16. {nbcat-0.9.4 → nbcat-0.9.6}/LICENSE +0 -0
  17. {nbcat-0.9.4 → nbcat-0.9.6}/Makefile +0 -0
  18. {nbcat-0.9.4 → nbcat-0.9.6}/src/nbcat/exceptions.py +0 -0
  19. {nbcat-0.9.4 → nbcat-0.9.6}/src/nbcat/py.typed +0 -0
  20. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/invalid.ipynb +0 -0
  21. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/many_tracebacks.ipynb +0 -0
  22. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/no_min_version.ipynb +0 -0
  23. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test3.ipynb +0 -0
  24. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test3_no_metadata.ipynb +0 -0
  25. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test3_no_min_version.ipynb +0 -0
  26. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test3_no_worksheets.ipynb +0 -0
  27. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test3_worksheet_with_no_cells.ipynb +0 -0
  28. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4.5.ipynb +0 -0
  29. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4.ipynb +0 -0
  30. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4custom.ipynb +0 -0
  31. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4docinfo.ipynb +0 -0
  32. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4jupyter_metadata.ipynb +0 -0
  33. {nbcat-0.9.4 → nbcat-0.9.6}/tests/assets/test4jupyter_metadata_timings.ipynb +0 -0
  34. {nbcat-0.9.4 → nbcat-0.9.6}/tests/conftest.py +0 -0
  35. {nbcat-0.9.4 → nbcat-0.9.6}/tests/test_read_notebook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbcat
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: cat for jupyter notebooks
5
5
  Project-URL: Homepage, https://github.com/akopdev/nbcat
6
6
  Project-URL: Repository, https://github.com/akopdev/nbcat
@@ -41,17 +41,31 @@ Requires-Dist: pytest-responses; extra == 'dev'
41
41
  Requires-Dist: ruff; extra == 'dev'
42
42
  Description-Content-Type: text/markdown
43
43
 
44
- # 📦 nbcat
44
+ # nbcat
45
45
 
46
- `nbcat` lets you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
46
+ `nbcat` let you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
47
47
 
48
- ## 🚀 Features
48
+ <p align="center">
49
+ <a href="docs/screenshot.png" target="blank"><img src="docs/screenshot.png" width="400" /></a>
50
+ </p>
49
51
 
50
- - Fast and lightweight with minimal external dependencies
52
+ ## Features
53
+
54
+ - Very fast and lightweight with minimal dependencies
51
55
  - Preview remote notebooks without downloading them
52
- - Supports for all Jupyter notebook versions - including legacy formats
56
+ - Supports for all Jupyter notebook versions, including old legacy formats
57
+
58
+ ## Motivation
59
+
60
+ The idea of previewing notebooks in a terminal is not new - there have been many previous attempts to achieve it.
61
+ However, most are either slow and overengineered with a ton of half-working features, or they're outdated and incompatible with modern Python.
62
+
63
+ I was looking for a simple tool that let me quickly render Jupyter notebooks without switching context from my terminal window or installing a ton of dependencies.
64
+
65
+ Please note, that `nbcat` doesn't aim to replace JupyterLab. If you need a full-featured terminal experience, I recommend checking out [euporie](https://euporie.readthedocs.io/) instead.
66
+
53
67
 
54
- ## 📦 Installation
68
+ ## Installation
55
69
 
56
70
  From the command line using pip:
57
71
 
@@ -59,7 +73,7 @@ From the command line using pip:
59
73
  pip install nbcat
60
74
  ```
61
75
 
62
- ## 🛠️ Quickstart
76
+ ## Quickstart
63
77
 
64
78
  ```bash
65
79
  $ nbcat notebook.ipynb
@@ -77,7 +91,7 @@ Example use case with `fzf` command that lists all `.ipynb` files and uses `nbca
77
91
  find . -type f -name "*.ipynb" | fzf --preview 'nbcat {}'
78
92
  ```
79
93
 
80
- ## 🧪 Testing & Development
94
+ ## Testing & Development
81
95
 
82
96
  Run the tests:
83
97
 
@@ -91,15 +105,15 @@ Check code quality:
91
105
  make format lint
92
106
  ```
93
107
 
94
- ## 🙌 Contributing
108
+ ## Contributing
95
109
 
96
110
  Contributions are welcome! Please open an issue or [pull request](https://github.com/akopdev/nbcat/pulls).
97
111
 
98
- ## 📄 License
112
+ ## License
99
113
 
100
114
  Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
101
115
 
102
- ## 🔗 Useful Links
116
+ ## Useful Links
103
117
 
104
118
  - 📘 Documentation: _coming soon_
105
119
  - 🐛 Issues: [GitHub Issues](https://github.com/akopdev/nbcat/issues)
nbcat-0.9.6/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # nbcat
2
+
3
+ `nbcat` let you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
4
+
5
+ <p align="center">
6
+ <a href="docs/screenshot.png" target="blank"><img src="docs/screenshot.png" width="400" /></a>
7
+ </p>
8
+
9
+ ## Features
10
+
11
+ - Very fast and lightweight with minimal dependencies
12
+ - Preview remote notebooks without downloading them
13
+ - Supports for all Jupyter notebook versions, including old legacy formats
14
+
15
+ ## Motivation
16
+
17
+ The idea of previewing notebooks in a terminal is not new - there have been many previous attempts to achieve it.
18
+ However, most are either slow and overengineered with a ton of half-working features, or they're outdated and incompatible with modern Python.
19
+
20
+ I was looking for a simple tool that let me quickly render Jupyter notebooks without switching context from my terminal window or installing a ton of dependencies.
21
+
22
+ Please note, that `nbcat` doesn't aim to replace JupyterLab. If you need a full-featured terminal experience, I recommend checking out [euporie](https://euporie.readthedocs.io/) instead.
23
+
24
+
25
+ ## Installation
26
+
27
+ From the command line using pip:
28
+
29
+ ```bash
30
+ pip install nbcat
31
+ ```
32
+
33
+ ## Quickstart
34
+
35
+ ```bash
36
+ $ nbcat notebook.ipynb
37
+ ```
38
+
39
+ You can pass URLs as well.
40
+
41
+ ```bash
42
+ $ nbcat https://raw.githubusercontent.com/akopdev/nbcat/refs/heads/main/tests/assets/test4.ipynb
43
+ ```
44
+
45
+ Example use case with `fzf` command that lists all `.ipynb` files and uses `nbcat` for previewing them:
46
+
47
+ ```bash
48
+ find . -type f -name "*.ipynb" | fzf --preview 'nbcat {}'
49
+ ```
50
+
51
+ ## Testing & Development
52
+
53
+ Run the tests:
54
+
55
+ ```bash
56
+ make test
57
+ ```
58
+
59
+ Check code quality:
60
+
61
+ ```bash
62
+ make format lint
63
+ ```
64
+
65
+ ## Contributing
66
+
67
+ Contributions are welcome! Please open an issue or [pull request](https://github.com/akopdev/nbcat/pulls).
68
+
69
+ ## License
70
+
71
+ Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
72
+
73
+ ## Useful Links
74
+
75
+ - 📘 Documentation: _coming soon_
76
+ - 🐛 Issues: [GitHub Issues](https://github.com/akopdev/nbcat/issues)
77
+ - 🚀 Releases: [GitHub Releases](https://github.com/akopdev/nbcat/releases)
78
+
79
+ ---
80
+
81
+ Made with ❤️ by [Akop Kesheshyan](https://github.com/akopdev)
Binary file
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nbcat"
3
- version = "0.9.4"
3
+ version = "0.9.6"
4
4
  description = "cat for jupyter notebooks"
5
5
  authors = [
6
6
  { name = "Akop Kesheshyan", email = "devnull@akop.dev" }
@@ -38,7 +38,6 @@ nbcat = "nbcat.main:main"
38
38
  mock_use_standalone_module = true
39
39
 
40
40
  [tool.pytest.ini_options]
41
- asyncio_default_fixture_loop_scope = "function"
42
41
  testpaths = "tests/"
43
42
 
44
43
  [tool.ruff]
@@ -0,0 +1 @@
1
+ __version__ = "0.9.6"
@@ -14,3 +14,10 @@ class OutputType(str, Enum):
14
14
  EXECUTE_RESULT = "execute_result"
15
15
  ERROR = "error"
16
16
  PYOUT = "pyout"
17
+
18
+
19
+ class OutputCellType(str, Enum):
20
+ PLAIN = "plain"
21
+ HTML = "html"
22
+ IMAGE = "image"
23
+ JSON = "json"
@@ -9,19 +9,20 @@ from argcomplete.completers import FilesCompleter
9
9
  from pydantic import ValidationError
10
10
  from rich import box
11
11
  from rich.console import Console, RenderableType
12
- from rich.markdown import Markdown
13
12
  from rich.panel import Panel
13
+ from rich.pretty import Pretty
14
14
  from rich.syntax import Syntax
15
15
  from rich.table import Table
16
16
  from rich.text import Text
17
17
 
18
18
  from . import __version__
19
- from .enums import CellType
19
+ from .enums import CellType, OutputCellType
20
20
  from .exceptions import (
21
21
  InvalidNotebookFormatError,
22
22
  NotebookNotFoundError,
23
23
  UnsupportedNotebookTypeError,
24
24
  )
25
+ from .markdown import Markdown
25
26
  from .schemas import Cell, Notebook
26
27
 
27
28
  console = Console()
@@ -92,18 +93,28 @@ def render_cell(cell: Cell) -> list[tuple[Union[str, None], RenderableType]]:
92
93
  def _render_raw(input: str) -> Text:
93
94
  return Text(input)
94
95
 
96
+ def _render_image(input: str) -> None:
97
+ return None
98
+
99
+ def _render_json(input: str) -> Pretty:
100
+ return Pretty(input)
101
+
95
102
  RENDERERS = {
96
103
  CellType.MARKDOWN: _render_markdown,
97
104
  CellType.CODE: _render_code,
98
105
  CellType.RAW: _render_raw,
99
106
  CellType.HEADING: _render_markdown,
107
+ OutputCellType.PLAIN: _render_raw,
108
+ OutputCellType.HTML: _render_markdown,
109
+ OutputCellType.IMAGE: _render_image,
110
+ OutputCellType.JSON: _render_json,
100
111
  }
101
112
 
102
113
  rows: list[tuple[Union[str, None], RenderableType]] = []
103
114
  renderer = RENDERERS.get(cell.cell_type)
104
115
  source = renderer(cell.input) if renderer else None
105
116
  if source:
106
- label = f"[green][{cell.execution_count}][/]:" if cell.execution_count else None
117
+ label = f"[green][{cell.execution_count}][/]" if cell.execution_count else None
107
118
  rows.append(
108
119
  (
109
120
  label,
@@ -113,13 +124,16 @@ def render_cell(cell: Cell) -> list[tuple[Union[str, None], RenderableType]]:
113
124
 
114
125
  for o in cell.outputs:
115
126
  if o.output:
116
- label = f"[blue][{o.execution_count}][/]:" if o.execution_count else None
117
- rows.append(
118
- (
119
- label,
120
- o.output,
127
+ renderer = RENDERERS.get(o.output.output_type)
128
+ output = renderer(o.output.text) if renderer else None
129
+ if output:
130
+ label = f"[blue][{o.execution_count}][/]" if o.execution_count else None
131
+ rows.append(
132
+ (
133
+ label,
134
+ output,
135
+ )
121
136
  )
122
- )
123
137
  return rows
124
138
 
125
139
 
@@ -0,0 +1,51 @@
1
+ # In an ideal world, this code shouldn't exist. However, for reasons unknown to me,
2
+ # `rich` decided to format markdown headers in a way that makes them unusable
3
+ # in the terminal: they are centered and wrapped in a Panel, which causes them to
4
+ # blend in with the rest of the content. It takes me time to understand what I'm
5
+ # looking at.
6
+ #
7
+ # Instead, I override the default implementation by adding colors and preserving
8
+ # the original formatting, so that the headers remain recognizable even on
9
+ # black-and-white screens.
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import ClassVar
14
+
15
+ from rich import markdown as md
16
+ from rich.console import Console, ConsoleOptions, RenderResult
17
+ from rich.text import Text
18
+
19
+
20
+ class Heading(md.Heading):
21
+ """A heading."""
22
+
23
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
24
+ styles = {
25
+ "h1": "bold cyan",
26
+ "h2": "bold magenta",
27
+ "h3": "bold yellow",
28
+ }
29
+ indent = int(self.tag.strip("h"))
30
+ yield Text(f"{'#' * indent} {self.text}", style=styles.get(self.tag, "dim white"))
31
+
32
+
33
+ class Markdown(md.Markdown):
34
+ elements: ClassVar[dict[str, type[md.MarkdownElement]]] = {
35
+ "paragraph_open": md.Paragraph,
36
+ "heading_open": Heading,
37
+ "fence": md.CodeBlock,
38
+ "code_block": md.CodeBlock,
39
+ "blockquote_open": md.BlockQuote,
40
+ "hr": md.HorizontalRule,
41
+ "bullet_list_open": md.ListElement,
42
+ "ordered_list_open": md.ListElement,
43
+ "list_item_open": md.ListItem,
44
+ "image": md.ImageItem,
45
+ "table_open": md.TableElement,
46
+ "tbody_open": md.TableBodyElement,
47
+ "thead_open": md.TableHeaderElement,
48
+ "tr_open": md.TableRowElement,
49
+ "td_open": md.TableDataElement,
50
+ "th_open": md.TableDataElement,
51
+ }
@@ -2,7 +2,7 @@ from typing import Any, Union
2
2
 
3
3
  from pydantic import BaseModel, computed_field, model_validator
4
4
 
5
- from .enums import CellType, OutputType
5
+ from .enums import CellType, OutputCellType, OutputType
6
6
  from .exceptions import InvalidNotebookFormatError
7
7
 
8
8
 
@@ -11,15 +11,19 @@ class BaseOutput(BaseModel):
11
11
  execution_count: Union[int, None] = None
12
12
 
13
13
 
14
+ class CellOutput(BaseModel):
15
+ output_type: OutputCellType
16
+ text: str
17
+
18
+
14
19
  class StreamOutput(BaseOutput):
15
20
  text: Union[list[str], str]
16
21
 
17
22
  @computed_field
18
23
  @property
19
- def output(self) -> str:
20
- if isinstance(self.text, list):
21
- return "".join(self.text)
22
- return self.text
24
+ def output(self) -> CellOutput:
25
+ text = "".join(self.text) if isinstance(self.text, list) else self.text
26
+ return CellOutput(output_type=OutputCellType.PLAIN, text=text)
23
27
 
24
28
 
25
29
  class DisplayDataOutput(BaseOutput):
@@ -27,9 +31,18 @@ class DisplayDataOutput(BaseOutput):
27
31
 
28
32
  @computed_field
29
33
  @property
30
- def output(self) -> str:
31
- # TODO: add support for rich display outputs
32
- return ""
34
+ def output(self) -> Union[CellOutput, None]:
35
+ data_type_map = {
36
+ "text/html": OutputCellType.HTML,
37
+ "text/png": OutputCellType.IMAGE,
38
+ "text/plain": OutputCellType.PLAIN,
39
+ "application/vnd.raw.v1+json": OutputCellType.JSON,
40
+ }
41
+ for data_type, output_type in data_type_map.items():
42
+ data = self.data.get(data_type)
43
+ if data:
44
+ text = "".join(data) if isinstance(data, list) else str(data)
45
+ return CellOutput(output_type=output_type, text=text)
33
46
 
34
47
 
35
48
  class ErrorOutput(BaseOutput):
@@ -39,8 +52,8 @@ class ErrorOutput(BaseOutput):
39
52
 
40
53
  @computed_field
41
54
  @property
42
- def output(self) -> str:
43
- return "\n".join(self.traceback)
55
+ def output(self) -> CellOutput:
56
+ return CellOutput(output_type=OutputCellType.PLAIN, text="\n".join(self.traceback))
44
57
 
45
58
 
46
59
  class PyoutDataOutput(BaseOutput):
@@ -48,8 +61,8 @@ class PyoutDataOutput(BaseOutput):
48
61
 
49
62
  @computed_field
50
63
  @property
51
- def output(self) -> str:
52
- return "\n".join(self.text)
64
+ def output(self) -> CellOutput:
65
+ return CellOutput(output_type=OutputCellType.PLAIN, text="\n".join(self.text))
53
66
 
54
67
 
55
68
  class Cell(BaseModel):
@@ -22,7 +22,7 @@ def test_render_cell_input_rendering(cell_type: str, source: str, expected):
22
22
 
23
23
  assert len(rendered) == 1
24
24
  label, content = rendered[0]
25
- assert label == "[green][42][/]:"
25
+ assert label == "[green][42][/]"
26
26
  assert isinstance(content, expected)
27
27
 
28
28
 
@@ -44,11 +44,11 @@ def test_render_cell_with_outputs():
44
44
  assert rendered[0][0] is None
45
45
  assert isinstance(rendered[0][1], Panel)
46
46
 
47
- assert rendered[1][0] == "[blue][7][/]:"
48
- assert isinstance(rendered[1][1], str)
47
+ assert rendered[1][0] == "[blue][7][/]"
48
+ assert isinstance(rendered[1][1], Text)
49
49
 
50
50
  assert rendered[2][0] is None
51
- assert isinstance(rendered[2][1], str)
51
+ assert isinstance(rendered[2][1], Text)
52
52
 
53
53
 
54
54
  def test_render_cell_skips_empty_outputs():
@@ -63,5 +63,5 @@ def test_render_cell_skips_empty_outputs():
63
63
  rendered = render_cell(cell)
64
64
 
65
65
  assert len(rendered) == 1 # Only source input is rendered
66
- assert rendered[0][0] == "[green][1][/]:"
66
+ assert rendered[0][0] == "[green][1][/]"
67
67
  assert isinstance(rendered[0][1], Text)
@@ -237,7 +237,7 @@ wheels = [
237
237
 
238
238
  [[package]]
239
239
  name = "nbcat"
240
- version = "0.9.4"
240
+ version = "0.9.6"
241
241
  source = { editable = "." }
242
242
  dependencies = [
243
243
  { name = "argcomplete" },
nbcat-0.9.4/README.md DELETED
@@ -1,67 +0,0 @@
1
- # 📦 nbcat
2
-
3
- `nbcat` lets you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
4
-
5
- ## 🚀 Features
6
-
7
- - Fast and lightweight with minimal external dependencies
8
- - Preview remote notebooks without downloading them
9
- - Supports for all Jupyter notebook versions - including legacy formats
10
-
11
- ## 📦 Installation
12
-
13
- From the command line using pip:
14
-
15
- ```bash
16
- pip install nbcat
17
- ```
18
-
19
- ## 🛠️ Quickstart
20
-
21
- ```bash
22
- $ nbcat notebook.ipynb
23
- ```
24
-
25
- You can pass URLs as well.
26
-
27
- ```bash
28
- $ nbcat https://raw.githubusercontent.com/akopdev/nbcat/refs/heads/main/tests/assets/test4.ipynb
29
- ```
30
-
31
- Example use case with `fzf` command that lists all `.ipynb` files and uses `nbcat` for previewing them:
32
-
33
- ```bash
34
- find . -type f -name "*.ipynb" | fzf --preview 'nbcat {}'
35
- ```
36
-
37
- ## 🧪 Testing & Development
38
-
39
- Run the tests:
40
-
41
- ```bash
42
- make test
43
- ```
44
-
45
- Check code quality:
46
-
47
- ```bash
48
- make format lint
49
- ```
50
-
51
- ## 🙌 Contributing
52
-
53
- Contributions are welcome! Please open an issue or [pull request](https://github.com/akopdev/nbcat/pulls).
54
-
55
- ## 📄 License
56
-
57
- Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
58
-
59
- ## 🔗 Useful Links
60
-
61
- - 📘 Documentation: _coming soon_
62
- - 🐛 Issues: [GitHub Issues](https://github.com/akopdev/nbcat/issues)
63
- - 🚀 Releases: [GitHub Releases](https://github.com/akopdev/nbcat/releases)
64
-
65
- ---
66
-
67
- Made with ❤️ by [Akop Kesheshyan](https://github.com/akopdev)
@@ -1 +0,0 @@
1
- __version__ = "0.9.4"
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
File without changes
File without changes
File without changes