# nbio


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Reading a notebook

A notebook is just a json file.

<details open class="code-fold">
<summary>Exported source</summary>

``` python
def _read_json(self, encoding=None, errors=None):
    return loads(Path(self).read_text(encoding=encoding, errors=errors))
```

</details>

``` python
minimal_fn = Path('../tests/minimal.ipynb')
minimal_txt = AttrDict(_read_json(minimal_fn))
```

It contains two sections, the `metadata`…:

``` python
minimal_txt.metadata
```

    {'solveit_dialog_mode': 'learning', 'solveit_ver': 2}

…and, more importantly, the `cells`:

``` python
minimal_txt.cells
```

    [{'cell_type': 'markdown',
      'id': '801558df',
      'metadata': {},
      'source': ['## A minimal notebook']},
     {'cell_type': 'code',
      'execution_count': None,
      'id': 'e2147a69',
      'metadata': {'time_run': '2026-01-04T20:52:49.901559+00:00'},
      'outputs': [{'data': {'text/plain': ['2']},
        'execution_count': 0,
        'metadata': {},
        'output_type': 'execute_result'}],
      'source': ['# Do some arithmetic\n', '1+1']}]

The second cell here is a `code` cell, however it contains no outputs,
because it hasn’t been executed yet. To execute a notebook, we first
need to convert it into a format suitable for `nbclient` (which expects
some `dict` keys to be available as attrs, and some available as regular
`dict` keys). Normally, `nbformat` is used for this step, but it’s
rather slow and inflexible, so we’ll write our own function based on
`fastcore`’s handy
[`dict2obj`](https://fastcore.fast.ai/xtras.html#dict2obj), which makes
all keys available as both attrs *and* keys.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L25"
target="_blank" style="float:right; font-size:smaller">source</a>

### NbCell

``` python

def NbCell(
    idx, cell
):

```

*`dict` subclass that also provides access to keys as attrs, and has a
pretty markdown repr*

We use an [`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict)
subclass which has some basic functionality for accessing notebook
cells.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L55"
target="_blank" style="float:right; font-size:smaller">source</a>

### dict2nb

``` python

def dict2nb(
    js:NoneType=None, kwargs:VAR_KEYWORD
):

```

*Convert dict `js` to an
[`AttrDict`](https://fastcore.fast.ai/basics.html#attrdict),*

We can now convert our JSON into this `nbclient`-compatible format,
which pretty prints the source code of cells in notebooks.

``` python
minimal = dict2nb(minimal_txt)
cell = minimal.cells[1]
cell
```

``` python
{ 'cell_type': 'code',
  'execution_count': None,
  'id': 'e2147a69',
  'idx_': 1,
  'metadata': {'time_run': '2026-01-04T20:52:49.901559+00:00'},
  'outputs': [ { 'data': {'text/plain': ['2']},
                 'execution_count': 0,
                 'metadata': {},
                 'output_type': 'execute_result'}],
  'source': '# Do some arithmetic\n1+1'}
```

The abstract syntax tree of source code cells is available in the
`parsed_` property:

``` python
cell.parsed_(), cell.parsed_()[0].value.op
```

    ([<ast.Expr>], <ast.Add>)

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L62"
target="_blank" style="float:right; font-size:smaller">source</a>

### read_nb

``` python

def read_nb(
    path
):

```

*Return notebook at `path`*

This reads the JSON for the file at `path` and converts it with
[`dict2nb`](https://fastcore.fast.ai/nbio.html#dict2nb). For instance:

``` python
minimal = read_nb(minimal_fn)
str(minimal.cells[0])
```

    "{'cell_type': 'markdown', 'id': '801558df', 'metadata': {}, 'source': '## A minimal notebook', 'idx_': 0}"

The file name read is stored in `path_`:

``` python
minimal.path_
```

    '../tests/minimal.ipynb'

## Creating a notebook

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L69"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_cell

``` python

def mk_cell(
    text, # `source` attr in cell
    cell_type:str='code', # `cell_type` attr in cell
    kwargs:VAR_KEYWORD
):

```

*Create an [`NbCell`](https://fastcore.fast.ai/nbio.html#nbcell)
containing `text`*

``` python
mk_cell('print(1)', execution_count=0)
```

``` python
{ 'cell_type': 'code',
  'directives_': {},
  'execution_count': 0,
  'id': '187ffbbf',
  'idx_': 0,
  'metadata': {},
  'outputs': [],
  'source': 'print(1)'}
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L81"
target="_blank" style="float:right; font-size:smaller">source</a>

### new_nb

``` python

def new_nb(
    cells:NoneType=None, meta:NoneType=None, nbformat:int=4, nbformat_minor:int=5
):

```

*Returns an empty new notebook*

Use this function when creating a new notebook. Useful for when you
don’t want to create a notebook on disk first and then read it.

## Writing a notebook

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L87"
target="_blank" style="float:right; font-size:smaller">source</a>

### nb2dict

``` python

def nb2dict(
    d, k:NoneType=None
):

```

*Convert parsed notebook to `dict`*

This returns the exact same dict as is read from the notebook JSON.

``` python
minimal_fn = Path('../tests/minimal.ipynb')
minimal = read_nb(minimal_fn)
minimal_dict = _read_json(minimal_fn)
assert minimal_dict==nb2dict(minimal)
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L95"
target="_blank" style="float:right; font-size:smaller">source</a>

### nb2str

``` python

def nb2str(
    nb
):

```

*Convert `nb` to a `str`*

To save a notebook we first need to convert it to a `str`:

``` python
print(nb2str(minimal)[:45])
```

    {
     "cells": [
      {
       "cell_type": "markdown",

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L101"
target="_blank" style="float:right; font-size:smaller">source</a>

### write_nb

``` python

def write_nb(
    nb, path
):

```

*Write `nb` to `path`*

This returns the exact same string as saved by Jupyter.

``` python
tmp = Path('tmp.ipynb')
try:
    minimal_txt = minimal_fn.read_text()
    write_nb(minimal, tmp)
    test_eq(minimal_txt, tmp.read_text())
finally: tmp.unlink()
```

Here’s how to put all the pieces of `fastcore.nbio` together:

``` python
nb = new_nb([mk_cell('print(1)')])
path = Path('test.ipynb')
write_nb(nb, path)
nb2 = read_nb(path)
print(nb2.cells)
path.unlink()
```

    [{'cell_type': 'code', 'execution_count': 0, 'id': 'b1d7a3b5', 'metadata': {}, 'outputs': [], 'source': 'print(1)', 'idx_': 0}]

## Output rendering

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L113"
target="_blank" style="float:right; font-size:smaller">source</a>

### preferred_out

``` python

def preferred_out(
    data, html1st:bool=True, include_imgs:bool=False
):

```

*Call self as a function.*

[`preferred_out`](https://fastcore.fast.ai/nbio.html#preferred_out)
selects the best MIME type from an output’s `data` dict, preferring HTML
by default:

``` python
data = dict(text_plain=['42'], **{'text/html': ['<b>42</b>'], 'text/plain': ['42']})
preferred_out(data), preferred_out(data, html1st=False)
```

    (('text/html', ['<b>42</b>']), ('text/html', ['<b>42</b>']))

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L123"
target="_blank" style="float:right; font-size:smaller">source</a>

### apply_controls

``` python

def apply_controls(
    text
):

```

*Apply nd o text, returning processed result*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L151"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_error

``` python

def mk_error(
    traceback, ename:str='', evalue:str=''
):

```

*Helper to create an error output dict*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L147"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_display

``` python

def mk_display(
    metadata:NoneType=None, data:VAR_KEYWORD
):

```

*Helper to create a display_data output dict*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L143"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_result

``` python

def mk_result(
    metadata:NoneType=None, data:VAR_KEYWORD
):

```

*Helper to create an execute_result output dict*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L136"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_stream

``` python

def mk_stream(
    name, text
):

```

*Helper to create an output stream dict*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L156"
target="_blank" style="float:right; font-size:smaller">source</a>

### concat_streams

``` python

def concat_streams(
    outputs
):

```

*Concatenate stream outputs by name (stdout/stderr), preserving
execute_result at end*

[`concat_streams`](https://fastcore.fast.ai/nbio.html#concat_streams)
merges consecutive stream outputs by name and moves execute_results to
the end, like standard jupyter output rendering:

``` python
outs = [mk_result(text_plain=['42']),
        mk_stream('stdout', 'hello '), mk_stream('stdout', 'world\n'), mk_stream('stderr', 'warn\n')]
outs
```

    [{'output_type': 'execute_result',
      'data': {'text/plain': ['42']},
      'metadata': {}},
     {'output_type': 'stream', 'name': 'stdout', 'text': 'hello '},
     {'output_type': 'stream', 'name': 'stdout', 'text': 'world\n'},
     {'output_type': 'stream', 'name': 'stderr', 'text': 'warn\n'}]

``` python
concat_streams(outs)
```

    [{'output_type': 'stream', 'name': 'stdout', 'text': 'hello world\n'},
     {'output_type': 'stream', 'name': 'stderr', 'text': 'warn\n'},
     {'output_type': 'execute_result',
      'data': {'text/plain': ['42']},
      'metadata': {}}]

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L179"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_output

``` python

def render_output(
    out
):

```

*Convert a single output dict to an HTML string*

``` python
print(render_output(mk_result(text_plain=['42'])))
```

    <pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">42</code></pre>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L197"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_outputs

``` python

def render_outputs(
    outputs
):

```

*Render a full list of outputs, concatenating streams first.*

``` python
print(render_outputs(outs))
```

    <pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">hello world
    </code></pre>
    <pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">warn
    </code></pre>
    <pre class="!border-0 !rounded-none !my-0 !p-0"><code class="nohighlight">42</code></pre>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L214"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_text

``` python

def render_text(
    outputs, html1st:bool=False
):

```

*Render notebook outputs to concise text, using XML-ish tags when
multiple outputs are present.*

A single output renders as plain text directly:…

``` python
print(render_text([outs[0]]))
```

    42

…but multiple outputs get wrapped in XML-ish tags so they stay
distinguishable. For rich outputs we prefer markdown by default; pass
`html1st=True` to prefer HTML instead. Outputs without a text
representation (e.g. images alone) render as empty.

``` python
print(render_text(outs))
```

    <stdout>
    hello world
    </stdout>
    <stderr>
    warn
    </stderr>
    <execute_result>
    42
    </execute_result>

``` python
disp = [mk_display(text_html=['<b>42</b>'], text_markdown=['**42**'], text_plain=['42'])]
test_eq(render_text(disp), '**42**')
test_eq(render_text(disp, html1st=True), '<b>42</b>')

test_eq(render_text([mk_display(image_png='abc')]), '')
test_eq(render_text([mk_error('oops')]), 'oops')
```

``` python
nb.cells[0].outputs = [mk_stream('stdout', '1\n')]
```

## Notebook class

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L232"
target="_blank" style="float:right; font-size:smaller">source</a>

### cells2xml

``` python

def cells2xml(
    cells, wrap:function=_f, ids:bool=True, incl_out:bool=True, kw:VAR_KEYWORD
):

```

*Convert notebook cells to XML format*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L224"
target="_blank" style="float:right; font-size:smaller">source</a>

### cell2xml

``` python

def cell2xml(
    cell, ids:bool=True, incl_out:bool=True
):

```

*Convert NbCell to concise XML format*

We can view any notebook as concise XML. For instance, our minimal
notebook:

``` python
print(cells2xml(nb.cells, incl_out=False))
```

    <nb><code id="b1d7a3b5">print(1)</code></nb>

``` python
repr(cell2xml(nb.cells[0], incl_out=False))
```

    '<code id="b1d7a3b5">print(1)</code>'

``` python
repr(cell2xml(nb.cells[0], incl_out=True))
```

    '<code id="b1d7a3b5"><source>print(1)<out>1\n</out></code>'

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L238"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook

``` python

def Notebook(
    nb, path:NoneType=None
):

```

*Read, query, and edit Jupyter notebooks*

We can now open a notebook and access its metadata and cells:

``` python
nbo = Notebook.open(minimal_fn)
list(nbo.meta), len(nbo.cells), len(nbo)
```

    (['solveit_dialog_mode', 'solveit_ver'], 2, 2)

``` python
nbo.path.name
```

    'minimal.ipynb'

``` python
[o.id for o in nbo]
```

    ['801558df', 'e2147a69']

``` python
'e2147a69' in nbo, 'nonexistent' in nbo
```

    (True, False)

Notebooks’ repr is their xml:

``` python
nbo
```

    <nb path="/Users/jhoward/aai-ws/fastcore/tests/minimal.ipynb"><markdown id="801558df">## A minimal notebook</markdown><code id="e2147a69"><source># Do some arithmetic
    1+1<out>2</out></code></nb>

You can also get a more concise version that doesn’t include outputs or
the full path:

``` python
print(nbo.concise)
```

    <nb path="minimal.ipynb"><markdown id="801558df">## A minimal notebook</markdown><code id="e2147a69"># Do some arithmetic
    1+1</code></nb>

Cells can be accessed by integer index or by their string id:

``` python
nbo[0].source
```

    '## A minimal notebook'

``` python
nbo['e2147a69'].source
```

    '# Do some arithmetic\n1+1'

You can directly set a cell’s source by id or index:

``` python
nbo['e2147a69'] = '2+2'
nbo['e2147a69'].source
```

    '2+2'

You can also update outputs and metadata directly on a cell:

``` python
nbo['e2147a69'].outputs = [{'output_type': 'execute_result', 'data': {'text/plain': ['4']}}]
nbo['e2147a69'].outputs
```

    [{'output_type': 'execute_result', 'data': {'text/plain': ['4']}}]

``` python
nbo['e2147a69'].metadata['custom'] = True
nbo['e2147a69'].metadata
```

``` python
{'custom': True, 'time_run': '2026-01-04T20:52:49.901559+00:00'}
```

The `add` method inserts a new cell at a given position (defaulting to
the end):

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L271"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook.add

``` python

def add(
    source, cell_type:str='code', idx:NoneType=None, after:NoneType=None, before:NoneType=None, kwargs:VAR_KEYWORD
):

```

*Add a new cell with `source` at `idx` (default: end), or
`after`/`before` a cell id*

``` python
nbo.add('print("hello")')
nbo.add('# A heading', cell_type='markdown', idx=0)
len(nbo), nbo[0].source
```

    (4, '## A minimal notebook')

Cells can also be inserted relative to an existing cell by id:

``` python
cid = nbo[0].id
nbo.add('# After first', cell_type='markdown', after=cid)
nbo.add('# Before first', cell_type='markdown', before=cid)
[c.source for c in nbo[:3]]
```

    ['# Before first', '## A minimal notebook', '# After first']

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L285"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook.md

``` python

def md(
    source, idx:NoneType=None, after:NoneType=None, before:NoneType=None, kwargs:VAR_KEYWORD
):

```

*Add a new cell with `source` at `idx` (default: end), or
`after`/`before` a cell id*

`md` is a shortcut to `add(..., cell_type='markdown')`

``` python
nbo.md('A note')
len(nbo), nbo[-1].cell_type
```

    (7, 'markdown')

You can delete by id or index:

``` python
prev_len = len(nbo)
del nbo[0]
len(nbo) == prev_len - 1
```

    True

The `find` method searches cell sources by regex, returning matching
cells:

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L291"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook.find

``` python

def find(
    pat, cell_type:NoneType=None
):

```

*Find cells with source matching regex `pat`*

``` python
nbo.find(r'\d\+\d', cell_type='code')
```

    [{'cell_type': 'code',
      'execution_count': None,
      'id': 'e2147a69',
      'metadata': {'time_run': '2026-01-04T20:52:49.901559+00:00', 'custom': True},
      'outputs': [{'output_type': 'execute_result',
        'data': {'text/plain': ['4']}}],
      'source': '2+2',
      'idx_': 1}]

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L297"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook.move

``` python

def move(
    src_ids, after:NoneType=None, before:NoneType=None
):

```

*Move cells with `src_ids` after/before a cell id, or to end*

Cells can be moved by id, either relative to another cell or to the end:

``` python
nbo = Notebook.open(minimal_fn)
c0,c1 = nbo[0].id,nbo[1].id
nbo.move(c1, before=c0)
[c.id for c in nbo] == [c1, c0]
```

    True

Use `save` to write to disk:

``` py
nbo.save('path.ipynb')
```

If no path is passed, the path used in `open()` will be re-used.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L309"
target="_blank" style="float:right; font-size:smaller">source</a>

### Notebook.view

``` python

def view(
    id, nums:bool=True
):

```

*Show cell source with optional line numbers*

The [`view`](https://fastcore.fast.ai/tools.html#view) method displays a
cell’s source with optional line numbers:

``` python
print(nbo.view('e2147a69'))
```

         1 │ # Do some arithmetic
         2 │ 1+1
