skip to main content

@tonyfast s notebooks

site navigation
notebook summary
title
mkdocs plugin for jupyter notebooks
description
i think i want more control of how mkdocs renders notebooks. i've been using mkdocs-jupyter for a while and it is great, but i need more knobs.
cells
10 total
4 code
state
executed out of order
kernel
Python [conda env:root] *
language
python
name
conda-root-py
lines of code
82
outputs
0
table of contents
{"kernelspec": {"display_name": "Python [conda env:root] *", "language": "python", "name": "conda-root-py"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.13"}, "tags": ["mkdocs-plugin"], "widgets": {"application/vnd.jupyter.widget-state+json": {"state": {}, "version_major": 2, "version_minor": 0}}, "title": "mkdocs plugin for jupyter notebooks", "description": "i think i want more control of how mkdocs renders notebooks.\ni've been using mkdocs-jupyter for a while and it is great,\nbut i need more knobs."}
notebook toolbar
Activate
cell ordering
1

mkdocs

i think i want more control of how mkdocs renders notebooks. i've been using mkdocs-jupyter for a while and it is great, but i need more knobs.

my particular need is to configure nbconvert exporters with more fine grain control than what mkdocs-jupyter offers. the major difference is we are going to target markdown output rather than html.

2

checklist for successfully integrating the plugin

steps to adding a mkdocs plugin to this the tonyfast project:

  • add plugin to mkdocs.yml

    plugins:
        - markdown_notebook
    
  • define plugin entry point for tonyfast

    [project.entry-points."mkdocs.plugins"]
    markdown_notebook = "tonyfast.mkdocs:MarkdownNotebook"
    
  • build the MarkdownNotebook plugin

  • integrate the plugin

    from tonyfast.mkdocs import MarkdownNotebook
    
  • add and improve the nbconvert export display renderers

3

building the

4
    import json, nbconvert, nbformat, pathlib, mkdocs.plugins, warnings, re, functools
    warnings.filterwarnings("ignore", category=DeprecationWarning)
5

the [ mkdocs plugin]

6

we now use a custom template and expo defined in 2022-12-31-markdownish-notebook.ipynb

7
        with __import__("importnb").Notebook():
            from tonyfast.xxii.__markdownish_notebook import template, PidgyExporter
8
    class MarkdownNotebook(mkdocs.plugins.BasePlugin):
        exporter_cls = PidgyExporter
        config_scheme = (
            # ('foo', mkdocs.config.config_options.Type(str, default='a default value')),
        )
                    
        @functools.lru_cache
        def get_exporter(self, key="mkdocs", **kw):
            with __import__("importnb").Notebook():
                from tonyfast.xxii.__markdownish_notebook import template, HEAD, replace_attachments
            kw.setdefault("template_file", key)
            exporter = self.exporter_cls(**kw)
            exporter.environment.filters.setdefault("attachment", replace_attachments)
            from jinja2 import DictLoader
            for loader in exporter.environment.loader.loaders:
                if isinstance(loader, DictLoader):
                    loader.mapping[key] = template
                    loader.mapping["HEAD"] = HEAD
                    break
            return exporter
            
        def on_page_read_source(self, page, config):
            if page.file.is_modified():
                if page.file.src_uri.endswith((".ipynb", )):
                    body = pathlib.Path(page.file.abs_src_path).read_text()
                    nb = nbformat.v4.reads(body)
                    exporter = self.get_exporter()
                    return "\n".join((
                        "---", json.dumps(nb.metadata),"---", # add metadata as front matter
                        exporter.from_notebook_node(nb)[0]
                    ))
                
        def on_post_page(self, output, page, config):
            if '<script type="application/vnd.jupyter.widget-view+json">' in output:
                left, sep, right = output.partition("</head")
                exporter = self.get_exporter()
                return left + self.get_exporter().environment.get_template("HEAD").render(resources=dict(
                            jupyter_widgets_base_url=exporter.jupyter_widgets_base_url, 
                            html_manager_semver_range=exporter.html_manager_semver_range, 
                            widget_renderer_url=exporter.widget_renderer_url,
                            require_js_url=exporter.require_js_url
                        )) + sep + right
            
        def on_page_markdown(self, markdown, page, config, files):
            import markdown
            title = markdown.Markdown(extensions=config['markdown_extensions']).convert(page.title)
            page.title = title[len("<p>"):-len("</p>")].strip().replace("code>", "pre>")
9

we trick mkdocs into thinking notebook files are markdown extensions.

10
    mkdocs.utils.markdown_extensions += ".ipynb", # this feels naughty.