jupyter-python-tutor 0.2.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.
- jupyter_python_tutor/__init__.py +16 -0
- jupyter_python_tutor/magics.py +109 -0
- jupyter_python_tutor/renderer.py +837 -0
- jupyter_python_tutor/tracer.py +591 -0
- jupyter_python_tutor-0.2.0.dist-info/METADATA +151 -0
- jupyter_python_tutor-0.2.0.dist-info/RECORD +9 -0
- jupyter_python_tutor-0.2.0.dist-info/WHEEL +5 -0
- jupyter_python_tutor-0.2.0.dist-info/licenses/LICENSE +21 -0
- jupyter_python_tutor-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -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)
|