Better Blogging with Jupyter Notebooks on WordPress.com

When I discovered Jupyter notebooks a few years ago I instantly recognized their potential as a technical blogging tool. Jupyter notebooks support mixtures of text, mathematics, program code, and graphics in a completely interactive environment. It’s easy to convert notebook JSON .ipynb files to markdown, \LaTeX, and HTML so it’s not a big leap to use Jupyter as a super-editor for blog posts with heavy doses of code, mathematics, and graphics.

I converted a few simple notebooks into HTML and tried loading them to my WordPress.com blog; the results did not amuse me! Raw notebook HTML is not suitable for WordPress.com.

WordPress.com imposes some serious constraints on low cost and free blogs. You cannot:

  1. Use arbitrary JavaScript.
  2. Import standalone CSS styles.
  3. Use non-standard plugins.

By setting up your own WordPress.org site or upgrading your WordPress.com account you can shed these limitations. I’ve considered both options but there’s just something about software vendors teasing users with basic features while nagging them to spend more on upgrades that gets my goat. I’m used to such abuse from the likes of Adobe and would advise WordPress.com to dial back upgrade nagging.

Fortunately, it’s not necessary to upgrade your WordPress.com account to make excellent use of Jupyter notebooks. With a few simple notebook file hacks, you can compose in Jupyter and post to WordPress.com

Hack #1: nb2wp

Benny Prijono has created a handy Python program nb2wp that converts Jupyter notebooks to WordPress.com oriented HTML. nb2wp uses BeautifulSoup, (a great software name if there ever was one), and the Python utility pynliner to convert the HTML generated by the Jupyter nbconvert utility to a WordPress.com oriented form.

nb2wp HTML can be pasted, (read Benny’s instructions), into the WordPress.com block editor. My post Using jodliterate was composed in this way.

nb2wp notebook HTML is treated as a single WordPress.com block editor block. This makes it hard to use the block editor which brings me to the next hack.

Hack #2: nb2subnb

Notebooks are stored as simple JSON files. It’s easy to split notebooks into n smaller notebooks. The following Python program, (available here), cuts notebook files into smaller sub-notebooks.

In [1]:
def nb2subnb(filename, *, cell_type='markdown', 
             single_nb=False, keep_cells=[], keep_texts=[]):
    r"""
    (nb2subnb) splits out typed cells of jupyter 
    notebooks into n sub-notebooks.

    examples:

       # split into n markdown cell notebooks
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb')

       # split into n code cell notebooks
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb', 
           cell_type='code')

       # all markdown cells in single notebook
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb', 
           single_nb='True')

       # code cells with numbers in range as single notebook
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb', 
           cell_type='code', single_nb='True', 
           keep_cells=list(range(10)))

       # markdown cells with strings 'Bhagavad' or 'github' 
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb', 
           single_nb='True',
           keep_texts=['Bhagavad','github'])

       # all code cells in range with string 'pacman' 
       nb2subnb(r'C:\temp\UsingJodliterate.ipynb', 
           cell_type='code',
           keep_texts=['pacman'], keep_cells=list(range(30)))

    """
    with open(filename) as in_file:
        nb_data = json.load(in_file)

    # notebook file name without extension/path
    nbname = os.path.basename(os.path.splitext(filename)[0])

    nb_cells, one_cell, nb_files = dict(), list(), list()

    for cnt, cell in enumerate(nb_data['cells']):
        if nb_data['cells'][cnt]['cell_type'] == cell_type:

            if not single_nb:
                # single cell notebooks
                nb_cells, one_cell = dict(), list()
                if 0 == len(keep_cells) or cnt in keep_cells:
                    if text_in_source(cell, keep_texts):
                        one_cell.append(cell)
                        nb_cells["cells"] = one_cell
                        nb_cells = insert_nb_metadata(
                            nb_data, nb_cells)
                        nb_out_file = NB_DIRECTORY + \
                            nbname + '-' + cell_type + \
                            '-' + str(cnt) + '.ipynb'
                        nb_files.append(nb_out_file)
                        with open(nb_out_file, 'w') as out_file:
                            json.dump(nb_cells, out_file,
                                      ensure_ascii=False)
            elif single_nb:
                # single notebook with only (cell_type) cells
                if 0 == len(keep_cells) or cnt in keep_cells:
                    if text_in_source(cell, keep_texts):
                        one_cell.append(cell)
                        nb_cells["cells"] = one_cell

    if single_nb:
        nb_out_file = NB_DIRECTORY + \
            nbname + '-' + cell_type + '-only.ipynb'
        nb_files.append(nb_out_file)
        nb_cells = insert_nb_metadata(nb_data, nb_cells)
        with open(nb_out_file, 'w') as out_file:
            json.dump(nb_cells, out_file, 
                      ensure_ascii=False)

    # list of generated sub-notebooks
    return nb_files

nb2wp can be applied to the sub-notebooks to produce smaller, more block editor friendly, HTML files. Blog posts can be put together by picking and pasting the smaller blocks.

Combining nb2wp and nb2subnb

nb2wpblk combines the actions of nb2wp and nb2subnb to generate n HTML files.

In [2]:
# append nb2* script directory to system path
import sys
sys.path.append(r'C:\temp\nb2wp')
In [3]:
# notebook file
nb_file = r'C:\temp\nb2wp\UsingJodliterate.ipynb'
In [4]:
nb2subnb_opts = {
    'single_nb': False,
    'cell_type': 'code',
    'keep_cells': [2, 8, 21],
    'keep_texts': []
}
In [5]:
import nb2wput as nbu

# split notebook into selected parts and convert to HTML 
nbu.nb2wpblk(nb_file, nb2subnb_parms=nb2subnb_opts)
Using template: full
Using CSS files ['C:\\temp\\nb2wp\\style.css']
Saving CSS to C:\temp\nb2wp\tmp\style.css
C:\temp\nb2wp\tmp\UsingJodliterate-code-2.html: 7054 bytes written in 4.488s
Using template: full
Using CSS files ['C:\\temp\\nb2wp\\style.css']
Saving CSS to C:\temp\nb2wp\tmp\style.css
C:\temp\nb2wp\tmp\UsingJodliterate-code-8.html: 4715 bytes written in 4.177s
Using template: full
Using CSS files ['C:\\temp\\nb2wp\\style.css']
Saving CSS to C:\temp\nb2wp\tmp\style.css
C:\temp\nb2wp\tmp\UsingJodliterate-code-21.html: 8683 bytes written in 5.437s

The utilities referenced in this post are available on Github here. Help yourself and blog better!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.