31

I have seen a few of the talks by iPython developers about how to convert an ipython notebook to a blog post, a pdf, or even to an entire book(~min 43). The PDF-to-X converter interprets the iPython cells which are written in markdown or code and spits out a newly formatted document in one step.

My problem is that I would like to generate a large document where many of the figures and sections are programmatically generated - something like this. For this to work in iPython using the methods above, I would need to be able to write a function that would write other iPython-Code-Blocks. Does this capability exist?

#some pseudocode to give an idea
for variable in list:
    image = make_image(variable)
    write_iPython_Markdown_Cell(variable)
    write_iPython_Image_cell(image)

I think this might be useful so I am wondering if:

  1. generating iPython Cells through iPython is possible
  2. if there is a reason that this is a bad idea and I should stick to a 'classic' solution like a templating library (Jinja).

thanks, zach cp

EDIT: As per Thomas' suggestion I posted on the ipython mailing list and got some feedback on the feasibility of this idea. In short - there are some technical difficulties that make this idea less than ideal for the original idea. For a repetitive report where you would like to generate markdown -cells and corresponding images/tables it is ore complicated to work through the ipython kernel/browser than to generate a report directly with a templating system like Jinja.

Cœur
  • 37,241
  • 25
  • 195
  • 267
zach
  • 29,475
  • 16
  • 67
  • 88

10 Answers10

26

There's a Notebook gist by Fernando Perez here that demonstrates how to programmatically create new cells. Note that you can also pass metadata in, so if you're generating a report and want to turn the notebook into a slideshow, you can easily indicate whether the cell should be a slide, sub-slide, fragment, etc.

You can add any kind of cell, so what you want is straightforward now (though it probably wasn't when the question was asked!). E.g., something like this (untested code) should work:

from IPython.nbformat import current as nbf

nb = nbf.new_notebook()

cells = []

for var in my_list:
    # Assume make_image() saves an image to file and returns the filename
    image_file = make_image(var)
    text = "Variable: %s\n![image](%s)" % (var, image_file)
    cell = nbf.new_text_cell('markdown', text)
    cells.append(cell)

nb['worksheets'].append(nbf.new_worksheet(cells=cells))

with open('my_notebook.ipynb', 'w') as f:
        nbf.write(nb, f, 'ipynb')
mlu
  • 137
  • 2
  • 8
Tal Yarkoni
  • 2,534
  • 1
  • 17
  • 12
18

I won't judge whether it's a good idea, but if you call get_ipython().set_next_input(s) in the notebook, it will create a new cell with the string s. This is what IPython uses internally for its %load and %recall commands.

Thomas K
  • 39,200
  • 7
  • 84
  • 86
  • Thanks Thomas. If this is the only function I'll need to look elsewhere. It can add text but not images (they aren't rendered). It also will result in the reverse-of-desired-order because it will always add from the top in tot he next row which pushes the others down. So probably a templating engine will be the way to go although its a nice idea to have a one-file-for-everything approach (if it is simple). – zach Nov 29 '12 at 23:45
  • There's not really an 'image cell', but you can display images as the output from code. To display more than one, see the `Image` class and `display` function in [IPython.core.display](https://github.com/ipython/ipython/blob/master/IPython/core/display.py). I don't think there's a way to add a Markdown cell yet, but there might be interest in adding one if you bring it up on the mailing list. – Thomas K Nov 30 '12 at 17:48
  • you're right I can do: x = Image(filename='test.png'); get_ipython().set_next_input(display_png(x)) to display an image but then I cannot put text between these images. also there wold have to be a way to hide the original cell. So maybe this is getting a bit complicated but I'll post to the mailing list as you suggest. thanks. – zach Dec 01 '12 at 21:25
  • (To be clear, you don't need the `set_next_input` to display an image - `set_next_input` only works to create a new cell with Python code. Images go in *output* cells) – Thomas K Dec 02 '12 at 22:52
  • Is there a way to create a markdown cell this way? I can't see a related parameter in the docs: https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.interactiveshell.html – BND Mar 23 '19 at 17:02
  • @BND there isn't. IPython doesn't know about the notebook document, this is just co-opting a mechanism originally intended to pre-fill the next input prompt in the shell. – Thomas K Mar 25 '19 at 10:21
  • set_next_input() will add a code cell, but how to add a new markdown cell? – xingpei Pang Oct 15 '21 at 09:17
  • There's no 'from inside' API that I know of to add a markdown cell. You can use the [IPython display API](https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html) to display Markdown output, though. Or if you're programatically creating/modifying a notebook file from *outside* the notebook, see the other answers about nbformat. – Thomas K Oct 17 '21 at 10:52
9

Note that the accepted answer by Tal is a little deprecated and getting more deprecated: in ipython v3 you can (/should) import nbformat directly, and after that you need to specify which version of notebook you want to create.

So,

from IPython.nbformat import current as nbf

becomes

from nbformat import current as nbf

becomes

from nbformat import v4 as nbf

However, in this final version, the compatibility breaks because the write method is in the parent module nbformat, where all of the other methods used by Fernando Perez are in the v4 module, although some of them are under different names (e.g. new_text_cell('markdown', source) becomes new_markdown_cell(source)).

Here is an example of the v3 way of doing things: see generate_examples.py for the code and plotstyles.ipynb for the output. IPython 4 is, at time of writing, so new that using the web interface and clicking 'new notebook' still produces a v3 notebook.

Chris L. Barnes
  • 475
  • 4
  • 13
3

Below is the code of the function which will load contents of a file and insert it into the next cell of the notebook:

from IPython.display import display_javascript

def make_cell(s):
   text = s.replace('\n','\\n').replace("\"", "\\\"").replace("'", "\\'")
   text2 = """var t_cell = IPython.notebook.get_selected_cell()
   t_cell.set_text('{}');
   var t_index = IPython.notebook.get_cells().indexOf(t_cell);
   IPython.notebook.to_code(t_index);
   IPython.notebook.get_cell(t_index).render();""".format(text)
   display_javascript(text2, raw=True)

def insert_file(filename):
   with open(filename, 'r') as content_file:
       content = content_file.read()
   make_cell(content)

See details in my blog.

Ihor B.
  • 1,275
  • 14
  • 17
  • I am using Jupyterlab and getting this error with the code above. `Javascript Error: IPython is not defined` – BND Mar 23 '19 at 16:58
2

Using the magics can be another solution. e.g.

get_ipython().run_cell_magic(u'HTML', u'', u'<font color=red>heffffo</font>')

Now that you can programatically generate HTML in a cell, you can format in any ways as you wish. Images are of course supported. If you want to repetitively generate output to multiple cells, just do multiple of the above with the string to be a placeholder.

p.s. I once had this need and reached this thread. I wanted to render a table (not the ascii output of lists and tuples) at that time. Later I found pandas.DataFrame is amazingly suited for my job. It generate HTML formatted tables automatically.

Pili Hu
  • 151
  • 3
  • 10
2
from IPython.display import display, Javascript

def add_cell(text,  type='code', direct='above'):
    text = text.replace('\n','\\n').replace("\"", "\\\"").replace("'", "\\'")
    display(Javascript('''
        var cell = IPython.notebook.insert_cell_{}("{}")
        cell.set_text("{}")
        '''.format(direct, type, text)));

for i in range(3):
    add_cell(f'# heading{i}', 'markdown')
    add_cell(f'code {i}')

codes above will add cells as follows: enter image description here

xingpei Pang
  • 1,185
  • 11
  • 15
1

As a slight update incorporating Tal's answer above, updates from Chris Barnes and a little digging in the nbformat docs, the following worked for me:

import nbformat
from nbformat import v4 as nbf

nb = nbf.new_notebook()

cells = [
    nbf.new_code_cell(f"""print("Doing the thing: {i}")""")
    for i in range(10)
]

nb.cells.extend(cells)

with open('generated_notebook.ipynb', 'w') as f:
    nbformat.write(nb, f)

You can then start up the new artificial notebook and cut-n-paste cells where ever you need them.

This is unlikely to be the best way to do anything, but it's useful as a dirty hack. ‍

This worked with the following versions:

Package              Version
-------------------- ----------
ipykernel            5.3.0
ipython              7.15.0
jupyter              1.0.0
jupyter-client       6.1.3
jupyter-console      6.1.0
jupyter-core         4.6.3
nbconvert            5.6.1
nbformat             5.0.7
notebook             6.0.3
...
cbare
  • 12,060
  • 8
  • 56
  • 63
1

@xingpei Pang solution is perfect, especially if you want to create customized code for each dataset having several groups for instance. However, the main issue with the javascript code is that if you run this code in a trusted notebook, it runs every time the notebook is loaded.

The solution I came up with is to clear the cell output after execution. The javascript code is stored in the output cell, so by clearing the output the code is gone and nothing is left to be executed in the trusted mode again. By using the code from here, the solution is the code below.

from IPython.display import display, Javascript, clear_output

def add_cell(text,  type='code', direct='above'):
    text = text.replace('\n','\\n').replace("\"", "\\\"").replace("'", "\\'")
    display(Javascript('''
        var cell = IPython.notebook.insert_cell_{}("{}")
        cell.set_text("{}")
        '''.format(direct, type, text)));

# create cells
for i in range(3):
    add_cell(f'# heading{i}', 'markdown')
    add_cell(f'code {i}')
    

# clean the javascript code from the current cell output
for i in range(10):
    clear_output(wait=True)

Note that the clear_output() needs the be run several times to make sure the output is cleared.

Mehdi
  • 999
  • 13
  • 11
-1

Run in the Jupyter notebook:

!pip install p2j

Then, using the command line, go the corresponding directory where the file is located and execute:

python p2j <myfile.py> -t <myfile.ipynb> 
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
Qazi Basheer
  • 127
  • 1
  • 2
-1

Using the command line goto the directory where the myfile.py file is located and execute (Example): C:\MyDir\pip install p2j

Then execute: C:\MyDir\p2j myfile.py -t myfile.ipynb

Qazi Basheer
  • 127
  • 1
  • 2