test_templateexporter.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. """
  2. Module with tests for templateexporter.py
  3. """
  4. # Copyright (c) Jupyter Development Team.
  5. # Distributed under the terms of the Modified BSD License.
  6. import os
  7. from traitlets import default
  8. from traitlets.config import Config
  9. from jinja2 import DictLoader, TemplateNotFound
  10. from nbformat import v4
  11. from .base import ExportersTestsBase
  12. from .cheese import CheesePreprocessor
  13. from ..templateexporter import TemplateExporter
  14. from ..rst import RSTExporter
  15. from ..html import HTMLExporter
  16. from ..markdown import MarkdownExporter
  17. from testpath import tempdir
  18. import pytest
  19. raw_template = """{%- extends 'rst.tpl' -%}
  20. {%- block in_prompt -%}
  21. blah
  22. {%- endblock in_prompt -%}
  23. """
  24. class TestExporter(ExportersTestsBase):
  25. """Contains test functions for exporter.py"""
  26. def test_constructor(self):
  27. """
  28. Can a TemplateExporter be constructed?
  29. """
  30. TemplateExporter()
  31. def test_export(self):
  32. """
  33. Can a TemplateExporter export something?
  34. """
  35. exporter = self._make_exporter()
  36. (output, resources) = exporter.from_filename(self._get_notebook())
  37. assert len(output) > 0
  38. def test_extract_outputs(self):
  39. """
  40. If the ExtractOutputPreprocessor is enabled, are outputs extracted?
  41. """
  42. config = Config({'ExtractOutputPreprocessor': {'enabled': True}})
  43. exporter = self._make_exporter(config=config)
  44. (output, resources) = exporter.from_filename(self._get_notebook())
  45. assert resources is not None
  46. assert isinstance(resources['outputs'], dict)
  47. assert len(resources['outputs']) > 0
  48. def test_preprocessor_class(self):
  49. """
  50. Can a preprocessor be added to the preprocessors list by class type?
  51. """
  52. config = Config({'Exporter': {'preprocessors': [CheesePreprocessor]}})
  53. exporter = self._make_exporter(config=config)
  54. (output, resources) = exporter.from_filename(self._get_notebook())
  55. assert resources is not None
  56. assert resources['cheese'] == 'real'
  57. def test_preprocessor_instance(self):
  58. """
  59. Can a preprocessor be added to the preprocessors list by instance?
  60. """
  61. config = Config({'Exporter': {'preprocessors': [CheesePreprocessor()]}})
  62. exporter = self._make_exporter(config=config)
  63. (output, resources) = exporter.from_filename(self._get_notebook())
  64. assert resources is not None
  65. assert resources['cheese'] == 'real'
  66. def test_preprocessor_dottedobjectname(self):
  67. """
  68. Can a preprocessor be added to the preprocessors list by dotted object name?
  69. """
  70. config = Config({'Exporter': {'preprocessors': ['nbconvert.exporters.tests.cheese.CheesePreprocessor']}})
  71. exporter = self._make_exporter(config=config)
  72. (output, resources) = exporter.from_filename(self._get_notebook())
  73. assert resources is not None
  74. assert resources['cheese'] == 'real'
  75. def test_preprocessor_via_method(self):
  76. """
  77. Can a preprocessor be added via the Exporter convenience method?
  78. """
  79. exporter = self._make_exporter()
  80. exporter.register_preprocessor(CheesePreprocessor, enabled=True)
  81. (output, resources) = exporter.from_filename(self._get_notebook())
  82. assert resources is not None
  83. assert resources['cheese'] == 'real'
  84. def test_absolute_template_file(self):
  85. with tempdir.TemporaryDirectory() as td:
  86. template = os.path.join(td, 'abstemplate.tpl')
  87. test_output = 'absolute!'
  88. with open(template, 'w') as f:
  89. f.write(test_output)
  90. config = Config()
  91. config.TemplateExporter.template_file = template
  92. exporter = self._make_exporter(config=config)
  93. assert exporter.template.filename == template
  94. assert os.path.dirname(template) in exporter.template_path
  95. def test_relative_template_file(self):
  96. with tempdir.TemporaryWorkingDirectory() as td:
  97. os.mkdir('relative')
  98. template = os.path.abspath(os.path.join(td, 'relative', 'relative_template.tpl'))
  99. test_output = 'relative!'
  100. with open(template, 'w') as f:
  101. f.write(test_output)
  102. config = Config()
  103. config.TemplateExporter.template_file = template
  104. exporter = self._make_exporter(config=config)
  105. assert os.path.abspath(exporter.template.filename) == template
  106. assert os.path.dirname(template) in [os.path.abspath(d) for d in exporter.template_path]
  107. def test_raw_template_attr(self):
  108. """
  109. Verify that you can assign a in memory template string by overwriting
  110. `raw_template` as simple(non-traitlet) attribute
  111. """
  112. nb = v4.new_notebook()
  113. nb.cells.append(v4.new_code_cell("some_text"))
  114. class AttrExporter(TemplateExporter):
  115. raw_template = raw_template
  116. exporter_attr = AttrExporter()
  117. output_attr, _ = exporter_attr.from_notebook_node(nb)
  118. assert "blah" in output_attr
  119. def test_raw_template_init(self):
  120. """
  121. Test that template_file and raw_template traitlets play nicely together.
  122. - source assigns template_file default first, then raw_template
  123. - checks that the raw_template overrules template_file if set
  124. - checks that once raw_template is set to '', template_file returns
  125. """
  126. nb = v4.new_notebook()
  127. nb.cells.append(v4.new_code_cell("some_text"))
  128. class AttrExporter(RSTExporter):
  129. def __init__(self, *args, **kwargs):
  130. self.raw_template = raw_template
  131. exporter_init = AttrExporter()
  132. output_init, _ = exporter_init.from_notebook_node(nb)
  133. assert "blah" in output_init
  134. exporter_init.raw_template = ''
  135. assert exporter_init.template_file == "rst.tpl"
  136. output_init, _ = exporter_init.from_notebook_node(nb)
  137. assert "blah" not in output_init
  138. def test_raw_template_dynamic_attr(self):
  139. """
  140. Test that template_file and raw_template traitlets play nicely together.
  141. - source assigns template_file default first, then raw_template
  142. - checks that the raw_template overrules template_file if set
  143. - checks that once raw_template is set to '', template_file returns
  144. """
  145. nb = v4.new_notebook()
  146. nb.cells.append(v4.new_code_cell("some_text"))
  147. class AttrDynamicExporter(TemplateExporter):
  148. @default('template_file')
  149. def _template_file_default(self):
  150. return "rst.tpl"
  151. @default('raw_template')
  152. def _raw_template_default(self):
  153. return raw_template
  154. exporter_attr_dynamic = AttrDynamicExporter()
  155. output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb)
  156. assert "blah" in output_attr_dynamic
  157. exporter_attr_dynamic.raw_template = ''
  158. assert exporter_attr_dynamic.template_file == "rst.tpl"
  159. output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb)
  160. assert "blah" not in output_attr_dynamic
  161. def test_raw_template_dynamic_attr_reversed(self):
  162. """
  163. Test that template_file and raw_template traitlets play nicely together.
  164. - source assigns raw_template default first, then template_file
  165. - checks that the raw_template overrules template_file if set
  166. - checks that once raw_template is set to '', template_file returns
  167. """
  168. nb = v4.new_notebook()
  169. nb.cells.append(v4.new_code_cell("some_text"))
  170. class AttrDynamicExporter(TemplateExporter):
  171. @default('raw_template')
  172. def _raw_template_default(self):
  173. return raw_template
  174. @default('template_file')
  175. def _template_file_default(self):
  176. return "rst.tpl"
  177. exporter_attr_dynamic = AttrDynamicExporter()
  178. output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb)
  179. assert "blah" in output_attr_dynamic
  180. exporter_attr_dynamic.raw_template = ''
  181. assert exporter_attr_dynamic.template_file == "rst.tpl"
  182. output_attr_dynamic, _ = exporter_attr_dynamic.from_notebook_node(nb)
  183. assert "blah" not in output_attr_dynamic
  184. def test_raw_template_constructor(self):
  185. """
  186. Test `raw_template` as a keyword argument in the exporter constructor.
  187. """
  188. nb = v4.new_notebook()
  189. nb.cells.append(v4.new_code_cell("some_text"))
  190. output_constructor, _ = TemplateExporter(
  191. raw_template=raw_template).from_notebook_node(nb)
  192. assert "blah" in output_constructor
  193. def test_raw_template_assignment(self):
  194. """
  195. Test `raw_template` assigned after the fact on non-custom Exporter.
  196. """
  197. nb = v4.new_notebook()
  198. nb.cells.append(v4.new_code_cell("some_text"))
  199. exporter_assign = TemplateExporter()
  200. exporter_assign.raw_template = raw_template
  201. output_assign, _ = exporter_assign.from_notebook_node(nb)
  202. assert "blah" in output_assign
  203. def test_raw_template_reassignment(self):
  204. """
  205. Test `raw_template` reassigned after the fact on non-custom Exporter.
  206. """
  207. nb = v4.new_notebook()
  208. nb.cells.append(v4.new_code_cell("some_text"))
  209. exporter_reassign = TemplateExporter()
  210. exporter_reassign.raw_template = raw_template
  211. output_reassign, _ = exporter_reassign.from_notebook_node(nb)
  212. assert "blah" in output_reassign
  213. exporter_reassign.raw_template = raw_template.replace("blah", "baz")
  214. output_reassign, _ = exporter_reassign.from_notebook_node(nb)
  215. assert "baz" in output_reassign
  216. def test_raw_template_deassignment(self):
  217. """
  218. Test `raw_template` does not overwrite template_file if deassigned after
  219. being assigned to a non-custom Exporter.
  220. """
  221. nb = v4.new_notebook()
  222. nb.cells.append(v4.new_code_cell("some_text"))
  223. exporter_deassign = RSTExporter()
  224. exporter_deassign.raw_template = raw_template
  225. output_deassign, _ = exporter_deassign.from_notebook_node(nb)
  226. assert "blah" in output_deassign
  227. exporter_deassign.raw_template = ''
  228. assert exporter_deassign.template_file == 'rst.tpl'
  229. output_deassign, _ = exporter_deassign.from_notebook_node(nb)
  230. assert "blah" not in output_deassign
  231. def test_raw_template_dereassignment(self):
  232. """
  233. Test `raw_template` does not overwrite template_file if deassigned after
  234. being assigned to a non-custom Exporter.
  235. """
  236. nb = v4.new_notebook()
  237. nb.cells.append(v4.new_code_cell("some_text"))
  238. exporter_dereassign = RSTExporter()
  239. exporter_dereassign.raw_template = raw_template
  240. output_dereassign, _ = exporter_dereassign.from_notebook_node(nb)
  241. assert "blah" in output_dereassign
  242. exporter_dereassign.raw_template = raw_template.replace("blah", "baz")
  243. output_dereassign, _ = exporter_dereassign.from_notebook_node(nb)
  244. assert "baz" in output_dereassign
  245. exporter_dereassign.raw_template = ''
  246. assert exporter_dereassign.template_file == 'rst.tpl'
  247. output_dereassign, _ = exporter_dereassign.from_notebook_node(nb)
  248. assert "blah" not in output_dereassign
  249. def test_fail_to_find_template_file(self):
  250. # Create exporter with invalid template file, check that it doesn't
  251. # exist in the environment, try to convert empty notebook. Failure is
  252. # expected due to nonexistant template file.
  253. template = 'does_not_exist.tpl'
  254. exporter = TemplateExporter(template_file=template)
  255. assert template not in exporter.environment.list_templates(extensions=['tpl'])
  256. nb = v4.new_notebook()
  257. with pytest.raises(TemplateNotFound):
  258. out, resources = exporter.from_notebook_node(nb)
  259. def test_exclude_code_cell(self):
  260. no_io = {
  261. "TemplateExporter":{
  262. "exclude_output": True,
  263. "exclude_input": True,
  264. "exclude_input_prompt": False,
  265. "exclude_output_prompt": False,
  266. "exclude_markdown": False,
  267. "exclude_code_cell": False,
  268. }
  269. }
  270. c_no_io = Config(no_io)
  271. exporter_no_io = TemplateExporter(config=c_no_io)
  272. exporter_no_io.template_file = 'markdown'
  273. nb_no_io, resources_no_io = exporter_no_io.from_filename(self._get_notebook())
  274. assert not resources_no_io['global_content_filter']['include_input']
  275. assert not resources_no_io['global_content_filter']['include_output']
  276. no_code = {
  277. "TemplateExporter":{
  278. "exclude_output": False,
  279. "exclude_input": False,
  280. "exclude_input_prompt": False,
  281. "exclude_output_prompt": False,
  282. "exclude_markdown": False,
  283. "exclude_code_cell": True,
  284. }
  285. }
  286. c_no_code = Config(no_code)
  287. exporter_no_code = TemplateExporter(config=c_no_code)
  288. exporter_no_code.template_file = 'markdown'
  289. nb_no_code, resources_no_code = exporter_no_code.from_filename(self._get_notebook())
  290. assert not resources_no_code['global_content_filter']['include_code']
  291. assert nb_no_io == nb_no_code
  292. def test_exclude_input_prompt(self):
  293. no_input_prompt = {
  294. "TemplateExporter":{
  295. "exclude_output": False,
  296. "exclude_input": False,
  297. "exclude_input_prompt": True,
  298. "exclude_output_prompt": False,
  299. "exclude_markdown": False,
  300. "exclude_code_cell": False,
  301. }
  302. }
  303. c_no_input_prompt = Config(no_input_prompt)
  304. exporter_no_input_prompt = MarkdownExporter(config=c_no_input_prompt)
  305. nb_no_input_prompt, resources_no_input_prompt = exporter_no_input_prompt.from_filename(self._get_notebook())
  306. assert not resources_no_input_prompt['global_content_filter']['include_input_prompt']
  307. assert "# In[" not in nb_no_input_prompt
  308. def test_exclude_markdown(self):
  309. no_md= {
  310. "TemplateExporter":{
  311. "exclude_output": False,
  312. "exclude_input": False,
  313. "exclude_input_prompt": False,
  314. "exclude_output_prompt": False,
  315. "exclude_markdown": True,
  316. "exclude_code_cell": False,
  317. }
  318. }
  319. c_no_md = Config(no_md)
  320. exporter_no_md = TemplateExporter(config=c_no_md)
  321. exporter_no_md.template_file = 'python'
  322. nb_no_md, resources_no_md = exporter_no_md.from_filename(self._get_notebook())
  323. assert not resources_no_md['global_content_filter']['include_markdown']
  324. assert "First import NumPy and Matplotlib" not in nb_no_md
  325. def test_exclude_output_prompt(self):
  326. no_output_prompt = {
  327. "TemplateExporter":{
  328. "exclude_output": False,
  329. "exclude_input": False,
  330. "exclude_input_prompt": False,
  331. "exclude_output_prompt": True,
  332. "exclude_markdown": False,
  333. "exclude_code_cell": False,
  334. }
  335. }
  336. c_no_output_prompt = Config(no_output_prompt)
  337. exporter_no_output_prompt = HTMLExporter(config=c_no_output_prompt)
  338. nb_no_output_prompt, resources_no_output_prompt = exporter_no_output_prompt.from_filename(self._get_notebook())
  339. assert not resources_no_output_prompt['global_content_filter']['include_output_prompt']
  340. assert "Out[" not in nb_no_output_prompt
  341. def test_remove_elements_with_tags(self):
  342. conf = Config({
  343. "TagRemovePreprocessor": {
  344. "remove_cell_tags": ["remove_cell"],
  345. "remove_all_outputs_tags": ["remove_output"],
  346. "remove_input_tags": ["remove_input"]
  347. },
  348. })
  349. exporter = MarkdownExporter(config=conf)
  350. nb, resources = exporter.from_filename(self._get_notebook())
  351. assert "hist(evs.real)" not in nb
  352. assert "cell is just markdown testing whether" not in nb
  353. assert "(100,)" not in nb
  354. def _make_exporter(self, config=None):
  355. # Create the exporter instance, make sure to set a template name since
  356. # the base TemplateExporter doesn't have a template associated with it.
  357. exporter = TemplateExporter(config=config)
  358. if not exporter.template_file:
  359. # give it a default if not specified
  360. exporter.template_file = 'python'
  361. return exporter