Skip to content

Commit b2cb261

Browse files
committed
[roottest] Use nbconvert as a library in nbdiff.py
Backport of root-project/root#20456.
1 parent dd4d456 commit b2cb261

File tree

3 files changed

+102
-95
lines changed

3 files changed

+102
-95
lines changed

python/JupyROOT/importROOT.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@
1414
],
1515
"metadata": {
1616
"kernelspec": {
17-
"display_name": "Python 2",
17+
"display_name": "Python 3",
1818
"language": "python",
19-
"name": "python2"
19+
"name": "python3"
2020
},
2121
"language_info": {
2222
"codemirror_mode": {
2323
"name": "ipython",
24-
"version": 2
24+
"version": 3
2525
},
2626
"file_extension": ".py",
2727
"mimetype": "text/x-python",
2828
"name": "python",
2929
"nbconvert_exporter": "python",
30-
"pygments_lexer": "ipython2",
31-
"version": "2.7.6"
30+
"pygments_lexer": "ipython3",
31+
"version": "3.11.9"
3232
}
3333
},
3434
"nbformat": 4,

python/JupyROOT/nbdiff.py

Lines changed: 92 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,76 @@
22
import json
33
import os
44
import shutil
5-
import subprocess
65
import sys
76
import tempfile
87

9-
nbExtension=".ipynb"
10-
convCmdTmpl = "%s nbconvert " \
11-
"--to notebook " \
12-
"--ExecutePreprocessor.kernel_name=%s " \
13-
"--ExecutePreprocessor.enabled=True " \
14-
"--ExecutePreprocessor.timeout=3600 " \
15-
"--ExecutePreprocessor.startup_timeout=180 " \
16-
"%s " \
17-
"--output %s"
18-
pythonInterpName = 'python3'
19-
20-
rootKernelFileContent = '''{
21-
"language": "c++",
22-
"display_name": "ROOT C++",
23-
"argv": [
24-
"%s",
25-
"-m",
26-
"JupyROOT.kernel.rootkernel",
27-
"-f",
28-
"{connection_file}"
29-
]
30-
}
31-
''' %pythonInterpName
32-
33-
348

359
# Replace the criterion according to which a line shall be skipped
36-
def customLineJunkFilter(line):
10+
def should_keep_line(line):
3711
# Skip the banner and empty lines
38-
junkLines =['Info in <TUnixSystem::ACLiC',
39-
'Info in <TMacOSXSystem::ACLiC',
40-
'FAILED TO establish the default connection to the WindowServer',
41-
'"version": ',
42-
'"pygments_lexer": "ipython',
43-
' "execution_count":',
44-
'libclang_rt.asan-']
45-
for junkLine in junkLines:
46-
if junkLine in line: return False
12+
skip_patterns = [
13+
"Info in <TUnixSystem::ACLiC",
14+
"Info in <TMacOSXSystem::ACLiC",
15+
"FAILED TO establish the default connection to the WindowServer",
16+
'"version": ',
17+
'"pygments_lexer": "ipython',
18+
' "execution_count":',
19+
"libclang_rt.asan-",
20+
]
21+
for pattern in skip_patterns:
22+
if pattern in line:
23+
return False
4724
return True
4825

26+
4927
def removeCellMetadata(lines):
5028
filteredLines = []
5129
discardLine = False
5230
for line in lines:
5331
if ' "metadata": {' in line:
54-
if line.endswith('},' + os.linesep): # empty metadata
32+
if line.endswith("}," + os.linesep): # empty metadata
5533
continue
5634
discardLine = True
5735

5836
if not discardLine:
5937
filteredLines.append(line)
6038

61-
if discardLine and ' },' in line: # end of metadata
39+
if discardLine and " }," in line: # end of metadata
6240
discardLine = False
6341

6442
return filteredLines
6543

44+
6645
def getFilteredLines(fileName):
67-
filteredLines = list(filter(customLineJunkFilter, open(fileName).readlines()))
46+
with open(fileName) as f:
47+
filteredLines = list(filter(should_keep_line, f.readlines()))
6848

6949
# Sometimes the jupyter server adds a new line at the end of the notebook
7050
# and nbconvert does not.
7151
lastLine = filteredLines[-1]
72-
if lastLine[-1] != "\n": filteredLines[-1] += "\n"
52+
if lastLine[-1] != "\n":
53+
filteredLines[-1] += "\n"
7354

7455
# Remove the metadata field of cells (contains specific execution timestamps)
7556
filteredLines = removeCellMetadata(filteredLines)
7657

7758
return filteredLines
7859

60+
7961
# Workaround to support nbconvert versions >= 7.14 . See #14303
8062
def patchForNBConvert714(outNBLines):
8163
newOutNBLines = []
82-
toReplace = ''' "1\\n"\n'''
64+
toReplace = """ "1\\n"\n"""
8365
replacement = [
84-
''' "1"\n''',
85-
''' ]\n''',
86-
''' },\n''',
87-
''' {\n''',
88-
''' "name": "stdout",\n''',
89-
''' "output_type": "stream",\n''',
90-
''' "text": [\n''',
91-
''' "\\n"\n''']
66+
""" "1"\n""",
67+
""" ]\n""",
68+
""" },\n""",
69+
""" {\n""",
70+
""" "name": "stdout",\n""",
71+
""" "output_type": "stream",\n""",
72+
""" "text": [\n""",
73+
""" "\\n"\n""",
74+
]
9275

9376
for line in outNBLines:
9477
if line == toReplace:
@@ -97,7 +80,8 @@ def patchForNBConvert714(outNBLines):
9780
newOutNBLines.append(line)
9881
return newOutNBLines
9982

100-
def compareNotebooks(inNBName,outNBName):
83+
84+
def compareNotebooks(inNBName, outNBName):
10185
inNBLines = getFilteredLines(inNBName)
10286
inNBLines = patchForNBConvert714(inNBLines)
10387
outNBLines = getFilteredLines(outNBName)
@@ -106,9 +90,11 @@ def compareNotebooks(inNBName,outNBName):
10690
for line in difflib.unified_diff(inNBLines, outNBLines, fromfile=inNBName, tofile=outNBName):
10791
areDifferent = True
10892
sys.stdout.write(line)
109-
if areDifferent: print("\n")
93+
if areDifferent:
94+
print("\n")
11095
return areDifferent
11196

97+
11298
def createKernelSpec():
11399
"""Create a root kernel spec with the right python interpreter name
114100
and puts it in a tmp directory. Return the name of such directory."""
@@ -117,12 +103,26 @@ def createKernelSpec():
117103
os.mkdir(kernelsPath)
118104
rootKernelPath = os.path.join(kernelsPath, "root")
119105
os.mkdir(rootKernelPath)
120-
kernel_file = open(os.path.join(rootKernelPath, "kernel.json"), "w")
121-
kernel_file.write(rootKernelFileContent)
122-
kernel_file.close()
106+
with open(os.path.join(rootKernelPath, "kernel.json"), "w") as kernel_file:
107+
kernel_file.write(
108+
"""{
109+
"language": "c++",
110+
"display_name": "ROOT C++",
111+
"argv": [
112+
"%s",
113+
"-m",
114+
"JupyROOT.kernel.rootkernel",
115+
"-f",
116+
"{connection_file}"
117+
]
118+
}
119+
"""
120+
% sys.executable
121+
)
123122

124123
return tmpd
125124

125+
126126
def addEtcToEnvironment(inNBDirName):
127127
"""Add the etc directory of root to the environment under the name of
128128
JUPYTER_PATH in order to pick up the kernel specs.
@@ -132,43 +132,58 @@ def addEtcToEnvironment(inNBDirName):
132132
os.environ["IPYTHONDIR"] = ipythondir
133133
return ipythondir
134134

135-
def getInterpreterName():
136-
"""Find if the 'jupyter' executable is available on the platform. If
137-
yes, return its name else return 'ipython'
138-
"""
139-
ret = subprocess.call("type jupyter",
140-
shell=True,
141-
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142-
return "jupyter" if ret == 0 else "i%s" %pythonInterpName
143135

144136
def getKernelName(inNBName):
145-
nbj = json.load(open(inNBName))
146-
if nbj["metadata"]["kernelspec"]["language"] == "python":
147-
return pythonInterpName
148-
else: # we support only Python and C++
149-
return 'root'
137+
with open(inNBName) as f:
138+
nbj = json.load(f)
139+
return nbj["metadata"]["kernelspec"]["name"]
150140

151141

152-
def canReproduceNotebook(inNBName, kernelName, needsCompare):
142+
def canReproduceNotebook(inNBName, needsCompare):
143+
import nbformat
144+
from nbconvert.preprocessors import ExecutePreprocessor
145+
153146
tmpDir = addEtcToEnvironment(os.path.dirname(inNBName))
154-
outNBName = inNBName.replace(nbExtension,"_out"+nbExtension)
155-
interpName = getInterpreterName()
156-
convCmd = convCmdTmpl %(interpName, kernelName, inNBName, outNBName)
157-
exitStatus = os.system(convCmd) # we use system to inherit the environment in os.environ
158-
shutil.rmtree(tmpDir)
147+
outNBName = inNBName.replace(".ipynb", "_out.ipynb")
148+
149+
# Load input notebook
150+
with open(inNBName, "r", encoding="utf-8") as f:
151+
nb = nbformat.read(f, as_version=4)
152+
153+
# Configure execution
154+
ep = ExecutePreprocessor(
155+
kernel_name=getKernelName(inNBName),
156+
timeout=3600,
157+
startup_timeout=180,
158+
allow_errors=False,
159+
)
160+
161+
# Run the notebook
162+
ep.preprocess(nb, {"metadata": {"path": os.path.dirname(inNBName)}})
163+
164+
# Export executed notebook
165+
with open(outNBName, "w", encoding="utf-8") as f:
166+
nbformat.write(nb, f)
167+
168+
# Compare or return success
159169
if needsCompare:
160-
return compareNotebooks(inNBName,outNBName)
170+
return compareNotebooks(inNBName, outNBName)
161171
else:
162-
return exitStatus
172+
return 0 # success
173+
174+
shutil.rmtree(tmpDir)
175+
163176

164177
def isInputNotebookFileName(filename):
165178
if not filename.endswith(".ipynb"):
166-
print("Notebook files shall have the %s extension" %nbExtension)
179+
print("Notebook files shall have the .ipynb extension")
167180
return False
168181
return True
169182

183+
170184
if __name__ == "__main__":
171185
import sys
186+
172187
needsCompare = True
173188
if len(sys.argv) < 2:
174189
print("Usage: nbdiff.py myNotebook.ipynb [compare_output]")
@@ -180,13 +195,5 @@ def isInputNotebookFileName(filename):
180195
if not isInputNotebookFileName(nbFileName):
181196
sys.exit(1)
182197

183-
try:
184-
# If jupyter is there, ipython is too
185-
import jupyter
186-
except:
187-
raise ImportError("Cannot import jupyter")
188-
189-
kernelName = getKernelName(nbFileName)
190-
191-
retCode = canReproduceNotebook(nbFileName, kernelName, needsCompare)
198+
retCode = canReproduceNotebook(nbFileName, needsCompare)
192199
sys.exit(retCode)

python/JupyROOT/simpleCppMagic.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,21 @@
9090
],
9191
"metadata": {
9292
"kernelspec": {
93-
"display_name": "Python 2",
93+
"display_name": "Python 3",
9494
"language": "python",
95-
"name": "python2"
95+
"name": "python3"
9696
},
9797
"language_info": {
9898
"codemirror_mode": {
9999
"name": "ipython",
100-
"version": 2
100+
"version": 3
101101
},
102102
"file_extension": ".py",
103103
"mimetype": "text/x-python",
104104
"name": "python",
105105
"nbconvert_exporter": "python",
106-
"pygments_lexer": "ipython2",
107-
"version": "2.7.6"
106+
"pygments_lexer": "ipython3",
107+
"version": "3.11.9"
108108
}
109109
},
110110
"nbformat": 4,

0 commit comments

Comments
 (0)