# 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#L23"
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#L53"
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#L60"
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#L67"
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': '784d2277',
  'idx_': 0,
  'metadata': {},
  'outputs': [],
  'source': 'print(1)'}
```

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

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L79"
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#L85"
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#L93"
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#L99"
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': '2d215f3a', 'metadata': {}, 'outputs': [], 'source': 'print(1)', 'idx_': 0}]

## Notebook class

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

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L121"
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#L113"
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))
```

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

``` python
repr(cell2xml(nb.cells[0]))
```

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

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

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/nbio.py#L127"
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>[{'data': {'text/plain': ['2']}, 'execution_count': 0, 'metadata': {}, 'output_type': 'execute_result'}]</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#L160"
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#L174"
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#L180"
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#L186"
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#L198"
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
