reflex-jupyter-renderer-react 0.1.0__py3-none-any.whl
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/__init__.py +79 -0
- reflex_jupyter_renderer_react/jupyter_renderer_react.py +175 -0
- reflex_jupyter_renderer_react/jupyter_renderer_react.pyi +120 -0
- reflex_jupyter_renderer_react/models.py +188 -0
- reflex_jupyter_renderer_react-0.1.0.dist-info/METADATA +248 -0
- reflex_jupyter_renderer_react-0.1.0.dist-info/RECORD +9 -0
- reflex_jupyter_renderer_react-0.1.0.dist-info/WHEEL +5 -0
- reflex_jupyter_renderer_react-0.1.0.dist-info/licenses/LICENSE +21 -0
- reflex_jupyter_renderer_react-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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")
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Native Reflex wrapper for the ``jupyter-renderer-react`` library.
|
|
2
|
+
|
|
3
|
+
``jupyter-renderer-react`` (by IOMETE) is a *real* React component library: it
|
|
4
|
+
exports a ``JupyterNotebookViewer`` React component that renders a Jupyter
|
|
5
|
+
notebook (``.ipynb``) with syntax highlighting, markdown, and rich outputs.
|
|
6
|
+
Because it ships an actual React component (and not an imperative class like some
|
|
7
|
+
JS libraries), wrapping it in Reflex is straightforward: declare the ``library``
|
|
8
|
+
and ``tag``, map each prop to an ``rx.Var``, and expose the callbacks as Reflex
|
|
9
|
+
event handlers.
|
|
10
|
+
|
|
11
|
+
The viewer accepts a notebook in three interchangeable shapes:
|
|
12
|
+
|
|
13
|
+
1. A parsed notebook object (a Python ``dict`` following the nbformat schema).
|
|
14
|
+
2. A JSON string of that object.
|
|
15
|
+
3. A remote/local file reference: ``{"filePath": "https://.../nb.ipynb"}``.
|
|
16
|
+
|
|
17
|
+
This module keeps the npm package name and component tag in module-level
|
|
18
|
+
constants so they can be overridden in one place if the upstream package is
|
|
19
|
+
published under a different name/registry (see the README "Naming" note).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, Union
|
|
25
|
+
|
|
26
|
+
import reflex as rx
|
|
27
|
+
from reflex.vars.object import ObjectVar
|
|
28
|
+
|
|
29
|
+
__all__ = ["JupyterNotebookViewer", "jupyter_notebook_viewer", "LIBRARY_NAME"]
|
|
30
|
+
|
|
31
|
+
# --------------------------------------------------------------------------- #
|
|
32
|
+
# Package coordinates
|
|
33
|
+
# --------------------------------------------------------------------------- #
|
|
34
|
+
# The public README documents `npm install jupyter-renderer-react` and a
|
|
35
|
+
# `JupyterNotebookViewer` named export. The repository's package.json publishes a
|
|
36
|
+
# scoped name (`@iomete/jupyter-renderer-react`) to the GitHub registry, and the
|
|
37
|
+
# source `index.ts` exports the symbol as `JupiterNotebookViewer` (sic). We default
|
|
38
|
+
# to the public, documented contract and centralize both so a consumer can pin a
|
|
39
|
+
# version or switch to the scoped package without touching the class body.
|
|
40
|
+
LIBRARY_NAME = "@iomete/jupyter-renderer-react"
|
|
41
|
+
LIBRARY_VERSION = "1.0.1" # empty string installs the latest published version.
|
|
42
|
+
LIBRARY = f"{LIBRARY_NAME}@{LIBRARY_VERSION}" if LIBRARY_VERSION else LIBRARY_NAME
|
|
43
|
+
|
|
44
|
+
# Named export rendered as the React tag. The published package (@iomete) exports
|
|
45
|
+
# the symbol as `JupiterNotebookViewer` (sic — "Jupiter", not "Jupyter").
|
|
46
|
+
COMPONENT_TAG = "JupiterNotebookViewer"
|
|
47
|
+
|
|
48
|
+
# Stylesheet shipped by the package (package.json `exports` maps `./dist/index.css`).
|
|
49
|
+
CSS_IMPORT = f"{LIBRARY_NAME}/dist/index.css"
|
|
50
|
+
|
|
51
|
+
# Themes recognized by the upstream `theme` prop when passed as a string.
|
|
52
|
+
PREDEFINED_THEMES = ("light", "dark")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _error_event_spec(error: ObjectVar) -> tuple[rx.Var[str]]:
|
|
56
|
+
"""Map a JS ``Error`` object to its serializable ``message`` string.
|
|
57
|
+
|
|
58
|
+
The upstream ``onError``/``onFileError`` callbacks receive a JS ``Error``
|
|
59
|
+
instance, which is not JSON-serializable. We forward only ``error.message``
|
|
60
|
+
so the Reflex event handler receives a plain ``str``.
|
|
61
|
+
"""
|
|
62
|
+
return (error.message.to(str),)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _notebook_event_spec(notebook: ObjectVar) -> tuple[rx.Var[dict]]:
|
|
66
|
+
"""Pass the loaded notebook object through to the backend as a dict."""
|
|
67
|
+
return (notebook.to(dict),)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class JupyterNotebookViewer(rx.Component):
|
|
71
|
+
"""Render a Jupyter notebook with the ``jupyter-renderer-react`` viewer."""
|
|
72
|
+
|
|
73
|
+
library = LIBRARY
|
|
74
|
+
tag = COMPONENT_TAG
|
|
75
|
+
# Named export (not `export default`), so disable default-import behavior.
|
|
76
|
+
is_default = False
|
|
77
|
+
|
|
78
|
+
# ---- Data ------------------------------------------------------------- #
|
|
79
|
+
# The notebook to render. Accepts a parsed object (dict), a JSON string, or a
|
|
80
|
+
# file reference dict such as {"filePath": "https://.../notebook.ipynb"}.
|
|
81
|
+
notebook: rx.Var[Union[dict, str]]
|
|
82
|
+
|
|
83
|
+
# ---- Presentation ----------------------------------------------------- #
|
|
84
|
+
# "light" | "dark" | a predefined theme name | a custom theme object (dict).
|
|
85
|
+
theme: rx.Var[Union[str, dict]]
|
|
86
|
+
|
|
87
|
+
# Show the execution count (e.g. `[1]`) next to code cells.
|
|
88
|
+
show_cell_numbers: rx.Var[bool]
|
|
89
|
+
|
|
90
|
+
# Render cell outputs (stream, display_data, execute_result, error).
|
|
91
|
+
show_outputs: rx.Var[bool]
|
|
92
|
+
|
|
93
|
+
# Allow cells to be collapsed/expanded.
|
|
94
|
+
collapsible: rx.Var[bool]
|
|
95
|
+
|
|
96
|
+
# Show a "copy to clipboard" button on code cells.
|
|
97
|
+
copyable: rx.Var[bool]
|
|
98
|
+
|
|
99
|
+
# Per-part class name overrides (maps to the `classNames` prop).
|
|
100
|
+
class_names: rx.Var[dict]
|
|
101
|
+
|
|
102
|
+
# Per-part inline style overrides (maps to the `styles` prop).
|
|
103
|
+
styles: rx.Var[dict]
|
|
104
|
+
|
|
105
|
+
# ---- File loading ----------------------------------------------------- #
|
|
106
|
+
# Fetch options used when `notebook` is a {"filePath": ...} reference, e.g.
|
|
107
|
+
# {"headers": {"Authorization": "Bearer ..."}, "timeout": 10000}.
|
|
108
|
+
fetch_options: rx.Var[dict]
|
|
109
|
+
|
|
110
|
+
# ---- Callbacks / events ---------------------------------------------- #
|
|
111
|
+
# Fired after a notebook is successfully loaded from a file path. The handler
|
|
112
|
+
# receives the parsed notebook object.
|
|
113
|
+
on_file_load: rx.EventHandler[_notebook_event_spec]
|
|
114
|
+
|
|
115
|
+
# Fired when loading a notebook from a file path fails (network/404/timeout).
|
|
116
|
+
on_file_error: rx.EventHandler[_error_event_spec]
|
|
117
|
+
|
|
118
|
+
# Fired when parsing/rendering a notebook fails.
|
|
119
|
+
on_error: rx.EventHandler[_error_event_spec]
|
|
120
|
+
|
|
121
|
+
def add_imports(self) -> dict[str, Any]:
|
|
122
|
+
"""Side-effect import of the package stylesheet.
|
|
123
|
+
|
|
124
|
+
The empty key tells Reflex this is a bare ``import "..."`` for CSS; the
|
|
125
|
+
component's own ``library``/``tag`` import is added automatically.
|
|
126
|
+
"""
|
|
127
|
+
return {"": CSS_IMPORT}
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def create( # type: ignore[override]
|
|
131
|
+
cls,
|
|
132
|
+
*children: Any,
|
|
133
|
+
notebook: Union[dict, str, None] = None,
|
|
134
|
+
**props: Any,
|
|
135
|
+
) -> "JupyterNotebookViewer":
|
|
136
|
+
"""Create a notebook viewer.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
notebook: A parsed notebook ``dict`` (nbformat), a JSON ``str`` of the
|
|
140
|
+
same, or a file reference like ``{"filePath": "https://..."}``.
|
|
141
|
+
Use :func:`from_url` / :func:`from_json` for explicit intent.
|
|
142
|
+
**props: Standard Reflex props plus any of the typed props above
|
|
143
|
+
(``theme``, ``show_cell_numbers``, ``copyable``, event handlers...).
|
|
144
|
+
"""
|
|
145
|
+
if notebook is not None:
|
|
146
|
+
props["notebook"] = notebook
|
|
147
|
+
return super().create(*children, **props)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_url(
|
|
151
|
+
cls,
|
|
152
|
+
url: str,
|
|
153
|
+
*,
|
|
154
|
+
fetch_options: dict | None = None,
|
|
155
|
+
**props: Any,
|
|
156
|
+
) -> "JupyterNotebookViewer":
|
|
157
|
+
"""Create a viewer that lazily fetches a ``.ipynb`` from ``url``.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
url: HTTP(S) URL (or served path) of the notebook file.
|
|
161
|
+
fetch_options: Optional fetch options (``headers``, ``timeout``).
|
|
162
|
+
**props: Other component props / event handlers.
|
|
163
|
+
"""
|
|
164
|
+
if fetch_options is not None:
|
|
165
|
+
props["fetch_options"] = fetch_options
|
|
166
|
+
return cls.create(notebook={"filePath": url}, **props)
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_json(cls, raw: str, **props: Any) -> "JupyterNotebookViewer":
|
|
170
|
+
"""Create a viewer from a raw notebook JSON ``str``."""
|
|
171
|
+
return cls.create(notebook=raw, **props)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Convenience alias matching the Reflex `rx.<component>` calling style.
|
|
175
|
+
jupyter_notebook_viewer = JupyterNotebookViewer.create
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Stub file for custom_components/reflex_jupyter_renderer_react/jupyter_renderer_react.py"""
|
|
2
|
+
|
|
3
|
+
# ------------------- DO NOT EDIT ----------------------
|
|
4
|
+
# This file was generated by `reflex/utils/pyi_generator.py`!
|
|
5
|
+
# ------------------------------------------------------
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
from reflex_components_core.core.breakpoints import Breakpoints
|
|
9
|
+
from reflex_base.event import (
|
|
10
|
+
EventType,
|
|
11
|
+
PointerEventInfo,
|
|
12
|
+
)
|
|
13
|
+
from reflex_base.vars.base import Var
|
|
14
|
+
import reflex as rx
|
|
15
|
+
|
|
16
|
+
__all__ = ["JupyterNotebookViewer", "jupyter_notebook_viewer", "LIBRARY_NAME"]
|
|
17
|
+
LIBRARY_NAME = "@iomete/jupyter-renderer-react"
|
|
18
|
+
LIBRARY_VERSION = "1.0.1"
|
|
19
|
+
LIBRARY = f"{LIBRARY_NAME}@{LIBRARY_VERSION}" if LIBRARY_VERSION else LIBRARY_NAME
|
|
20
|
+
COMPONENT_TAG = "JupiterNotebookViewer"
|
|
21
|
+
CSS_IMPORT = f"{LIBRARY_NAME}/dist/index.css"
|
|
22
|
+
PREDEFINED_THEMES = ("light", "dark")
|
|
23
|
+
|
|
24
|
+
class JupyterNotebookViewer(rx.Component):
|
|
25
|
+
def add_imports(self) -> dict[str, Any]: ...
|
|
26
|
+
@classmethod
|
|
27
|
+
def create(
|
|
28
|
+
cls,
|
|
29
|
+
*children,
|
|
30
|
+
notebook: dict | str | None = None,
|
|
31
|
+
theme: Var[dict | str] | dict | str | None = None,
|
|
32
|
+
show_cell_numbers: Var[bool] | bool | None = None,
|
|
33
|
+
show_outputs: Var[bool] | bool | None = None,
|
|
34
|
+
collapsible: Var[bool] | bool | None = None,
|
|
35
|
+
copyable: Var[bool] | bool | None = None,
|
|
36
|
+
class_names: Var[dict] | dict | None = None,
|
|
37
|
+
styles: Var[dict] | dict | None = None,
|
|
38
|
+
fetch_options: Var[dict] | dict | None = None,
|
|
39
|
+
style: Sequence[Mapping[str, Any]]
|
|
40
|
+
| Mapping[str, Any]
|
|
41
|
+
| Var[Mapping[str, Any]]
|
|
42
|
+
| Breakpoints
|
|
43
|
+
| None = None,
|
|
44
|
+
key: Any | None = None,
|
|
45
|
+
id: Any | None = None,
|
|
46
|
+
ref: Var | None = None,
|
|
47
|
+
class_name: Any | None = None,
|
|
48
|
+
custom_attrs: dict[str, Any | Var] | None = None,
|
|
49
|
+
on_blur: Optional[EventType[()]] = None,
|
|
50
|
+
on_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
51
|
+
on_context_menu: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
52
|
+
on_double_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
53
|
+
on_error: Optional[EventType[()] | EventType[rx.Var[str]]] = None,
|
|
54
|
+
on_file_error: Optional[EventType[()] | EventType[rx.Var[str]]] = None,
|
|
55
|
+
on_file_load: Optional[EventType[()] | EventType[rx.Var[dict]]] = None,
|
|
56
|
+
on_focus: Optional[EventType[()]] = None,
|
|
57
|
+
on_mount: Optional[EventType[()]] = None,
|
|
58
|
+
on_mouse_down: Optional[EventType[()]] = None,
|
|
59
|
+
on_mouse_enter: Optional[EventType[()]] = None,
|
|
60
|
+
on_mouse_leave: Optional[EventType[()]] = None,
|
|
61
|
+
on_mouse_move: Optional[EventType[()]] = None,
|
|
62
|
+
on_mouse_out: Optional[EventType[()]] = None,
|
|
63
|
+
on_mouse_over: Optional[EventType[()]] = None,
|
|
64
|
+
on_mouse_up: Optional[EventType[()]] = None,
|
|
65
|
+
on_scroll: Optional[EventType[()]] = None,
|
|
66
|
+
on_scroll_end: Optional[EventType[()]] = None,
|
|
67
|
+
on_unmount: Optional[EventType[()]] = None,
|
|
68
|
+
**props,
|
|
69
|
+
) -> "JupyterNotebookViewer":
|
|
70
|
+
"""Create a notebook viewer.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
notebook: A parsed notebook ``dict`` (nbformat), a JSON ``str`` of the
|
|
74
|
+
same, or a file reference like ``{"filePath": "https://..."}``.
|
|
75
|
+
Use :func:`from_url` / :func:`from_json` for explicit intent.
|
|
76
|
+
theme: ---- Presentation ----------------------------------------------------- # "light" | "dark" | a predefined theme name | a custom theme object (dict).
|
|
77
|
+
show_cell_numbers: Show the execution count (e.g. `[1]`) next to code cells.
|
|
78
|
+
show_outputs: Render cell outputs (stream, display_data, execute_result, error).
|
|
79
|
+
collapsible: Allow cells to be collapsed/expanded.
|
|
80
|
+
copyable: Show a "copy to clipboard" button on code cells.
|
|
81
|
+
class_names: Per-part class name overrides (maps to the `classNames` prop).
|
|
82
|
+
styles: Per-part inline style overrides (maps to the `styles` prop).
|
|
83
|
+
fetch_options: ---- File loading ----------------------------------------------------- # Fetch options used when `notebook` is a {"filePath": ...} reference, e.g. {"headers": {"Authorization": "Bearer ..."}, "timeout": 10000}.
|
|
84
|
+
style: The style of the component.
|
|
85
|
+
key: A unique key for the component.
|
|
86
|
+
id: The id for the component.
|
|
87
|
+
ref: The Var to pass as the ref to the component.
|
|
88
|
+
class_name: The class name for the component.
|
|
89
|
+
custom_attrs: Attributes passed directly to the component.
|
|
90
|
+
on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input.
|
|
91
|
+
on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input.
|
|
92
|
+
on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button.
|
|
93
|
+
on_context_menu: Fired when the user right-clicks on an element.
|
|
94
|
+
on_double_click: Fired when the user double-clicks on an element.
|
|
95
|
+
on_mouse_down: Fired when the user presses a mouse button on an element.
|
|
96
|
+
on_mouse_enter: Fired when the mouse pointer enters the element.
|
|
97
|
+
on_mouse_leave: Fired when the mouse pointer leaves the element.
|
|
98
|
+
on_mouse_move: Fired when the mouse pointer moves over the element.
|
|
99
|
+
on_mouse_out: Fired when the mouse pointer moves out of the element.
|
|
100
|
+
on_mouse_over: Fired when the mouse pointer moves onto the element.
|
|
101
|
+
on_mouse_up: Fired when the user releases a mouse button on an element.
|
|
102
|
+
on_scroll: Fired when the user scrolls the element.
|
|
103
|
+
on_scroll_end: Fired when scrolling ends on the element.
|
|
104
|
+
on_mount: Fired when the component is mounted to the page.
|
|
105
|
+
on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh.
|
|
106
|
+
on_file_load: ---- Callbacks / events ---------------------------------------------- # Fired after a notebook is successfully loaded from a file path. The handler receives the parsed notebook object.
|
|
107
|
+
on_file_error: Fired when loading a notebook from a file path fails (network/404/timeout).
|
|
108
|
+
on_error: Fired when parsing/rendering a notebook fails.
|
|
109
|
+
**props: Standard Reflex props plus any of the typed props above
|
|
110
|
+
(``theme``, ``show_cell_numbers``, ``copyable``, event handlers...)."""
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_url(
|
|
115
|
+
cls, url: str, *, fetch_options: dict | None = None, **props: Any
|
|
116
|
+
) -> "JupyterNotebookViewer": ...
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_json(cls, raw: str, **props: Any) -> "JupyterNotebookViewer": ...
|
|
119
|
+
|
|
120
|
+
jupyter_notebook_viewer = JupyterNotebookViewer.create
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Typed, dependency-free builders for the Jupyter ``nbformat`` notebook shape.
|
|
2
|
+
|
|
3
|
+
These helpers let you construct a notebook (or load one from disk) in pure Python
|
|
4
|
+
and hand the resulting ``dict`` to :class:`JupyterNotebookViewer`. They mirror the
|
|
5
|
+
relevant subset of the `nbformat v4 schema
|
|
6
|
+
<https://nbformat.readthedocs.io/en/latest/format_description.html>`_ that the
|
|
7
|
+
``jupyter-renderer-react`` viewer understands.
|
|
8
|
+
|
|
9
|
+
This module deliberately imports nothing from Reflex so the data layer stays
|
|
10
|
+
usable for tooling and unit tests even when Reflex is not installed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Union
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Output",
|
|
22
|
+
"stream_output",
|
|
23
|
+
"display_data_output",
|
|
24
|
+
"execute_result_output",
|
|
25
|
+
"error_output",
|
|
26
|
+
"CodeCell",
|
|
27
|
+
"MarkdownCell",
|
|
28
|
+
"RawCell",
|
|
29
|
+
"Notebook",
|
|
30
|
+
"load_notebook",
|
|
31
|
+
"notebook_from_url",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# A source can be a single string or a list of lines (both are valid nbformat).
|
|
35
|
+
Source = Union[str, list]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _norm_source(source: Source) -> Union[str, list]:
|
|
39
|
+
"""Return source as-is (string or list of lines), both accepted by nbformat."""
|
|
40
|
+
return source
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# --------------------------------------------------------------------------- #
|
|
44
|
+
# Outputs
|
|
45
|
+
# --------------------------------------------------------------------------- #
|
|
46
|
+
@dataclass
|
|
47
|
+
class Output:
|
|
48
|
+
"""A raw cell output already shaped as an nbformat output dict."""
|
|
49
|
+
|
|
50
|
+
data: dict
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict:
|
|
53
|
+
return dict(self.data)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def stream_output(text: Source, name: str = "stdout") -> dict:
|
|
57
|
+
"""A ``stream`` output (stdout/stderr text)."""
|
|
58
|
+
return {"output_type": "stream", "name": name, "text": _norm_source(text)}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def display_data_output(data: dict, metadata: dict | None = None) -> dict:
|
|
62
|
+
"""A ``display_data`` output, e.g. ``{"image/png": "<base64>"}``."""
|
|
63
|
+
return {
|
|
64
|
+
"output_type": "display_data",
|
|
65
|
+
"data": data,
|
|
66
|
+
"metadata": metadata or {},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def execute_result_output(
|
|
71
|
+
data: dict, execution_count: int | None = None, metadata: dict | None = None
|
|
72
|
+
) -> dict:
|
|
73
|
+
"""An ``execute_result`` output (the value of the last expression)."""
|
|
74
|
+
return {
|
|
75
|
+
"output_type": "execute_result",
|
|
76
|
+
"data": data,
|
|
77
|
+
"execution_count": execution_count,
|
|
78
|
+
"metadata": metadata or {},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def error_output(ename: str, evalue: str, traceback: list[str] | None = None) -> dict:
|
|
83
|
+
"""An ``error`` output (an exception raised by a code cell)."""
|
|
84
|
+
return {
|
|
85
|
+
"output_type": "error",
|
|
86
|
+
"ename": ename,
|
|
87
|
+
"evalue": evalue,
|
|
88
|
+
"traceback": traceback or [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# --------------------------------------------------------------------------- #
|
|
93
|
+
# Cells
|
|
94
|
+
# --------------------------------------------------------------------------- #
|
|
95
|
+
@dataclass
|
|
96
|
+
class CodeCell:
|
|
97
|
+
"""A code cell with optional outputs."""
|
|
98
|
+
|
|
99
|
+
source: Source
|
|
100
|
+
execution_count: int | None = None
|
|
101
|
+
outputs: list[dict] = field(default_factory=list)
|
|
102
|
+
metadata: dict = field(default_factory=dict)
|
|
103
|
+
|
|
104
|
+
def to_dict(self) -> dict:
|
|
105
|
+
return {
|
|
106
|
+
"cell_type": "code",
|
|
107
|
+
"execution_count": self.execution_count,
|
|
108
|
+
"metadata": dict(self.metadata),
|
|
109
|
+
"source": _norm_source(self.source),
|
|
110
|
+
"outputs": [dict(o) for o in self.outputs],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class MarkdownCell:
|
|
116
|
+
"""A markdown cell."""
|
|
117
|
+
|
|
118
|
+
source: Source
|
|
119
|
+
metadata: dict = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict:
|
|
122
|
+
return {
|
|
123
|
+
"cell_type": "markdown",
|
|
124
|
+
"metadata": dict(self.metadata),
|
|
125
|
+
"source": _norm_source(self.source),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class RawCell:
|
|
131
|
+
"""A raw cell (rendered verbatim)."""
|
|
132
|
+
|
|
133
|
+
source: Source
|
|
134
|
+
metadata: dict = field(default_factory=dict)
|
|
135
|
+
|
|
136
|
+
def to_dict(self) -> dict:
|
|
137
|
+
return {
|
|
138
|
+
"cell_type": "raw",
|
|
139
|
+
"metadata": dict(self.metadata),
|
|
140
|
+
"source": _norm_source(self.source),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
Cell = Union[CodeCell, MarkdownCell, RawCell]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# --------------------------------------------------------------------------- #
|
|
148
|
+
# Notebook
|
|
149
|
+
# --------------------------------------------------------------------------- #
|
|
150
|
+
@dataclass
|
|
151
|
+
class Notebook:
|
|
152
|
+
"""A minimal nbformat v4 notebook builder."""
|
|
153
|
+
|
|
154
|
+
cells: list[Cell] = field(default_factory=list)
|
|
155
|
+
metadata: dict = field(default_factory=dict)
|
|
156
|
+
nbformat: int = 4
|
|
157
|
+
nbformat_minor: int = 4
|
|
158
|
+
|
|
159
|
+
def add(self, cell: Cell) -> "Notebook":
|
|
160
|
+
"""Append a cell and return ``self`` for chaining."""
|
|
161
|
+
self.cells.append(cell)
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def to_dict(self) -> dict:
|
|
165
|
+
return {
|
|
166
|
+
"cells": [c.to_dict() for c in self.cells],
|
|
167
|
+
"metadata": dict(self.metadata),
|
|
168
|
+
"nbformat": self.nbformat,
|
|
169
|
+
"nbformat_minor": self.nbformat_minor,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def to_json(self, *, indent: int | None = None) -> str:
|
|
173
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def load_notebook(path: Union[str, Path]) -> dict:
|
|
177
|
+
"""Read a ``.ipynb`` file from disk and return its parsed ``dict``."""
|
|
178
|
+
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def notebook_from_url(url: str, **fetch_options: Any) -> dict:
|
|
182
|
+
"""Build the ``{"filePath": url}`` reference understood by the viewer.
|
|
183
|
+
|
|
184
|
+
Note: ``fetch_options`` are accepted for symmetry but should be passed to the
|
|
185
|
+
component's ``fetch_options`` prop, not embedded in the notebook reference.
|
|
186
|
+
"""
|
|
187
|
+
ref: dict[str, Any] = {"filePath": url}
|
|
188
|
+
return ref
|
|
@@ -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,9 @@
|
|
|
1
|
+
reflex_jupyter_renderer_react/__init__.py,sha256=ZRgk0_6LRFpB58H2SxRfXjkjDxS9Vy3hxvUVdFLWhDU,2123
|
|
2
|
+
reflex_jupyter_renderer_react/jupyter_renderer_react.py,sha256=qyURfGUmzQjFmiI5E-b8xM9EOXQuJtUe-OznJBxYa_Y,7285
|
|
3
|
+
reflex_jupyter_renderer_react/jupyter_renderer_react.pyi,sha256=n48503Ute65FmKsSaewHwDBg3cDan1Dn21WPB5FyA2c,6989
|
|
4
|
+
reflex_jupyter_renderer_react/models.py,sha256=s30IyNZ3nJu4kgufX_jWmYai9TH6LBwqwpfZiexjDls,5558
|
|
5
|
+
reflex_jupyter_renderer_react-0.1.0.dist-info/licenses/LICENSE,sha256=ikDZVzB6LJXytfIXkllDZj9ningVU9BduOAcL8jLKYI,1071
|
|
6
|
+
reflex_jupyter_renderer_react-0.1.0.dist-info/METADATA,sha256=kA1HhYAnVflUkPc66DBP3mPJF7NMKobTHy4tMM96hJs,9424
|
|
7
|
+
reflex_jupyter_renderer_react-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
reflex_jupyter_renderer_react-0.1.0.dist-info/top_level.txt,sha256=-2CTb1FYe7rQMBxmX9w38zKg3_PFVTk70dHMb0EWhfc,30
|
|
9
|
+
reflex_jupyter_renderer_react-0.1.0.dist-info/RECORD,,
|
|
@@ -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 @@
|
|
|
1
|
+
reflex_jupyter_renderer_react
|