[Go to site: main page, start]

process

A notebook processor

Special comments at the start of a cell provide information to nbdev about how to process it. The parsing primitives live in fastcore.nbio (the langs table, nb_lang, first_code_ln, and NbCell’s directives property and remove_directives method) and are available here via its re-exports; this module builds the notebook processing pipeline on top of them.


source

opt_set

def opt_set(
    var, newval
):

newval if newval else var


source

instantiate

def instantiate(
    x, **kwargs
):

Instantiate x if it’s a type


source

NBProcessor

def NBProcessor(
    path:NoneType=None, procs:NoneType=None, nb:NoneType=None, debug:bool=False, rm_directives:bool=True,
    process:bool=False
):

Process cells and nbdev comments in a notebook

Cell processors can be callables (e.g regular functions), in which case they are called for every cell (set a cell’s source to None to remove the cell):

everything_fn = '../../tests/01_everything.ipynb'

def print_execs(cell):
    if 'exec' in cell.source: print(cell.source)

NBProcessor(everything_fn, print_execs).process()
---
title: Foo
execute:
  echo: false
---
exec("o_y=1")
exec("p_y=1")
_all_ = [o_y, 'p_y']

Directives are put in a cell attribute directives_ as a dictionary keyed by directive name, with the raw value string as each value ('' for a bare directive). When a processor method receives a directive, the value is whitespace-split into positional arguments:

Notebook-level directives can be stored in the notebook metadata, as a dict under an nbdev key: they are merged into the first code cell’s directives_, so notebook-scope directives like default_exp need no cell to live in. A directive of the same name anywhere in a cell takes priority.

_nbm = dict2nb(dict(cells=[mk_cell('# a note', 'markdown'), mk_cell('1+1')],
                    metadata=dict(nbdev=dict(default_exp='core')), nbformat=4, nbformat_minor=5))
NBProcessor(nb=_nbm)
test_eq(_nbm.cells[1].directives_, {'default_exp': 'core'})

_nbm = dict2nb(dict(cells=[mk_cell('#| default_exp: other\n1+1')],
                    metadata=dict(nbdev=dict(default_exp='core')), nbformat=4, nbformat_minor=5))
NBProcessor(nb=_nbm)
test_eq(_nbm.cells[0].directives_, {'default_exp': 'other'})
def printme_func(cell):
    if cell.directives_ and 'printme' in cell.directives_: print(cell.directives_['printme'])

NBProcessor(everything_fn, printme_func).process()
testing

However, a more convenient way to handle comment directives is to use a class as a processor, and include a method in your class with the same name as your directive, surrounded by underscores:

class _PrintExample:
    def _printme_(self, cell, to_print): print(to_print)

NBProcessor(everything_fn, _PrintExample()).process()
testing

In the case that your processor supports just one comment directive, you can just use a regular function, with the same name as your directive, but with an underscore appended – here printme_ is identical to _PrintExample above:

def printme_(cell, to_print): print(to_print)

NBProcessor(everything_fn, printme_).process()
testing
NBProcessor(everything_fn, _PrintExample()).process()
testing

source

Processor

def Processor(
    nb
):

Base class for processors

For more complex behavior, inherit from Processor, and override one of more of begin() (called before any cells are processed), cell() (called for each cell), and end() (called after all cells are processed). You can also include comment directives (such as the _printme example above) in these subclasses. Subclasses will automatically have access to self.nb, containing the processed notebook.

class CountCellProcessor(Processor):
    def begin(self):
        print(f"First cell:\n{self.nb.cells[0].source}")
        self.count=0
    def cell(self, cell):
        if cell.cell_type=='code': self.count += 1
    def end(self): print(f"* There were {self.count} code cells")
NBProcessor(everything_fn, CountCellProcessor).process()
First cell:
---
title: Foo
execute:
  echo: false
---
* There were 26 code cells