test_nbconvertapp.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. # -*- coding: utf-8 -*-
  2. """Test NbConvertApp"""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. import os
  6. import io
  7. from .base import TestsBase
  8. from ..postprocessors import PostProcessorBase
  9. from ..tests.utils import onlyif_cmds_exist
  10. from nbconvert import nbconvertapp
  11. from nbconvert.exporters import Exporter
  12. from traitlets.tests.utils import check_help_all_output
  13. from testpath import tempdir
  14. import pytest
  15. #-----------------------------------------------------------------------------
  16. # Classes and functions
  17. #-----------------------------------------------------------------------------
  18. class DummyPost(PostProcessorBase):
  19. def postprocess(self, filename):
  20. print("Dummy:%s" % filename)
  21. class TestNbConvertApp(TestsBase):
  22. """Collection of NbConvertApp tests"""
  23. def test_notebook_help(self):
  24. """Will help show if no notebooks are specified?"""
  25. with self.create_temp_cwd():
  26. out, err = self.nbconvert('--log-level 0', ignore_return_code=True)
  27. self.assertIn("--help-all", out)
  28. def test_help_output(self):
  29. """ipython nbconvert --help-all works"""
  30. check_help_all_output('nbconvert')
  31. def test_glob(self):
  32. """
  33. Do search patterns work for notebook names?
  34. """
  35. with self.create_temp_cwd(['notebook*.ipynb']):
  36. self.nbconvert('--to python *.ipynb --log-level 0')
  37. assert os.path.isfile('notebook1.py')
  38. assert os.path.isfile('notebook2.py')
  39. def test_glob_subdir(self):
  40. """
  41. Do search patterns work for subdirectory notebook names?
  42. """
  43. with self.create_temp_cwd():
  44. self.copy_files_to(['notebook*.ipynb'], 'subdir/')
  45. self.nbconvert('--to python --log-level 0 ' +
  46. os.path.join('subdir', '*.ipynb'))
  47. assert os.path.isfile(os.path.join('subdir', 'notebook1.py'))
  48. assert os.path.isfile(os.path.join('subdir', 'notebook2.py'))
  49. def test_build_dir(self):
  50. """build_directory affects export location"""
  51. with self.create_temp_cwd():
  52. self.copy_files_to(['notebook*.ipynb'], 'subdir/')
  53. self.nbconvert('--to python --log-level 0 --output-dir . ' +
  54. os.path.join('subdir', '*.ipynb'))
  55. assert os.path.isfile('notebook1.py')
  56. assert os.path.isfile('notebook2.py')
  57. def test_convert_full_qualified_name(self):
  58. """
  59. Test that nbconvert can convert file using a full qualified name for a
  60. package, import and use it.
  61. """
  62. with self.create_temp_cwd():
  63. self.copy_files_to(['notebook*.ipynb'], 'subdir')
  64. self.nbconvert('--to nbconvert.tests.fake_exporters.MyExporter --log-level 0 ' +
  65. os.path.join('subdir', '*.ipynb'))
  66. assert os.path.isfile(os.path.join('subdir', 'notebook1.test_ext'))
  67. assert os.path.isfile(os.path.join('subdir', 'notebook2.test_ext'))
  68. def test_explicit(self):
  69. """
  70. Do explicit notebook names work?
  71. """
  72. with self.create_temp_cwd(['notebook*.ipynb']):
  73. self.nbconvert('--log-level 0 --to python notebook2')
  74. assert not os.path.isfile('notebook1.py')
  75. assert os.path.isfile('notebook2.py')
  76. def test_absolute_template_file(self):
  77. """--template '/path/to/template.tpl'"""
  78. with self.create_temp_cwd(['notebook*.ipynb']), tempdir.TemporaryDirectory() as td:
  79. template = os.path.join(td, 'mytemplate.tpl')
  80. test_output = 'success!'
  81. with open(template, 'w') as f:
  82. f.write(test_output)
  83. self.nbconvert('--log-level 0 notebook2 --template %s' % template)
  84. assert os.path.isfile('notebook2.html')
  85. with open('notebook2.html') as f:
  86. text = f.read()
  87. assert text == test_output
  88. def test_relative_template_file(self):
  89. """Test --template 'relative/path.tpl'"""
  90. with self.create_temp_cwd(['notebook*.ipynb']):
  91. os.mkdir('relative')
  92. template = os.path.join('relative', 'path.tpl')
  93. test_output = 'success!'
  94. with open(template, 'w') as f:
  95. f.write(test_output)
  96. self.nbconvert('--log-level 0 notebook2 --template %s' % template)
  97. assert os.path.isfile('notebook2.html')
  98. with open('notebook2.html') as f:
  99. text = f.read()
  100. assert text == test_output
  101. @onlyif_cmds_exist('pandoc', 'xelatex')
  102. def test_filename_spaces(self):
  103. """
  104. Generate PDFs with graphics if notebooks have spaces in the name?
  105. """
  106. with self.create_temp_cwd(['notebook2.ipynb']):
  107. os.rename('notebook2.ipynb', 'notebook with spaces.ipynb')
  108. self.nbconvert('--log-level 0 --to pdf'
  109. ' "notebook with spaces"'
  110. ' --PDFExporter.latex_count=1'
  111. ' --PDFExporter.verbose=True'
  112. )
  113. assert os.path.isfile('notebook with spaces.pdf')
  114. @onlyif_cmds_exist('pandoc', 'xelatex')
  115. def test_pdf(self):
  116. """
  117. Check to see if pdfs compile, even if strikethroughs are included.
  118. """
  119. with self.create_temp_cwd(['notebook2.ipynb']):
  120. self.nbconvert('--log-level 0 --to pdf'
  121. ' "notebook2"'
  122. ' --PDFExporter.latex_count=1'
  123. ' --PDFExporter.verbose=True'
  124. )
  125. assert os.path.isfile('notebook2.pdf')
  126. def test_post_processor(self):
  127. """Do post processors work?"""
  128. with self.create_temp_cwd(['notebook1.ipynb']):
  129. out, err = self.nbconvert('--log-level 0 --to python notebook1 '
  130. '--post nbconvert.tests.test_nbconvertapp.DummyPost')
  131. self.assertIn('Dummy:notebook1.py', out)
  132. @onlyif_cmds_exist('pandoc')
  133. def test_spurious_cr(self):
  134. """Check for extra CR characters"""
  135. with self.create_temp_cwd(['notebook2.ipynb']):
  136. self.nbconvert('--log-level 0 --to latex notebook2')
  137. assert os.path.isfile('notebook2.tex')
  138. with open('notebook2.tex') as f:
  139. tex = f.read()
  140. self.nbconvert('--log-level 0 --to html notebook2')
  141. assert os.path.isfile('notebook2.html')
  142. with open('notebook2.html') as f:
  143. html = f.read()
  144. self.assertEqual(tex.count('\r'), tex.count('\r\n'))
  145. self.assertEqual(html.count('\r'), html.count('\r\n'))
  146. @onlyif_cmds_exist('pandoc')
  147. def test_png_base64_html_ok(self):
  148. """Is embedded png data well formed in HTML?"""
  149. with self.create_temp_cwd(['notebook2.ipynb']):
  150. self.nbconvert('--log-level 0 --to HTML '
  151. 'notebook2.ipynb --template full')
  152. assert os.path.isfile('notebook2.html')
  153. with open('notebook2.html') as f:
  154. assert "data:image/png;base64,b'" not in f.read()
  155. @onlyif_cmds_exist('pandoc')
  156. def test_template(self):
  157. """
  158. Do export templates work?
  159. """
  160. with self.create_temp_cwd(['notebook2.ipynb']):
  161. self.nbconvert('--log-level 0 --to slides '
  162. 'notebook2.ipynb')
  163. assert os.path.isfile('notebook2.slides.html')
  164. with open('notebook2.slides.html') as f:
  165. assert '/reveal.css' in f.read()
  166. def test_output_ext(self):
  167. """test --output=outputfile[.ext]"""
  168. with self.create_temp_cwd(['notebook1.ipynb']):
  169. self.nbconvert('--log-level 0 --to python '
  170. 'notebook1.ipynb --output nb.py')
  171. assert os.path.exists('nb.py')
  172. self.nbconvert('--log-level 0 --to python '
  173. 'notebook1.ipynb --output nb2')
  174. assert os.path.exists('nb2.py')
  175. def test_glob_explicit(self):
  176. """
  177. Can a search pattern be used along with matching explicit notebook names?
  178. """
  179. with self.create_temp_cwd(['notebook*.ipynb']):
  180. self.nbconvert('--log-level 0 --to python '
  181. '*.ipynb notebook1.ipynb notebook2.ipynb')
  182. assert os.path.isfile('notebook1.py')
  183. assert os.path.isfile('notebook2.py')
  184. def test_explicit_glob(self):
  185. """
  186. Can explicit notebook names be used and then a matching search pattern?
  187. """
  188. with self.create_temp_cwd(['notebook*.ipynb']):
  189. self.nbconvert('--log-level 0 --to=python '
  190. 'notebook1.ipynb notebook2.ipynb *.ipynb')
  191. assert os.path.isfile('notebook1.py')
  192. assert os.path.isfile('notebook2.py')
  193. def test_default_config(self):
  194. """
  195. Does the default config work?
  196. """
  197. with self.create_temp_cwd(['notebook*.ipynb', 'jupyter_nbconvert_config.py']):
  198. self.nbconvert('--log-level 0')
  199. assert os.path.isfile('notebook1.py')
  200. assert not os.path.isfile('notebook2.py')
  201. def test_override_config(self):
  202. """
  203. Can the default config be overridden?
  204. """
  205. with self.create_temp_cwd(['notebook*.ipynb',
  206. 'jupyter_nbconvert_config.py',
  207. 'override.py']):
  208. self.nbconvert('--log-level 0 --config="override.py"')
  209. assert not os.path.isfile('notebook1.py')
  210. assert os.path.isfile('notebook2.py')
  211. def test_accents_in_filename(self):
  212. """
  213. Can notebook names include accents?
  214. """
  215. with self.create_temp_cwd():
  216. self.create_empty_notebook(u'nb1_análisis.ipynb')
  217. self.nbconvert('--log-level 0 --to Python nb1_*')
  218. assert os.path.isfile(u'nb1_análisis.py')
  219. @onlyif_cmds_exist('xelatex', 'pandoc')
  220. def test_filename_accent_pdf(self):
  221. """
  222. Generate PDFs if notebooks have an accent in their name?
  223. """
  224. with self.create_temp_cwd():
  225. self.create_empty_notebook(u'nb1_análisis.ipynb')
  226. self.nbconvert('--log-level 0 --to pdf "nb1_*"'
  227. ' --PDFExporter.latex_count=1'
  228. ' --PDFExporter.verbose=True')
  229. assert os.path.isfile(u'nb1_análisis.pdf')
  230. def test_cwd_plugin(self):
  231. """
  232. Verify that an extension in the cwd can be imported.
  233. """
  234. with self.create_temp_cwd(['hello.py']):
  235. self.create_empty_notebook(u'empty.ipynb')
  236. self.nbconvert('empty --to html --NbConvertApp.writer_class=\'hello.HelloWriter\'')
  237. assert os.path.isfile(u'hello.txt')
  238. def test_output_suffix(self):
  239. """
  240. Verify that the output suffix is applied
  241. """
  242. with self.create_temp_cwd():
  243. self.create_empty_notebook('empty.ipynb')
  244. self.nbconvert('empty.ipynb --to notebook')
  245. assert os.path.isfile('empty.nbconvert.ipynb')
  246. def test_different_build_dir(self):
  247. """
  248. Verify that the output suffix is not applied
  249. """
  250. with self.create_temp_cwd():
  251. self.create_empty_notebook('empty.ipynb')
  252. os.mkdir('output')
  253. self.nbconvert(
  254. 'empty.ipynb --to notebook '
  255. '--FilesWriter.build_directory=output')
  256. assert os.path.isfile('output/empty.ipynb')
  257. def test_inplace(self):
  258. """
  259. Verify that the notebook is converted in place
  260. """
  261. with self.create_temp_cwd():
  262. self.create_empty_notebook('empty.ipynb')
  263. self.nbconvert('empty.ipynb --inplace')
  264. assert os.path.isfile('empty.ipynb')
  265. assert not os.path.isfile('empty.nbconvert.ipynb')
  266. assert not os.path.isfile('empty.html')
  267. def test_no_prompt(self):
  268. """
  269. Verify that the html has no prompts when given --no-prompt.
  270. """
  271. with self.create_temp_cwd(["notebook1.ipynb"]):
  272. self.nbconvert('notebook1.ipynb --log-level 0 --no-prompt --to html')
  273. assert os.path.isfile('notebook1.html')
  274. with open("notebook1.html",'r') as f:
  275. text = f.read()
  276. assert "In [" not in text
  277. assert "Out[" not in text
  278. self.nbconvert('notebook1.ipynb --log-level 0 --to html')
  279. assert os.path.isfile('notebook1.html')
  280. with open("notebook1.html",'r') as f:
  281. text2 = f.read()
  282. assert "In [" in text2
  283. assert "Out[" in text2
  284. def test_cell_tag_output(self):
  285. """
  286. Verify that the html has tags in cell attributes if they exist.
  287. """
  288. with self.create_temp_cwd(["notebook_tags.ipynb"]):
  289. self.nbconvert('notebook_tags.ipynb --log-level 0 --to html')
  290. assert os.path.isfile('notebook_tags.html')
  291. with open("notebook_tags.html",'r') as f:
  292. text = f.read()
  293. assert 'code_cell rendered celltag_mycelltag celltag_mysecondcelltag">' in text
  294. assert 'code_cell rendered">' in text
  295. assert 'text_cell rendered celltag_mymarkdowncelltag">' in text
  296. assert 'text_cell rendered">' in text
  297. def test_no_input(self):
  298. """
  299. Verify that the html has no input when given --no-input.
  300. """
  301. with self.create_temp_cwd(["notebook1.ipynb"]):
  302. self.nbconvert('notebook1.ipynb --log-level 0 --no-input --to html')
  303. assert os.path.isfile('notebook1.html')
  304. with open("notebook1.html",'r') as f:
  305. text = f.read()
  306. assert "In [" not in text
  307. assert "Out[" not in text
  308. assert ('<span class="n">x</span>'
  309. '<span class="p">,</span>'
  310. '<span class="n">y</span>'
  311. '<span class="p">,</span>'
  312. '<span class="n">z</span> '
  313. '<span class="o">=</span> '
  314. '<span class="n">symbols</span>'
  315. '<span class="p">(</span>'
  316. '<span class="s1">&#39;x y z&#39;</span>'
  317. '<span class="p">)</span>') not in text
  318. self.nbconvert('notebook1.ipynb --log-level 0 --to html')
  319. assert os.path.isfile('notebook1.html')
  320. with open("notebook1.html",'r') as f:
  321. text2 = f.read()
  322. assert "In&nbsp;[" in text2
  323. assert "Out[" in text2
  324. assert ('<span class="n">x</span>'
  325. '<span class="p">,</span>'
  326. '<span class="n">y</span>'
  327. '<span class="p">,</span>'
  328. '<span class="n">z</span> '
  329. '<span class="o">=</span> '
  330. '<span class="n">symbols</span>'
  331. '<span class="p">(</span>'
  332. '<span class="s1">&#39;x y z&#39;</span>'
  333. '<span class="p">)</span>') in text2
  334. def test_allow_errors(self):
  335. """
  336. Verify that conversion is aborted with '--execute' if an error is
  337. encountered, but that conversion continues if '--allow-errors' is
  338. used in addition.
  339. """
  340. with self.create_temp_cwd(['notebook3*.ipynb']):
  341. # Convert notebook containing a cell that raises an error,
  342. # both without and with cell execution enabled.
  343. output1, _ = self.nbconvert('--to markdown --stdout notebook3*.ipynb') # no cell execution
  344. output2, _ = self.nbconvert('--to markdown --allow-errors --stdout notebook3*.ipynb') # no cell execution; --allow-errors should have no effect
  345. output3, _ = self.nbconvert('--execute --allow-errors --to markdown --stdout notebook3*.ipynb') # with cell execution; errors are allowed
  346. # Un-executed outputs should not contain either
  347. # of the two numbers computed in the notebook.
  348. assert '23' not in output1
  349. assert '42' not in output1
  350. assert '23' not in output2
  351. assert '42' not in output2
  352. # Executed output should contain both numbers.
  353. assert '23' in output3
  354. assert '42' in output3
  355. # Executing the notebook should raise an exception if --allow-errors is not specified
  356. with pytest.raises(OSError):
  357. self.nbconvert('--execute --to markdown --stdout notebook3*.ipynb')
  358. def test_errors_print_traceback(self):
  359. """
  360. Verify that the stderr output contains the traceback of the cell execution exception.
  361. """
  362. with self.create_temp_cwd(['notebook3_with_errors.ipynb']):
  363. _, error_output = self.nbconvert('--execute --to markdown --stdout notebook3_with_errors.ipynb',
  364. ignore_return_code=True)
  365. assert 'print("Some text before the error")' in error_output
  366. assert 'raise RuntimeError("This is a deliberate exception")' in error_output
  367. assert 'RuntimeError: This is a deliberate exception' in error_output
  368. def test_fenced_code_blocks_markdown(self):
  369. """
  370. Verify that input cells use fenced code blocks with the language
  371. name in nb.metadata.kernelspec.language, if that exists
  372. """
  373. with self.create_temp_cwd(["notebook1*.ipynb"]):
  374. # this notebook doesn't have nb.metadata.kernelspec, so it should
  375. # just do a fenced code block, with no language
  376. output1, _ = self.nbconvert('--to markdown --stdout notebook1.ipynb')
  377. assert '```python' not in output1 # shouldn't have language
  378. assert "```" in output1 # but should have fenced blocks
  379. with self.create_temp_cwd(["notebook_jl*.ipynb"]):
  380. output2, _ = self.nbconvert('--to markdown --stdout notebook_jl.ipynb')
  381. assert '```julia' in output2 # shouldn't have language
  382. assert "```" in output2 # but should also plain ``` to close cell
  383. def test_convert_from_stdin_to_stdout(self):
  384. """
  385. Verify that conversion can be done via stdin to stdout
  386. """
  387. with self.create_temp_cwd(["notebook1.ipynb"]):
  388. with io.open('notebook1.ipynb') as f:
  389. notebook = f.read().encode()
  390. output1, _ = self.nbconvert('--to markdown --stdin --stdout', stdin=notebook)
  391. assert '```python' not in output1 # shouldn't have language
  392. assert "```" in output1 # but should have fenced blocks
  393. def test_convert_from_stdin(self):
  394. """
  395. Verify that conversion can be done via stdin.
  396. """
  397. with self.create_temp_cwd(["notebook1.ipynb"]):
  398. with io.open('notebook1.ipynb') as f:
  399. notebook = f.read().encode()
  400. self.nbconvert('--to markdown --stdin', stdin=notebook)
  401. assert os.path.isfile("notebook.md") # default name for stdin input
  402. with io.open('notebook.md') as f:
  403. output1 = f.read()
  404. assert '```python' not in output1 # shouldn't have language
  405. assert "```" in output1 # but should have fenced blocks
  406. @onlyif_cmds_exist('pandoc', 'xelatex')
  407. def test_linked_images(self):
  408. """
  409. Generate PDFs with an image linked in a markdown cell
  410. """
  411. with self.create_temp_cwd(['latex-linked-image.ipynb', 'testimage.png']):
  412. self.nbconvert('--to pdf latex-linked-image.ipynb')
  413. assert os.path.isfile('latex-linked-image.pdf')
  414. @onlyif_cmds_exist('pandoc')
  415. def test_embedded_jpeg(self):
  416. """
  417. Verify that latex conversion succeeds
  418. with a notebook with an embedded .jpeg
  419. """
  420. with self.create_temp_cwd(['notebook4_jpeg.ipynb',
  421. 'containerized_deployments.jpeg']):
  422. self.nbconvert('--to latex notebook4_jpeg.ipynb')
  423. assert os.path.isfile('notebook4_jpeg.tex')
  424. @onlyif_cmds_exist('pandoc')
  425. def test_markdown_display_priority(self):
  426. """
  427. Check to see if markdown conversion embeds PNGs,
  428. even if an (unsupported) PDF is present.
  429. """
  430. with self.create_temp_cwd(['markdown_display_priority.ipynb']):
  431. self.nbconvert('--log-level 0 --to markdown '
  432. '"markdown_display_priority.ipynb"')
  433. assert os.path.isfile('markdown_display_priority.md')
  434. with io.open('markdown_display_priority.md') as f:
  435. markdown_output = f.read()
  436. assert ("markdown_display_priority_files/"
  437. "markdown_display_priority_0_1.png") in markdown_output
  438. @onlyif_cmds_exist('pandoc')
  439. def test_write_figures_to_custom_path(self):
  440. """
  441. Check if figure files are copied to configured path.
  442. """
  443. def fig_exists(path):
  444. return (len(os.listdir(path)) > 0)
  445. # check absolute path
  446. with self.create_temp_cwd(['notebook4_jpeg.ipynb',
  447. 'containerized_deployments.jpeg']):
  448. output_dir = tempdir.TemporaryDirectory()
  449. path = os.path.join(output_dir.name, 'files')
  450. self.nbconvert(
  451. '--log-level 0 notebook4_jpeg.ipynb --to rst '
  452. '--NbConvertApp.output_files_dir={}'
  453. .format(path))
  454. assert fig_exists(path)
  455. output_dir.cleanup()
  456. # check relative path
  457. with self.create_temp_cwd(['notebook4_jpeg.ipynb',
  458. 'containerized_deployments.jpeg']):
  459. self.nbconvert(
  460. '--log-level 0 notebook4_jpeg.ipynb --to rst '
  461. '--NbConvertApp.output_files_dir=output')
  462. assert fig_exists('output')
  463. # check default path with notebook name
  464. with self.create_temp_cwd(['notebook4_jpeg.ipynb',
  465. 'containerized_deployments.jpeg']):
  466. self.nbconvert(
  467. '--log-level 0 notebook4_jpeg.ipynb --to rst')
  468. assert fig_exists('notebook4_jpeg_files')