66import sys
77import tempfile
88
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 = '''{
9+ nbExtension = ".ipynb"
10+ convCmdTmpl = (
11+ "%s nbconvert "
12+ "--to notebook "
13+ "--ExecutePreprocessor.kernel_name=%s "
14+ "--ExecutePreprocessor.enabled=True "
15+ "--ExecutePreprocessor.timeout=3600 "
16+ "--ExecutePreprocessor.startup_timeout=180 "
17+ "%s "
18+ "--output %s"
19+ )
20+ pythonInterpName = "python3"
21+
22+ rootKernelFileContent = (
23+ """{
2124 "language": "c++",
2225 "display_name": "ROOT C++",
2326 "argv": [
2831 "{connection_file}"
2932 ]
3033}
31- ''' % pythonInterpName
32-
34+ """
35+ % pythonInterpName
36+ )
3337
3438
3539# Replace the criterion according to which a line shall be skipped
3640def customLineJunkFilter (line ):
3741 # 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-' ]
42+ junkLines = [
43+ "Info in <TUnixSystem::ACLiC" ,
44+ "Info in <TMacOSXSystem::ACLiC" ,
45+ "FAILED TO establish the default connection to the WindowServer" ,
46+ '"version": ' ,
47+ '"pygments_lexer": "ipython' ,
48+ ' "execution_count":' ,
49+ "libclang_rt.asan-" ,
50+ ]
4551 for junkLine in junkLines :
46- if junkLine in line : return False
52+ if junkLine in line :
53+ return False
4754 return True
4855
56+
4957def removeCellMetadata (lines ):
5058 filteredLines = []
5159 discardLine = False
5260 for line in lines :
5361 if ' "metadata": {' in line :
54- if line .endswith ('},' + os .linesep ): # empty metadata
62+ if line .endswith ("}," + os .linesep ): # empty metadata
5563 continue
5664 discardLine = True
5765
5866 if not discardLine :
5967 filteredLines .append (line )
6068
61- if discardLine and ' },' in line : # end of metadata
69+ if discardLine and " }," in line : # end of metadata
6270 discardLine = False
6371
6472 return filteredLines
6573
74+
6675def getFilteredLines (fileName ):
6776 with open (fileName ) as f :
6877 filteredLines = list (filter (customLineJunkFilter , f .readlines ()))
6978
7079 # Sometimes the jupyter server adds a new line at the end of the notebook
7180 # and nbconvert does not.
7281 lastLine = filteredLines [- 1 ]
73- if lastLine [- 1 ] != "\n " : filteredLines [- 1 ] += "\n "
82+ if lastLine [- 1 ] != "\n " :
83+ filteredLines [- 1 ] += "\n "
7484
7585 # Remove the metadata field of cells (contains specific execution timestamps)
7686 filteredLines = removeCellMetadata (filteredLines )
7787
7888 return filteredLines
7989
90+
8091# Workaround to support nbconvert versions >= 7.14 . See #14303
8192def patchForNBConvert714 (outNBLines ):
8293 newOutNBLines = []
83- toReplace = ''' "1\\ n"\n '''
94+ toReplace = """ "1\\ n"\n """
8495 replacement = [
85- ''' "1"\n ''' ,
86- ''' ]\n ''' ,
87- ''' },\n ''' ,
88- ''' {\n ''' ,
89- ''' "name": "stdout",\n ''' ,
90- ''' "output_type": "stream",\n ''' ,
91- ''' "text": [\n ''' ,
92- ''' "\\ n"\n ''' ]
96+ """ "1"\n """ ,
97+ """ ]\n """ ,
98+ """ },\n """ ,
99+ """ {\n """ ,
100+ """ "name": "stdout",\n """ ,
101+ """ "output_type": "stream",\n """ ,
102+ """ "text": [\n """ ,
103+ """ "\\ n"\n """ ,
104+ ]
93105
94106 for line in outNBLines :
95107 if line == toReplace :
@@ -98,7 +110,8 @@ def patchForNBConvert714(outNBLines):
98110 newOutNBLines .append (line )
99111 return newOutNBLines
100112
101- def compareNotebooks (inNBName ,outNBName ):
113+
114+ def compareNotebooks (inNBName , outNBName ):
102115 inNBLines = getFilteredLines (inNBName )
103116 inNBLines = patchForNBConvert714 (inNBLines )
104117 outNBLines = getFilteredLines (outNBName )
@@ -107,9 +120,11 @@ def compareNotebooks(inNBName,outNBName):
107120 for line in difflib .unified_diff (inNBLines , outNBLines , fromfile = inNBName , tofile = outNBName ):
108121 areDifferent = True
109122 sys .stdout .write (line )
110- if areDifferent : print ("\n " )
123+ if areDifferent :
124+ print ("\n " )
111125 return areDifferent
112126
127+
113128def createKernelSpec ():
114129 """Create a root kernel spec with the right python interpreter name
115130 and puts it in a tmp directory. Return the name of such directory."""
@@ -123,6 +138,7 @@ def createKernelSpec():
123138
124139 return tmpd
125140
141+
126142def addEtcToEnvironment (inNBDirName ):
127143 """Add the etc directory of root to the environment under the name of
128144 JUPYTER_PATH in order to pick up the kernel specs.
@@ -132,44 +148,47 @@ def addEtcToEnvironment(inNBDirName):
132148 os .environ ["IPYTHONDIR" ] = ipythondir
133149 return ipythondir
134150
151+
135152def getInterpreterName ():
136153 """Find if the 'jupyter' executable is available on the platform. If
137154 yes, return its name else return 'ipython'
138155 """
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
156+ ret = subprocess .call ("type jupyter" , shell = True , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
157+ return "jupyter" if ret == 0 else "i%s" % pythonInterpName
158+
143159
144160def getKernelName (inNBName ):
145161 with open (inNBName ) as f :
146162 nbj = json .load (f )
147163 if nbj ["metadata" ]["kernelspec" ]["language" ] == "python" :
148164 return pythonInterpName
149- else : # we support only Python and C++
150- return ' root'
165+ else : # we support only Python and C++
166+ return " root"
151167
152168
153169def canReproduceNotebook (inNBName , kernelName , needsCompare ):
154170 tmpDir = addEtcToEnvironment (os .path .dirname (inNBName ))
155- outNBName = inNBName .replace (nbExtension ,"_out" + nbExtension )
171+ outNBName = inNBName .replace (nbExtension , "_out" + nbExtension )
156172 interpName = getInterpreterName ()
157- convCmd = convCmdTmpl % (interpName , kernelName , inNBName , outNBName )
158- exitStatus = os .system (convCmd ) # we use system to inherit the environment in os.environ
173+ convCmd = convCmdTmpl % (interpName , kernelName , inNBName , outNBName )
174+ exitStatus = os .system (convCmd ) # we use system to inherit the environment in os.environ
159175 shutil .rmtree (tmpDir )
160176 if needsCompare :
161- return compareNotebooks (inNBName ,outNBName )
177+ return compareNotebooks (inNBName , outNBName )
162178 else :
163179 return exitStatus
164180
181+
165182def isInputNotebookFileName (filename ):
166183 if not filename .endswith (".ipynb" ):
167- print ("Notebook files shall have the %s extension" % nbExtension )
184+ print ("Notebook files shall have the %s extension" % nbExtension )
168185 return False
169186 return True
170187
188+
171189if __name__ == "__main__" :
172190 import sys
191+
173192 needsCompare = True
174193 if len (sys .argv ) < 2 :
175194 print ("Usage: nbdiff.py myNotebook.ipynb [compare_output]" )
0 commit comments