jupyter-python-tutor 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dive4dec
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,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: jupyter-python-tutor
3
+ Version: 0.2.0
4
+ Summary: Python Tutor-style step-by-step code visualization for JupyterLab 4.x and Notebook 7
5
+ Author: dive4dec
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dive4dec/jupyter-python-tutor
8
+ Project-URL: Repository, https://github.com/dive4dec/jupyter-python-tutor
9
+ Project-URL: Issues, https://github.com/dive4dec/jupyter-python-tutor/issues
10
+ Project-URL: Changelog, https://github.com/dive4dec/jupyter-python-tutor/blob/main/CHANGELOG.md
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: ipython>=8.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest; extra == "dev"
17
+ Requires-Dist: build; extra == "dev"
18
+ Requires-Dist: twine; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # jupyter-python-tutor
22
+
23
+ Python Tutor–style step-by-step code visualization directly inside JupyterLab 4.x and Notebook 7 cells.
24
+
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ pip install jupyter-python-tutor
29
+ ```
30
+
31
+ In a Jupyter cell:
32
+
33
+ ```python
34
+ %load_ext jupyter_python_tutor
35
+ ```
36
+
37
+ Then use the `%%tutor` cell magic:
38
+
39
+ ```python
40
+ %%tutor
41
+ x = 3
42
+ y = x + 4
43
+ print(y)
44
+ ```
45
+
46
+ An interactive trace appears below the cell — drag the slider to navigate
47
+ through execution, see variable values change at each line, with SVG arrows
48
+ from frame variables to heap objects (like [Python Tutor](https://pythontutor.com)).
49
+
50
+ ## Features
51
+
52
+ - **Interactive slider navigation** — drag the slider (or use ◀ ▶ buttons / ← → arrow keys) to scrub through execution steps
53
+ - **SVG pointer arrows** — arrows from frame variables to heap objects, like OPT_Mentor / Python Tutor
54
+ - **Single-step view** — only the current step is shown (not all steps stacked)
55
+ - **Call stack visualization** — global frame + local frames with line numbers
56
+ - **Heap objects** — lists, dicts, class instances, functions, and other objects shown as boxes with arrows pointing to them
57
+ - **Resizable columns** — drag the vertical dividers between code / variables / objects to resize; double-click a divider to collapse/expand
58
+ - **Auto-height** — iframe height follows the tallest column content (no fixed height, no internal scroll)
59
+ - **AST-based variable filtering** — only variables used by the traced code are shown (no IPython internals, no contamination from other cells)
60
+ - **stdout capture** — `print()` output is displayed per step
61
+ - **functools.wraps-safe** — functions show their true name (`__code__.co_name`), not the wrapper's `__name__`
62
+ - **Trusted notebook support** — renders fully (with JS, CSS, arrows) in trusted notebooks; `jupyter trust notebook.ipynb` after execution
63
+ - **Works with Notebook 7** — compatible with the new Jupyter Notebook 7 (JupyterLab-based)
64
+ - **Pure Python** — no compiled extensions, works in CPython 3.8+
65
+
66
+ ## Options
67
+
68
+ ```
69
+ %%tutor --max-steps 1000 # Limit trace steps (default: 5000)
70
+ %%tutor --raw # Output trace as JSON instead of HTML
71
+ %%tutor --height 400 # Set minimum widget height (default: 500)
72
+ ```
73
+
74
+ ## Examples
75
+
76
+ ### Loops
77
+
78
+ ```python
79
+ %%tutor
80
+ total = 0
81
+ for i in range(5):
82
+ total += i
83
+ print(f"Sum: {total}")
84
+ ```
85
+
86
+ ### Functions with recursion
87
+
88
+ ```python
89
+ %%tutor
90
+ def factorial(n):
91
+ if n <= 1:
92
+ return 1
93
+ return n * factorial(n - 1)
94
+
95
+ result = factorial(4)
96
+ print(result)
97
+ ```
98
+
99
+ ### Classes and inheritance
100
+
101
+ ```python
102
+ %%tutor
103
+ class Animal:
104
+ def __init__(self, name):
105
+ self.name = name
106
+ def speak(self):
107
+ return f"{self.name} says hello"
108
+
109
+ class Dog(Animal):
110
+ def speak(self):
111
+ return f"{self.name} barks"
112
+
113
+ d = Dog("Rex")
114
+ print(d.speak())
115
+ ```
116
+
117
+ ## How It Works
118
+
119
+ 1. **Tracing**: The `%%tutor` magic compiles the cell code and executes it under
120
+ `sys.settrace()`. At each line/call/return event, a snapshot of all local and
121
+ global variables is captured. AST analysis (`ast.parse`) extracts the set of
122
+ names the traced code touches, so only code-relevant variables are shown
123
+ (filtering out IPython internals and variables from other cells).
124
+
125
+ 2. **Rendering**: The trace steps are rendered as a complete HTML page inside
126
+ an `<iframe srcdoc="...">`. This bypasses JupyterLab 4's HTML sanitizer
127
+ (which strips `<script>`, `<style>`, `<button>`, `<input>`, etc. from
128
+ untrusted output). In trusted notebooks, the iframe renders with full
129
+ CSS + JavaScript — enabling slider navigation, SVG arrows, draggable
130
+ dividers, and auto-height.
131
+
132
+ 3. **Arrows**: SVG paths are computed at render time from the bounding boxes
133
+ of pointer boxes (in frame variables) to heap objects. Arrows redraw on
134
+ slider navigation, column resize, and window resize.
135
+
136
+ 4. **JupyterLab 4 compatibility**: JupyterLab 4's HTML sanitizer (DOMPurify)
137
+ strips JavaScript and external URLs from untrusted notebooks. The iframe
138
+ `srcdoc` approach bypasses this entirely in trusted notebooks. Run
139
+ `jupyter trust your_notebook.ipynb` after executing cells with `%%tutor`
140
+ to ensure the visualization renders correctly.
141
+
142
+ ## Limitations
143
+
144
+ - **Trusted notebooks required** for full rendering (arrows, slider, dividers).
145
+ Untrusted notebooks will show a stripped-down version.
146
+ - `sys.settrace()` adds overhead — very long loops will be slow; use `--max-steps` to limit
147
+ - Threads are not traced (only the main thread)
148
+
149
+ ## License
150
+
151
+ MIT
@@ -0,0 +1,131 @@
1
+ # jupyter-python-tutor
2
+
3
+ Python Tutor–style step-by-step code visualization directly inside JupyterLab 4.x and Notebook 7 cells.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ pip install jupyter-python-tutor
9
+ ```
10
+
11
+ In a Jupyter cell:
12
+
13
+ ```python
14
+ %load_ext jupyter_python_tutor
15
+ ```
16
+
17
+ Then use the `%%tutor` cell magic:
18
+
19
+ ```python
20
+ %%tutor
21
+ x = 3
22
+ y = x + 4
23
+ print(y)
24
+ ```
25
+
26
+ An interactive trace appears below the cell — drag the slider to navigate
27
+ through execution, see variable values change at each line, with SVG arrows
28
+ from frame variables to heap objects (like [Python Tutor](https://pythontutor.com)).
29
+
30
+ ## Features
31
+
32
+ - **Interactive slider navigation** — drag the slider (or use ◀ ▶ buttons / ← → arrow keys) to scrub through execution steps
33
+ - **SVG pointer arrows** — arrows from frame variables to heap objects, like OPT_Mentor / Python Tutor
34
+ - **Single-step view** — only the current step is shown (not all steps stacked)
35
+ - **Call stack visualization** — global frame + local frames with line numbers
36
+ - **Heap objects** — lists, dicts, class instances, functions, and other objects shown as boxes with arrows pointing to them
37
+ - **Resizable columns** — drag the vertical dividers between code / variables / objects to resize; double-click a divider to collapse/expand
38
+ - **Auto-height** — iframe height follows the tallest column content (no fixed height, no internal scroll)
39
+ - **AST-based variable filtering** — only variables used by the traced code are shown (no IPython internals, no contamination from other cells)
40
+ - **stdout capture** — `print()` output is displayed per step
41
+ - **functools.wraps-safe** — functions show their true name (`__code__.co_name`), not the wrapper's `__name__`
42
+ - **Trusted notebook support** — renders fully (with JS, CSS, arrows) in trusted notebooks; `jupyter trust notebook.ipynb` after execution
43
+ - **Works with Notebook 7** — compatible with the new Jupyter Notebook 7 (JupyterLab-based)
44
+ - **Pure Python** — no compiled extensions, works in CPython 3.8+
45
+
46
+ ## Options
47
+
48
+ ```
49
+ %%tutor --max-steps 1000 # Limit trace steps (default: 5000)
50
+ %%tutor --raw # Output trace as JSON instead of HTML
51
+ %%tutor --height 400 # Set minimum widget height (default: 500)
52
+ ```
53
+
54
+ ## Examples
55
+
56
+ ### Loops
57
+
58
+ ```python
59
+ %%tutor
60
+ total = 0
61
+ for i in range(5):
62
+ total += i
63
+ print(f"Sum: {total}")
64
+ ```
65
+
66
+ ### Functions with recursion
67
+
68
+ ```python
69
+ %%tutor
70
+ def factorial(n):
71
+ if n <= 1:
72
+ return 1
73
+ return n * factorial(n - 1)
74
+
75
+ result = factorial(4)
76
+ print(result)
77
+ ```
78
+
79
+ ### Classes and inheritance
80
+
81
+ ```python
82
+ %%tutor
83
+ class Animal:
84
+ def __init__(self, name):
85
+ self.name = name
86
+ def speak(self):
87
+ return f"{self.name} says hello"
88
+
89
+ class Dog(Animal):
90
+ def speak(self):
91
+ return f"{self.name} barks"
92
+
93
+ d = Dog("Rex")
94
+ print(d.speak())
95
+ ```
96
+
97
+ ## How It Works
98
+
99
+ 1. **Tracing**: The `%%tutor` magic compiles the cell code and executes it under
100
+ `sys.settrace()`. At each line/call/return event, a snapshot of all local and
101
+ global variables is captured. AST analysis (`ast.parse`) extracts the set of
102
+ names the traced code touches, so only code-relevant variables are shown
103
+ (filtering out IPython internals and variables from other cells).
104
+
105
+ 2. **Rendering**: The trace steps are rendered as a complete HTML page inside
106
+ an `<iframe srcdoc="...">`. This bypasses JupyterLab 4's HTML sanitizer
107
+ (which strips `<script>`, `<style>`, `<button>`, `<input>`, etc. from
108
+ untrusted output). In trusted notebooks, the iframe renders with full
109
+ CSS + JavaScript — enabling slider navigation, SVG arrows, draggable
110
+ dividers, and auto-height.
111
+
112
+ 3. **Arrows**: SVG paths are computed at render time from the bounding boxes
113
+ of pointer boxes (in frame variables) to heap objects. Arrows redraw on
114
+ slider navigation, column resize, and window resize.
115
+
116
+ 4. **JupyterLab 4 compatibility**: JupyterLab 4's HTML sanitizer (DOMPurify)
117
+ strips JavaScript and external URLs from untrusted notebooks. The iframe
118
+ `srcdoc` approach bypasses this entirely in trusted notebooks. Run
119
+ `jupyter trust your_notebook.ipynb` after executing cells with `%%tutor`
120
+ to ensure the visualization renders correctly.
121
+
122
+ ## Limitations
123
+
124
+ - **Trusted notebooks required** for full rendering (arrows, slider, dividers).
125
+ Untrusted notebooks will show a stripped-down version.
126
+ - `sys.settrace()` adds overhead — very long loops will be slow; use `--max-steps` to limit
127
+ - Threads are not traced (only the main thread)
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,16 @@
1
+ """jupyter-python-tutor: Python Tutor-style visualization for JupyterLab 4.x and Notebook 7.
2
+
3
+ Usage in a Jupyter cell:
4
+
5
+ %%tutor
6
+ x = 3
7
+ y = x + 4
8
+ print(y)
9
+ """
10
+ from .tracer import trace_code
11
+ from .renderer import render_trace
12
+ from .magics import load_ipython_extension
13
+
14
+ __version__ = "0.2.0"
15
+
16
+ __all__ = ["trace_code", "render_trace", "load_ipython_extension"]
@@ -0,0 +1,109 @@
1
+ """IPython cell magic for jupyter-python-tutor.
2
+
3
+ Usage in a Jupyter cell:
4
+
5
+ %%tutor
6
+ x = 3
7
+ y = x + 4
8
+ print(y)
9
+
10
+ Options:
11
+ %%tutor --max-steps 1000 — limit the number of trace steps
12
+ %%tutor --raw — output the trace as JSON instead of HTML
13
+ %%tutor --height 400 — set max-height of the variables panel (px)
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import typing
19
+
20
+ from IPython.core.magic import Magics, magics_class, cell_magic
21
+ from IPython.display import HTML, JSON
22
+
23
+ from .tracer import trace_code
24
+ from .renderer import render_trace
25
+
26
+ __all__ = ["TutorMagics", "load_ipython_extension", "unload_ipython_extension"]
27
+
28
+
29
+ def _make_parser() -> argparse.ArgumentParser:
30
+ parser = argparse.ArgumentParser(
31
+ prog="%%tutor",
32
+ description="Trace Python code and display an interactive visualization.",
33
+ )
34
+ parser.add_argument(
35
+ "--max-steps", type=int, default=5000,
36
+ help="Maximum number of trace steps to capture (default: 5000)",
37
+ )
38
+ parser.add_argument(
39
+ "--raw", action="store_true",
40
+ help="Output trace as JSON instead of interactive HTML",
41
+ )
42
+ parser.add_argument(
43
+ "--height", type=int, default=300,
44
+ help="Max height of the variables panel in pixels (default: 300)",
45
+ )
46
+ return parser
47
+
48
+
49
+ @magics_class
50
+ class TutorMagics(Magics):
51
+ """Register the %%tutor cell magic."""
52
+
53
+ @cell_magic("tutor")
54
+ def tutor(self, line: str, cell: str):
55
+ """Trace the cell code and display an interactive visualization."""
56
+ parser = _make_parser()
57
+ try:
58
+ args = parser.parse_args(line.split() if line else [])
59
+ except SystemExit:
60
+ return
61
+
62
+ # Pass the ACTUAL user namespace (not a copy) so that:
63
+ # 1. Variables/classes/functions from previous cells are available
64
+ # 2. Variables created by the traced code persist in the kernel
65
+ # (just like a normal Jupyter cell)
66
+ # This matches how a regular cell works and how the Jupyter visual
67
+ # debugger operates.
68
+ user_ns = self.shell.user_ns
69
+
70
+ steps = trace_code(
71
+ cell,
72
+ max_steps=args.max_steps,
73
+ user_globals=user_ns,
74
+ )
75
+
76
+ if args.raw:
77
+ return JSON(steps)
78
+ else:
79
+ # render_trace returns an HTML string with an <iframe srcdoc="...">
80
+ # containing full CSS + JS for interactive visualization.
81
+ # The iframe approach bypasses JupyterLab's HTML sanitizer, which
82
+ # strips <style>, <script>, <iframe>, <input>, etc. from untrusted
83
+ # outputs. When the user runs the cell live, the output is trusted
84
+ # and the iframe renders with full interactivity (slider, SVG arrows).
85
+ result = render_trace(steps, source_code=cell, height=args.height)
86
+ import warnings
87
+ with warnings.catch_warnings():
88
+ # Suppress IPython's "Consider using IFrame instead" warning —
89
+ # we intentionally use HTML(iframe srcdoc) because IFrame()
90
+ # requires a URL, not inline HTML.
91
+ warnings.filterwarnings("ignore", message="Consider using IPython.display.IFrame")
92
+ from IPython.display import HTML, display
93
+ display(HTML(result))
94
+
95
+
96
+ def load_ipython_extension(ipython):
97
+ """Load the extension in IPython/Jupyter.
98
+
99
+ Usage::
100
+
101
+ %load_ext jupyter_python_tutor
102
+ """
103
+ ipython.register_magics(TutorMagics)
104
+
105
+
106
+ def unload_ipython_extension(ipython):
107
+ """Unload the extension."""
108
+ if "tutor" in ipython.magics_manager.magics["cell"]:
109
+ ipython.magics_manager.magics["cell"].pop("tutor", None)