├── requirements.txt ├── .gitignore ├── docs └── static │ └── nbreport.gif ├── postBuild ├── jupyter-config ├── nbconfig │ └── notebook.d │ │ └── my_fancy_module.json │ │ └── nbreport.json └── jupyter_notebook_config.d │ └── nbreport.json ├── nbreport ├── static │ ├── extra.css │ └── report.tpl └── __init__.py ├── setup.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | nbclean 2 | nbformat -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | *.egg-info/ 3 | __pycache__/ 4 | example/an_example_notebook.html -------------------------------------------------------------------------------- /docs/static/nbreport.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choldgraf/nbreport/HEAD/docs/static/nbreport.gif -------------------------------------------------------------------------------- /postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | jupyter bundlerextension enable --py nbreport --sys-prefix 4 | 5 | 6 | -------------------------------------------------------------------------------- /jupyter-config/nbconfig/notebook.d/my_fancy_module.json/nbreport.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "nbreport/index": true 4 | } 5 | } -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.d/nbreport.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "nbreport": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /nbreport/static/extra.css: -------------------------------------------------------------------------------- 1 | /* Add an extra style for a figure caption */ 2 | figure figcaption { 3 | text-align: center !important; 4 | font-style: italic; 5 | padding-bottom: 20px; 6 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | nbreport 3 | """ 4 | 5 | import codecs 6 | import os 7 | from setuptools import setup, find_packages 8 | import nbreport 9 | 10 | install_requires = [] 11 | 12 | setup( 13 | name="nbreport", 14 | description="Generate quick reports", 15 | long_description='', 16 | version=nbreport.__version__, 17 | packages=find_packages(), 18 | package_data={'sphinx_gallery': ['static/report.tpl']}, 19 | url="https://github.com/choldgraf/nbreport", 20 | author="Chris Holdgraf", 21 | author_email='choldgraf@berkeley.edu', 22 | install_requires=install_requires, 23 | license='3-clause BSD', 24 | classifiers=['Intended Audience :: Developers', 25 | 'Development Status :: 3 - Alpha', 26 | 'Framework :: Sphinx :: Extension', 27 | 'Programming Language :: Python', 28 | ], 29 | entry_points={ 30 | 'console_scripts': [ 31 | 'nbreport = nbreport:main', 32 | ]}, 33 | include_package_data=True, 34 | data_files=[ 35 | ("nbreport/static/", ["nbreport/static/report.tpl", 'nbreport/static/extra.css']), 36 | ], 37 | zip_safe=False 38 | ) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nbreport 2 | 3 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/choldgraf/nbreport/master?filepath=example%2F) 4 | 5 | Quick generate HTML reports from your Jupyter Notebooks. 6 | 7 | This is a lightweight Jupyter plugin that gives you the option to download 8 | a report from the Jupyter Notebook interface. Upon installing, you get a 9 | new button in the `Download as` section that, when clicked, does the following 10 | things: 11 | 12 | 1. Removes all empty cells 13 | 2. Removes any cells with `hide_cell` in the tags 14 | 3. Removes the code of any cells with `hide_code` in the tags 15 | 4. Clears the `stderr` output 16 | 5. Clears the input / output numbers 17 | 6. Adds author / title information to the top of the page 18 | 6. Outputs the notebook as a single HTML file that can be 19 | opened in the browser. 20 | 21 | ![demo](docs/static/nbreport.gif) 22 | 23 | `nbreport` uses another lightweight tool called [nbclean](https://github.com/choldgraf/nbclean) 24 | in order to accomplish the above, along with a simple template designed 25 | for [nbconvert](https://github.com/jupyter/nbconvert). 26 | 27 | ## Installation 28 | 29 | To install `nbreport`, first clone this repository to your computer, then run 30 | 31 | ``` 32 | pip install -e nbreport/ 33 | ``` 34 | 35 | Finally, activate `nbreport` on your Jupyter environment with the following command: 36 | 37 | ``` 38 | jupyter bundlerextension enable --py nbreport --sys-prefix 39 | ``` 40 | 41 | ## Usage 42 | 43 | There are two primary ways to use `nbreport`: 44 | 45 | * **As a command-line tool**. You can invoke `nbreport` from the command line with the 46 | following command: 47 | 48 | ``` 49 | nbreport path/to/notebook.ipynb 50 | ``` 51 | 52 | It will output the HTML file in the same directory as the notebook you used as input. 53 | 54 | * **From the Jupyter Notebook interface**. From a live Notebook, you can convert the 55 | current notebook to an HTML report by following clicking on `File --> Download As --> NBReport`. 56 | The HTML will be created in the same folder as the notebook you've opened. 57 | 58 | ## Command-line options 59 | 60 | You can expand the functionality of `nbreport` with a few extra command-line options. For 61 | example, the `--css` parameter allows you to include arbitrary extra CSS files with your built 62 | HTML, allowing you to style it however you like. -------------------------------------------------------------------------------- /nbreport/static/report.tpl: -------------------------------------------------------------------------------- 1 | {%- extends 'basic.tpl' -%} 2 | {% from 'mathjax.tpl' import mathjax %} 3 | 4 | 5 | {%- block header -%} 6 | 7 | 8 | 9 | {%- block html_head -%} 10 | 11 | {% set nb_title = nb.metadata.get('title', '') or resources['metadata']['name'] %} 12 | {{nb_title}} 13 | 14 | 15 | 16 | 17 | {% block ipywidgets %} 18 | {%- if "widgets" in nb.metadata -%} 19 | 39 | {%- endif -%} 40 | {% endblock ipywidgets %} 41 | 42 | {% for css in resources.inlining.css -%} 43 | 46 | {% endfor %} 47 | 48 | 90 | 91 | 92 | 93 | 94 | 95 | {{ mathjax() }} 96 | {%- endblock html_head -%} 97 | 98 | {%- endblock header -%} 99 | 100 | {% block body %} 101 | 102 |
103 |
104 | {% set nb_title = nb.metadata.get('title', '') or resources['metadata']['name'] %} 105 | {% set nb_author = nb.metadata.get('author', '') %} 106 |

{{ nb_title | replace("_", " ") | capitalize }}

107 | {% if nb_author %}

{{ nb_author }}

{% endif %} 108 |
109 | {{ super() }} 110 |
111 |
112 | 113 | {%- endblock body %} 114 | 115 | {% block data_png %} 116 |
117 | {{ super() }} 118 | {% if cell.metadata.caption %} 119 |
{{ cell.metadata.caption }}
120 | {% endif %} 121 |
122 | {% endblock data_png %} 123 | 124 | {% block footer %} 125 | {{ super() }} 126 | 127 | {% endblock footer %} -------------------------------------------------------------------------------- /nbreport/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from nbclean import NotebookCleaner 3 | import nbformat as nbf 4 | import os.path as op 5 | import subprocess 6 | import os 7 | 8 | __version__ = 'v0.1' 9 | path_template = op.join(op.dirname(__file__), 'static', 'report.tpl') 10 | 11 | 12 | def _jupyter_bundlerextension_paths(): 13 | """The nbreport extension!""" 14 | return [{ 15 | 'name': 'nbreport', # unique bundler name 16 | 'label': 'NBReport', # human-redable menu item label 17 | 'module_name': 'nbreport', # module containing bundle() 18 | 'group': 'download' # group under 'deploy' or 'download' menu 19 | }] 20 | 21 | def bundle(handler, model): 22 | """Transform, convert, bundle, etc. the notebook referenced by the given 23 | model. 24 | 25 | Then issue a Tornado web response using the `handler` to redirect 26 | the user's browser, download a file, show a HTML page, etc. This function 27 | must finish the handler response before returning either explicitly or by 28 | raising an exception. 29 | 30 | Parameters 31 | ---------- 32 | handler : tornado.web.RequestHandler 33 | Handler that serviced the bundle request 34 | model : dict 35 | Notebook model from the configured ContentManager 36 | """ 37 | # Return the buffer value as the response 38 | abs_nb_path = op.join(handler.settings['contents_manager'].root_dir, model['path']) 39 | notebook_filename = model['name'] 40 | path_out = op.join(op.dirname(abs_nb_path), notebook_filename) 41 | generate_report(model['content'], path_out) 42 | 43 | css = model['content']['metadata'].get('css_extra') 44 | if css: 45 | path_to_nb_dir = op.relpath(op.dirname(model['path']), op.curdir) 46 | path_css = op.join(path_to_nb_dir, css) 47 | 48 | if not op.exists(path_css): 49 | raise ValueError("Could not find CSS file: {}".format(path_css)) 50 | _extra_css(path_out.replace('.ipynb', '.html'), path_css) 51 | 52 | output_temp = "

{}
" + "{}

".format(path_out.replace('.ipynb', '.html')) 53 | if op.exists(path_out.replace('.ipynb', '.html')): 54 | output = output_temp.format("Report generated at the file below") 55 | else: 56 | output = output_temp.format("Report failed to generated the file below") 57 | handler.finish(output) 58 | 59 | 60 | def generate_report(ntbk, path_out): 61 | 62 | # Clean up the notebook 63 | cleaner = NotebookCleaner(ntbk) 64 | cleaner.remove_cells(empty=True) 65 | cleaner.remove_cells(tag='hide_cell') 66 | cleaner.clear(kind="content", tag='hide_code') 67 | cleaner.clear('stderr') 68 | 69 | path_out_tmp = path_out + '_TMP' 70 | cleaner.save(path_out_tmp) 71 | 72 | # Build the HTML 73 | call = ['jupyter', 'nbconvert', 74 | '--log-level="CRITICAL"', 75 | '--to', 'html', 76 | '--template', path_template, 77 | "--EmbedImagesPreprocessor.embed_images=True", 78 | "--TemplateExporter.exclude_input_prompt=True", 79 | "--TemplateExporter.exclude_output_prompt=True", 80 | '--FilesWriter.build_directory={}'.format(op.dirname(path_out)), 81 | path_out_tmp] 82 | subprocess.run(call) 83 | os.remove(path_out_tmp) 84 | 85 | 86 | def main(): 87 | parser = argparse.ArgumentParser() 88 | parser.add_argument("input_path", help="Path to the notebook for which you want to create a report.") 89 | parser.add_argument("--output_folder", default=None, help="The output folder for the HTML file.") 90 | parser.add_argument("--css", default=None, help="The path to a CSS file to include in the HTML.") 91 | 92 | args = parser.parse_args() 93 | path_in = args.input_path 94 | css = args.css 95 | ntbk = nbf.read(path_in, nbf.NO_CONVERT) 96 | output_folder = args.output_folder 97 | if args.output_folder is None: 98 | output_folder = op.dirname(path_in) 99 | output_path = op.join(output_folder, op.basename(path_in)) 100 | generate_report(ntbk, output_path) 101 | _extra_css(output_path.replace('.ipynb', '.html'), css) 102 | 103 | print('Finished generating report for file: \n{}'.format(output_path)) 104 | 105 | def _extra_css(path_html, css): 106 | # Add extra CSS if possible 107 | if css: 108 | if not op.exists(css): 109 | raise ValueError("Could not find CSS file: {}".format(css)) 110 | with open(path_html, 'r') as ff: 111 | lines = ff.readlines() 112 | 113 | # Read and add the new CSS lines 114 | with open(css, 'r') as ff: 115 | css_lines = ff.readlines() 116 | 117 | # Indent the added CSS 118 | css_lines = [' ' + iline for iline in css_lines] 119 | css_lines = [''] 121 | 122 | # Add in the CSS at the end of the file (but not the last line since it's ) 123 | lines = lines[:-2] + css_lines + lines[-2:] 124 | with open(path_html, 'w') as ff: 125 | ff.writelines(lines) 126 | 127 | if __name__ == '__main__': 128 | main() 129 | --------------------------------------------------------------------------------