reflex-jupyter-renderer-react 0.1.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.
- reflex_jupyter_renderer_react-0.1.0/LICENSE +21 -0
- reflex_jupyter_renderer_react-0.1.0/PKG-INFO +248 -0
- reflex_jupyter_renderer_react-0.1.0/README.md +220 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react/__init__.py +79 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react/jupyter_renderer_react.py +175 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react/jupyter_renderer_react.pyi +120 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react/models.py +188 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react.egg-info/PKG-INFO +248 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react.egg-info/SOURCES.txt +14 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react.egg-info/dependency_links.txt +1 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react.egg-info/requires.txt +6 -0
- reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react.egg-info/top_level.txt +1 -0
- reflex_jupyter_renderer_react-0.1.0/pyproject.toml +60 -0
- reflex_jupyter_renderer_react-0.1.0/setup.cfg +4 -0
- reflex_jupyter_renderer_react-0.1.0/tests/test_component.py +65 -0
- reflex_jupyter_renderer_react-0.1.0/tests/test_models.py +71 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ernesto Crespo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflex-jupyter-renderer-react
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A native Reflex component wrapping jupyter-renderer-react to render Jupyter notebooks (.ipynb) in pure Python.
|
|
5
|
+
Author-email: Ernesto Crespo <ecrespo@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ecrespo/reflex-jupyter-renderer-react
|
|
8
|
+
Project-URL: Repository, https://github.com/ecrespo/reflex-jupyter-renderer-react
|
|
9
|
+
Project-URL: Issues, https://github.com/ecrespo/reflex-jupyter-renderer-react/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/ecrespo/reflex-jupyter-renderer-react#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/ecrespo/reflex-jupyter-renderer-react/releases
|
|
12
|
+
Project-URL: PyPI, https://pypi.org/project/reflex-jupyter-renderer-react/
|
|
13
|
+
Project-URL: Upstream, https://github.com/iomete/jupyter-renderer-react
|
|
14
|
+
Keywords: reflex,reflex-custom-components,jupyter,notebook,ipynb,renderer,react,component,visualization
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: reflex>=0.8.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# reflex-jupyter-renderer-react
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/reflex-jupyter-renderer-react/)
|
|
32
|
+
[](https://pypi.org/project/reflex-jupyter-renderer-react/)
|
|
33
|
+
[](https://github.com/ecrespo/reflex-jupyter-renderer-react/blob/main/LICENSE)
|
|
34
|
+
[](https://github.com/ecrespo/reflex-jupyter-renderer-react)
|
|
35
|
+
|
|
36
|
+
- **Source:** https://github.com/ecrespo/reflex-jupyter-renderer-react
|
|
37
|
+
- **PyPI:** https://pypi.org/project/reflex-jupyter-renderer-react/
|
|
38
|
+
- **Issues:** https://github.com/ecrespo/reflex-jupyter-renderer-react/issues
|
|
39
|
+
- **Upstream React library:** https://github.com/iomete/jupyter-renderer-react
|
|
40
|
+
|
|
41
|
+
A native [Reflex](https://reflex.dev/) component that wraps the
|
|
42
|
+
[`jupyter-renderer-react`](https://github.com/iomete/jupyter-renderer-react) React
|
|
43
|
+
library, so you can render **Jupyter notebooks (`.ipynb`)** in a Reflex app —
|
|
44
|
+
with syntax highlighting, markdown, rich outputs, theming, collapsible cells and
|
|
45
|
+
copy-to-clipboard — in **pure Python**. No HTML, no `iframe`, no hand-written
|
|
46
|
+
JavaScript.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import reflex as rx
|
|
50
|
+
from reflex_jupyter_renderer_react import (
|
|
51
|
+
jupyter_notebook_viewer, Notebook, MarkdownCell, CodeCell, stream_output,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
nb = Notebook(cells=[
|
|
55
|
+
MarkdownCell("# Hello, notebook"),
|
|
56
|
+
CodeCell("print('hi')", execution_count=1, outputs=[stream_output("hi\n")]),
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
def index() -> rx.Component:
|
|
60
|
+
return jupyter_notebook_viewer(notebook=nb.to_dict(), theme="dark")
|
|
61
|
+
|
|
62
|
+
app = rx.App()
|
|
63
|
+
app.add_page(index)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Why a native component?
|
|
67
|
+
|
|
68
|
+
| iframe / pre-rendered HTML | native component (this package) |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Isolated from app state | Wired into Reflex state via events (`on_file_load`, `on_error`, …) |
|
|
71
|
+
| Tied to static files/routes | Reusable, `pip install`-able |
|
|
72
|
+
| Untyped JSON edited by hand | Typed Python builders for notebooks |
|
|
73
|
+
| External hosting / manual embedding | npm dependency resolved by Reflex automatically |
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install reflex-jupyter-renderer-react
|
|
79
|
+
# or, with uv:
|
|
80
|
+
uv add reflex-jupyter-renderer-react
|
|
81
|
+
# or, from this repo (editable):
|
|
82
|
+
uv pip install -e .
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Reflex installs the underlying npm package (`jupyter-renderer-react`) into the
|
|
86
|
+
compiled frontend automatically on the first `reflex run`.
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
### Three ways to supply a notebook
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from reflex_jupyter_renderer_react import (
|
|
94
|
+
jupyter_notebook_viewer, JupyterNotebookViewer, Notebook, MarkdownCell, CodeCell,
|
|
95
|
+
load_notebook,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 1) Typed Python builders
|
|
99
|
+
nb = Notebook(cells=[MarkdownCell("# Title"), CodeCell("1 + 1", execution_count=1)])
|
|
100
|
+
jupyter_notebook_viewer(notebook=nb.to_dict())
|
|
101
|
+
|
|
102
|
+
# 2) A raw JSON string
|
|
103
|
+
jupyter_notebook_viewer(notebook='{"cells": [], "nbformat": 4, "nbformat_minor": 4}')
|
|
104
|
+
# or: JupyterNotebookViewer.from_json(json_str)
|
|
105
|
+
|
|
106
|
+
# 3) Load from a URL or from disk
|
|
107
|
+
JupyterNotebookViewer.from_url("https://example.com/notebook.ipynb")
|
|
108
|
+
jupyter_notebook_viewer(notebook=load_notebook("assets/demo-example.ipynb"))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Props
|
|
112
|
+
|
|
113
|
+
| Prop | Type | Default | Description |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `notebook` | `dict \| str` | required | Parsed dict, JSON string, or `{"filePath": "..."}`. |
|
|
116
|
+
| `theme` | `str \| dict` | `"light"` | `"light"`, `"dark"`, or a custom theme object. |
|
|
117
|
+
| `show_cell_numbers` | `bool` | `True` | Show execution count for code cells. |
|
|
118
|
+
| `show_outputs` | `bool` | `True` | Render cell outputs. |
|
|
119
|
+
| `collapsible` | `bool` | `False` | Allow cells to collapse. |
|
|
120
|
+
| `copyable` | `bool` | `True` | Copy button on code cells. |
|
|
121
|
+
| `class_names` | `dict` | `{}` | Per-part class names (`classNames`). |
|
|
122
|
+
| `styles` | `dict` | `{}` | Per-part inline styles (`styles`). |
|
|
123
|
+
| `fetch_options` | `dict` | `{}` | `{"headers": {...}, "timeout": 10000}` for URL loading. |
|
|
124
|
+
|
|
125
|
+
> Reflex converts `snake_case` props to `camelCase` automatically
|
|
126
|
+
> (`show_cell_numbers` → `showCellNumbers`).
|
|
127
|
+
|
|
128
|
+
### Events → Python
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
class State(rx.State):
|
|
132
|
+
status: str = "idle"
|
|
133
|
+
|
|
134
|
+
@rx.event
|
|
135
|
+
def loaded(self, notebook: dict):
|
|
136
|
+
self.status = f"{len(notebook.get('cells', []))} cells loaded"
|
|
137
|
+
|
|
138
|
+
@rx.event
|
|
139
|
+
def failed(self, message: str):
|
|
140
|
+
self.status = f"error: {message}"
|
|
141
|
+
|
|
142
|
+
JupyterNotebookViewer.from_url(
|
|
143
|
+
"https://example.com/notebook.ipynb",
|
|
144
|
+
fetch_options={"timeout": 10000},
|
|
145
|
+
on_file_load=State.loaded,
|
|
146
|
+
on_file_error=State.failed,
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| Event | Payload | Fires when |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `on_file_load` | `notebook: dict` | A notebook is fetched from a `filePath`. |
|
|
153
|
+
| `on_file_error` | `message: str` | File loading fails (network / 404 / timeout). |
|
|
154
|
+
| `on_error` | `message: str` | Parsing / rendering fails. |
|
|
155
|
+
|
|
156
|
+
## Demo app
|
|
157
|
+
|
|
158
|
+
A full demo lives in [`jupyter_renderer_react_demo/`](jupyter_renderer_react_demo)
|
|
159
|
+
and exercises every feature (typed builders, JSON string, URL loading with events,
|
|
160
|
+
custom styles, and live theme/prop toggles).
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
uv pip install -e .
|
|
164
|
+
cd jupyter_renderer_react_demo
|
|
165
|
+
uv run reflex run
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Building notebooks in Python
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from reflex_jupyter_renderer_react import (
|
|
172
|
+
Notebook, MarkdownCell, CodeCell,
|
|
173
|
+
stream_output, execute_result_output, error_output,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
nb = Notebook(cells=[
|
|
177
|
+
MarkdownCell("# Report"),
|
|
178
|
+
CodeCell("print('hi')", execution_count=1, outputs=[stream_output("hi\n")]),
|
|
179
|
+
CodeCell("2 ** 10", execution_count=2,
|
|
180
|
+
outputs=[execute_result_output({"text/plain": "1024"}, execution_count=2)]),
|
|
181
|
+
CodeCell("1/0", execution_count=3,
|
|
182
|
+
outputs=[error_output("ZeroDivisionError", "division by zero")]),
|
|
183
|
+
])
|
|
184
|
+
data = nb.to_dict()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The data-model layer (`Notebook`, cells, output factories, `load_notebook`) imports
|
|
188
|
+
**without Reflex installed**, so it is usable in plain scripts and unit tests.
|
|
189
|
+
|
|
190
|
+
## Naming note
|
|
191
|
+
|
|
192
|
+
The upstream project has some naming inconsistencies. This wrapper centralizes
|
|
193
|
+
them in overridable constants and defaults to the **public, documented contract**:
|
|
194
|
+
|
|
195
|
+
| Source | Name |
|
|
196
|
+
|---|---|
|
|
197
|
+
| Public README usage | `JupyterNotebookViewer` |
|
|
198
|
+
| `src/index.ts` export | `JupiterNotebookViewer` (sic) |
|
|
199
|
+
| `package.json` name | `@iomete/jupyter-renderer-react` (GitHub registry) |
|
|
200
|
+
|
|
201
|
+
Defaults: `library = "jupyter-renderer-react"`, `tag = "JupyterNotebookViewer"`.
|
|
202
|
+
To retarget the scoped package or the source-spelled tag:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import reflex_jupyter_renderer_react.jupyter_renderer_react as j
|
|
206
|
+
j.JupyterNotebookViewer.library = "@iomete/jupyter-renderer-react"
|
|
207
|
+
j.JupyterNotebookViewer.tag = "JupiterNotebookViewer"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
You can also pin a version via `LIBRARY_VERSION` in
|
|
211
|
+
`custom_components/reflex_jupyter_renderer_react/jupyter_renderer_react.py`.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
This project follows the Reflex **custom component** workflow and uses **`uv`**.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv venv
|
|
219
|
+
uv pip install -e ".[dev]"
|
|
220
|
+
uv run pytest # run the tests
|
|
221
|
+
|
|
222
|
+
# Publish (custom component flow)
|
|
223
|
+
reflex component build # -> dist/
|
|
224
|
+
uv publish # or: twine upload dist/*
|
|
225
|
+
reflex component share # list in the Reflex gallery
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
See [`specs/`](specs) for the full Spec-Driven Design documentation: PRD,
|
|
229
|
+
architecture, API, data-model schema, and the phased implementation plan & tasks.
|
|
230
|
+
|
|
231
|
+
## Project structure
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
reflex-jupyter-renderer-react/
|
|
235
|
+
├── custom_components/reflex_jupyter_renderer_react/
|
|
236
|
+
│ ├── __init__.py
|
|
237
|
+
│ ├── jupyter_renderer_react.py # the Reflex component
|
|
238
|
+
│ └── models.py # dependency-free notebook builders
|
|
239
|
+
├── jupyter_renderer_react_demo/ # runnable demo app
|
|
240
|
+
├── tests/ # data-model + component contract tests
|
|
241
|
+
├── specs/ # SDD: prd / technical / api / data-model / plans
|
|
242
|
+
├── pyproject.toml
|
|
243
|
+
├── README.md · CHANGELOG.md · LICENSE
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT © Ernesto Crespo. The upstream `jupyter-renderer-react` library is also MIT.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# reflex-jupyter-renderer-react
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/reflex-jupyter-renderer-react/)
|
|
4
|
+
[](https://pypi.org/project/reflex-jupyter-renderer-react/)
|
|
5
|
+
[](https://github.com/ecrespo/reflex-jupyter-renderer-react/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/ecrespo/reflex-jupyter-renderer-react)
|
|
7
|
+
|
|
8
|
+
- **Source:** https://github.com/ecrespo/reflex-jupyter-renderer-react
|
|
9
|
+
- **PyPI:** https://pypi.org/project/reflex-jupyter-renderer-react/
|
|
10
|
+
- **Issues:** https://github.com/ecrespo/reflex-jupyter-renderer-react/issues
|
|
11
|
+
- **Upstream React library:** https://github.com/iomete/jupyter-renderer-react
|
|
12
|
+
|
|
13
|
+
A native [Reflex](https://reflex.dev/) component that wraps the
|
|
14
|
+
[`jupyter-renderer-react`](https://github.com/iomete/jupyter-renderer-react) React
|
|
15
|
+
library, so you can render **Jupyter notebooks (`.ipynb`)** in a Reflex app —
|
|
16
|
+
with syntax highlighting, markdown, rich outputs, theming, collapsible cells and
|
|
17
|
+
copy-to-clipboard — in **pure Python**. No HTML, no `iframe`, no hand-written
|
|
18
|
+
JavaScript.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import reflex as rx
|
|
22
|
+
from reflex_jupyter_renderer_react import (
|
|
23
|
+
jupyter_notebook_viewer, Notebook, MarkdownCell, CodeCell, stream_output,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
nb = Notebook(cells=[
|
|
27
|
+
MarkdownCell("# Hello, notebook"),
|
|
28
|
+
CodeCell("print('hi')", execution_count=1, outputs=[stream_output("hi\n")]),
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
def index() -> rx.Component:
|
|
32
|
+
return jupyter_notebook_viewer(notebook=nb.to_dict(), theme="dark")
|
|
33
|
+
|
|
34
|
+
app = rx.App()
|
|
35
|
+
app.add_page(index)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Why a native component?
|
|
39
|
+
|
|
40
|
+
| iframe / pre-rendered HTML | native component (this package) |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Isolated from app state | Wired into Reflex state via events (`on_file_load`, `on_error`, …) |
|
|
43
|
+
| Tied to static files/routes | Reusable, `pip install`-able |
|
|
44
|
+
| Untyped JSON edited by hand | Typed Python builders for notebooks |
|
|
45
|
+
| External hosting / manual embedding | npm dependency resolved by Reflex automatically |
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install reflex-jupyter-renderer-react
|
|
51
|
+
# or, with uv:
|
|
52
|
+
uv add reflex-jupyter-renderer-react
|
|
53
|
+
# or, from this repo (editable):
|
|
54
|
+
uv pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Reflex installs the underlying npm package (`jupyter-renderer-react`) into the
|
|
58
|
+
compiled frontend automatically on the first `reflex run`.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
### Three ways to supply a notebook
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from reflex_jupyter_renderer_react import (
|
|
66
|
+
jupyter_notebook_viewer, JupyterNotebookViewer, Notebook, MarkdownCell, CodeCell,
|
|
67
|
+
load_notebook,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 1) Typed Python builders
|
|
71
|
+
nb = Notebook(cells=[MarkdownCell("# Title"), CodeCell("1 + 1", execution_count=1)])
|
|
72
|
+
jupyter_notebook_viewer(notebook=nb.to_dict())
|
|
73
|
+
|
|
74
|
+
# 2) A raw JSON string
|
|
75
|
+
jupyter_notebook_viewer(notebook='{"cells": [], "nbformat": 4, "nbformat_minor": 4}')
|
|
76
|
+
# or: JupyterNotebookViewer.from_json(json_str)
|
|
77
|
+
|
|
78
|
+
# 3) Load from a URL or from disk
|
|
79
|
+
JupyterNotebookViewer.from_url("https://example.com/notebook.ipynb")
|
|
80
|
+
jupyter_notebook_viewer(notebook=load_notebook("assets/demo-example.ipynb"))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Props
|
|
84
|
+
|
|
85
|
+
| Prop | Type | Default | Description |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| `notebook` | `dict \| str` | required | Parsed dict, JSON string, or `{"filePath": "..."}`. |
|
|
88
|
+
| `theme` | `str \| dict` | `"light"` | `"light"`, `"dark"`, or a custom theme object. |
|
|
89
|
+
| `show_cell_numbers` | `bool` | `True` | Show execution count for code cells. |
|
|
90
|
+
| `show_outputs` | `bool` | `True` | Render cell outputs. |
|
|
91
|
+
| `collapsible` | `bool` | `False` | Allow cells to collapse. |
|
|
92
|
+
| `copyable` | `bool` | `True` | Copy button on code cells. |
|
|
93
|
+
| `class_names` | `dict` | `{}` | Per-part class names (`classNames`). |
|
|
94
|
+
| `styles` | `dict` | `{}` | Per-part inline styles (`styles`). |
|
|
95
|
+
| `fetch_options` | `dict` | `{}` | `{"headers": {...}, "timeout": 10000}` for URL loading. |
|
|
96
|
+
|
|
97
|
+
> Reflex converts `snake_case` props to `camelCase` automatically
|
|
98
|
+
> (`show_cell_numbers` → `showCellNumbers`).
|
|
99
|
+
|
|
100
|
+
### Events → Python
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
class State(rx.State):
|
|
104
|
+
status: str = "idle"
|
|
105
|
+
|
|
106
|
+
@rx.event
|
|
107
|
+
def loaded(self, notebook: dict):
|
|
108
|
+
self.status = f"{len(notebook.get('cells', []))} cells loaded"
|
|
109
|
+
|
|
110
|
+
@rx.event
|
|
111
|
+
def failed(self, message: str):
|
|
112
|
+
self.status = f"error: {message}"
|
|
113
|
+
|
|
114
|
+
JupyterNotebookViewer.from_url(
|
|
115
|
+
"https://example.com/notebook.ipynb",
|
|
116
|
+
fetch_options={"timeout": 10000},
|
|
117
|
+
on_file_load=State.loaded,
|
|
118
|
+
on_file_error=State.failed,
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Event | Payload | Fires when |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `on_file_load` | `notebook: dict` | A notebook is fetched from a `filePath`. |
|
|
125
|
+
| `on_file_error` | `message: str` | File loading fails (network / 404 / timeout). |
|
|
126
|
+
| `on_error` | `message: str` | Parsing / rendering fails. |
|
|
127
|
+
|
|
128
|
+
## Demo app
|
|
129
|
+
|
|
130
|
+
A full demo lives in [`jupyter_renderer_react_demo/`](jupyter_renderer_react_demo)
|
|
131
|
+
and exercises every feature (typed builders, JSON string, URL loading with events,
|
|
132
|
+
custom styles, and live theme/prop toggles).
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv pip install -e .
|
|
136
|
+
cd jupyter_renderer_react_demo
|
|
137
|
+
uv run reflex run
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Building notebooks in Python
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from reflex_jupyter_renderer_react import (
|
|
144
|
+
Notebook, MarkdownCell, CodeCell,
|
|
145
|
+
stream_output, execute_result_output, error_output,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
nb = Notebook(cells=[
|
|
149
|
+
MarkdownCell("# Report"),
|
|
150
|
+
CodeCell("print('hi')", execution_count=1, outputs=[stream_output("hi\n")]),
|
|
151
|
+
CodeCell("2 ** 10", execution_count=2,
|
|
152
|
+
outputs=[execute_result_output({"text/plain": "1024"}, execution_count=2)]),
|
|
153
|
+
CodeCell("1/0", execution_count=3,
|
|
154
|
+
outputs=[error_output("ZeroDivisionError", "division by zero")]),
|
|
155
|
+
])
|
|
156
|
+
data = nb.to_dict()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The data-model layer (`Notebook`, cells, output factories, `load_notebook`) imports
|
|
160
|
+
**without Reflex installed**, so it is usable in plain scripts and unit tests.
|
|
161
|
+
|
|
162
|
+
## Naming note
|
|
163
|
+
|
|
164
|
+
The upstream project has some naming inconsistencies. This wrapper centralizes
|
|
165
|
+
them in overridable constants and defaults to the **public, documented contract**:
|
|
166
|
+
|
|
167
|
+
| Source | Name |
|
|
168
|
+
|---|---|
|
|
169
|
+
| Public README usage | `JupyterNotebookViewer` |
|
|
170
|
+
| `src/index.ts` export | `JupiterNotebookViewer` (sic) |
|
|
171
|
+
| `package.json` name | `@iomete/jupyter-renderer-react` (GitHub registry) |
|
|
172
|
+
|
|
173
|
+
Defaults: `library = "jupyter-renderer-react"`, `tag = "JupyterNotebookViewer"`.
|
|
174
|
+
To retarget the scoped package or the source-spelled tag:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
import reflex_jupyter_renderer_react.jupyter_renderer_react as j
|
|
178
|
+
j.JupyterNotebookViewer.library = "@iomete/jupyter-renderer-react"
|
|
179
|
+
j.JupyterNotebookViewer.tag = "JupiterNotebookViewer"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
You can also pin a version via `LIBRARY_VERSION` in
|
|
183
|
+
`custom_components/reflex_jupyter_renderer_react/jupyter_renderer_react.py`.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
This project follows the Reflex **custom component** workflow and uses **`uv`**.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
uv venv
|
|
191
|
+
uv pip install -e ".[dev]"
|
|
192
|
+
uv run pytest # run the tests
|
|
193
|
+
|
|
194
|
+
# Publish (custom component flow)
|
|
195
|
+
reflex component build # -> dist/
|
|
196
|
+
uv publish # or: twine upload dist/*
|
|
197
|
+
reflex component share # list in the Reflex gallery
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See [`specs/`](specs) for the full Spec-Driven Design documentation: PRD,
|
|
201
|
+
architecture, API, data-model schema, and the phased implementation plan & tasks.
|
|
202
|
+
|
|
203
|
+
## Project structure
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
reflex-jupyter-renderer-react/
|
|
207
|
+
├── custom_components/reflex_jupyter_renderer_react/
|
|
208
|
+
│ ├── __init__.py
|
|
209
|
+
│ ├── jupyter_renderer_react.py # the Reflex component
|
|
210
|
+
│ └── models.py # dependency-free notebook builders
|
|
211
|
+
├── jupyter_renderer_react_demo/ # runnable demo app
|
|
212
|
+
├── tests/ # data-model + component contract tests
|
|
213
|
+
├── specs/ # SDD: prd / technical / api / data-model / plans
|
|
214
|
+
├── pyproject.toml
|
|
215
|
+
├── README.md · CHANGELOG.md · LICENSE
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT © Ernesto Crespo. The upstream `jupyter-renderer-react` library is also MIT.
|
reflex_jupyter_renderer_react-0.1.0/custom_components/reflex_jupyter_renderer_react/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""reflex-jupyter-renderer-react.
|
|
2
|
+
|
|
3
|
+
A native Reflex component that wraps the ``jupyter-renderer-react`` library so you
|
|
4
|
+
can render Jupyter notebooks (``.ipynb``) in a Reflex app — with syntax
|
|
5
|
+
highlighting, markdown, rich outputs, themes and collapsible cells — in pure
|
|
6
|
+
Python.
|
|
7
|
+
|
|
8
|
+
Typical use::
|
|
9
|
+
|
|
10
|
+
import reflex as rx
|
|
11
|
+
from reflex_jupyter_renderer_react import jupyter_notebook_viewer, Notebook, CodeCell, MarkdownCell
|
|
12
|
+
|
|
13
|
+
nb = Notebook(cells=[
|
|
14
|
+
MarkdownCell("# Hello"),
|
|
15
|
+
CodeCell("print('hi')", execution_count=1),
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
def index() -> rx.Component:
|
|
19
|
+
return jupyter_notebook_viewer(notebook=nb.to_dict(), theme="dark")
|
|
20
|
+
|
|
21
|
+
You can also load from a URL::
|
|
22
|
+
|
|
23
|
+
from reflex_jupyter_renderer_react import JupyterNotebookViewer
|
|
24
|
+
|
|
25
|
+
JupyterNotebookViewer.from_url("https://example.com/notebook.ipynb")
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .models import (
|
|
29
|
+
CodeCell,
|
|
30
|
+
MarkdownCell,
|
|
31
|
+
Notebook,
|
|
32
|
+
Output,
|
|
33
|
+
RawCell,
|
|
34
|
+
display_data_output,
|
|
35
|
+
error_output,
|
|
36
|
+
execute_result_output,
|
|
37
|
+
load_notebook,
|
|
38
|
+
notebook_from_url,
|
|
39
|
+
stream_output,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__version__ = "0.1.0"
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Data model (importable without Reflex)
|
|
46
|
+
"Notebook",
|
|
47
|
+
"CodeCell",
|
|
48
|
+
"MarkdownCell",
|
|
49
|
+
"RawCell",
|
|
50
|
+
"Output",
|
|
51
|
+
"stream_output",
|
|
52
|
+
"display_data_output",
|
|
53
|
+
"execute_result_output",
|
|
54
|
+
"error_output",
|
|
55
|
+
"load_notebook",
|
|
56
|
+
"notebook_from_url",
|
|
57
|
+
# Component (requires Reflex)
|
|
58
|
+
"JupyterNotebookViewer",
|
|
59
|
+
"jupyter_notebook_viewer",
|
|
60
|
+
"LIBRARY_NAME",
|
|
61
|
+
"COMPONENT_TAG",
|
|
62
|
+
"PREDEFINED_THEMES",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Keep the data-model layer importable even when Reflex is not installed.
|
|
66
|
+
try:
|
|
67
|
+
from .jupyter_renderer_react import (
|
|
68
|
+
COMPONENT_TAG,
|
|
69
|
+
LIBRARY_NAME,
|
|
70
|
+
PREDEFINED_THEMES,
|
|
71
|
+
JupyterNotebookViewer,
|
|
72
|
+
jupyter_notebook_viewer,
|
|
73
|
+
)
|
|
74
|
+
except ModuleNotFoundError: # pragma: no cover - only without reflex installed
|
|
75
|
+
JupyterNotebookViewer = None # type: ignore[assignment]
|
|
76
|
+
jupyter_notebook_viewer = None # type: ignore[assignment]
|
|
77
|
+
LIBRARY_NAME = "jupyter-renderer-react"
|
|
78
|
+
COMPONENT_TAG = "JupyterNotebookViewer"
|
|
79
|
+
PREDEFINED_THEMES = ("light", "dark")
|