What follows is a collection of things I've learned while trying to help answer questions on the forum, fix issues and introduce magic flags. Hope it helps to know that you can ...

Use magic flags in place of special comments

Using nbdev 0.2.20 from pypi, you can start using magic flags like %nbdev_export in place of special comments like #export.

Magic flags are covered by the nbdev docs and nbdev is getting a little %magic explains how to migrate existing projects to use magic flags etc.

Export a if __name__ == "__main__" clause

I'd recommend using console scripts wherever you can - but if you want your modules to run something when invoked directly, if __name__ == "__main__" could be the answer.

Anything you %nbdev_export gets written to your .py - so you could write a cell like:

%nbdev_export
try: from nbdev.imports import IN_NOTEBOOK
except: IN_NOTEBOOK=False

if __name__ == "__main__" and not IN_NOTEBOOK:
    print('Running "command line" logic ...')

Note:

  • We need the not IN_NOTEBOOK check because __name__ == "__main__" when running in a notebook
  • We put the IN_NOTEBOOK import in a try catch so that your module doesn't have a dependency on nbdev

Use multiple flags in a cell

Most flags can be used in combination with other flags. Here's a couple of combinations that might be useful:

  • You can hide input and collapse output with %nbdev_hide_input and %nbdev_collapse_output
  • You can hide a cell and specify a test flag with %nbdev_hide and %nbdev_slow_test

Note: You can't put multiple flags on the same line.

There are a few things that are not supported yet, such as:

  • You can collapse input or output but not both
    • If nbdev finds both %nbdev_collapse_input and %nbdev_collapse_output it will ignore %nbdev_collapse_output

Remove all special comments / flags from the docs

You might have seen special comments in your docs. This can happen if the special comment is not the 1st thing in the cell:

# hiding the input of this cell as the output is more important
# collapse-hide
# let's run some code ...
some_code('here')

This ↑ cell would get converted to ↓ in the docs.

# collapse-hide
# let's run some code ...
some_code('here')

Converting your project to use magic flags with nbdev_upgrade would change the cell to:

# hiding the input of this cell as the output is more important
%nbdev_collapse_input
# let's run some code ...
some_code('here')

Which gets converted to ↓ in the docs.

# let's run some code ...
some_code('here')

Write to a module that is created "later"

Lets say we have 2 notebooks:

  • 00_core.ipynb
    • Which contains %nbdev_default_export core to create core.py
  • 01_data.ipynb
    • Which contains %nbdev_default_export data to create data.py

It used to be the case that writing a cell in 00_core.ipynb that exports to data would cause problems:

%nbdev_export data
def some_data_func():
    that_we_also_want_to_use in ['the core notebook']

nbdev is totally happy with this now.

This change has also made it possible to use the same %nbdev_default_export in multiple notebooks. I'm not recommending that you do this but ... you can write to the same module from multiple notebooks.

Exclude a single notebook from doc builds

You can do simple exclusions with glob. The following command include all .ipynb files unless they start with 2:

nbdev_build_docs --fname=[!2]*.ipynb

It's worth remembering that nbdev will ignore any file that starts with an underscore - I use this all the time with _tmp in .gitignore.

Hide markdown cells from the docs

Apart from #hide and %nbdev_hide, nbdev will ignore flags that are not in code cells. This means that you can use markdown cells for "developer-only" details and #hide them from the HTML docs.

Put any number of test flags in a cell

Lets say you have tst_flags = slow|cuda in settings.ini so that you can flag test as being slow or needing a GPU. If you need to flag a test as being both slow and needing a GPU, you can:

%nbdev_slow_test
%nbdev_cuda_test
tst_result=long_running_fn()

Currently, only one test flag can be applied to all cells in a notebook. So if you had ↓ only the slow test flag would be picked up:

%nbdev_slow_test all
%nbdev_cuda_test all

Create code coverage reports

We can run tests in parallel and get coverage with pytest-cov.

If you'd like to try this:

  • install pytest-cov and its dependencies
  • copy test_nbs.py to your nbdev project
  • then run pytest --cov=[your lib name]

Feel free to join the discussion (o:

When running tests, nbdev runs all cells in a notebook

Currently, when you nbdev_test_nbs, nbdev will run your notebooks from top to bottom.

A consequence of this is that if you have

  • 00_core.ipynb that contains %nbdev_default_export core,
  • nbdev_test_nbs --fname 00_core.ipynb will not test core.py.

While test_nbs.py might look a little complicated, it means that nbdev_test_nbs --fname 00_core.ipynb will test core.py and you get accurate coverage data.

Code coverage tells you what you definitely haven't tested, not what you have

I really like this ↑ quote from Mark Simpson

While there are lots of good uses of coverage reports, I've seen them do more harm than good to projects when used as a strict quality measure. I really like the fastai style of writing tests that show how code can be used, then focusing on functional coverage.

Use %nbdev_add2all in place of _all_

Sometimes objects are not picked to be automatically added to the __all__ of the module so you will need to add them manually. To do so, create an exported cell with the following code %nbdev_add2all "name", "name2"

Please note:

  • elements in %nbdev_add2all can be space and/or comma separated and don't have to be quoted
    # 'func', 'func2' and 'func3' will be added to `__all__`
    %nbdev_add2all func, func2 func3
    # you'll see warnings if any unquoted function names can't be found
    
  • elements on a new line will not be added to __all__
    # 'func2' won't get added to `__all__`
    %nbdev_add2all 'func',
          'func2'
    
  • only the first %nbdev_add2all in a cell will get picked up
    %nbdev_add2all ['func']
    # `func2` won't get added to `__all__`
    %nbdev_add2all ['func2']
    
  • but you can have any number of %nbdev_add2alls in a notebook by putting them in different cells.

I'm not recommending that you do this but ... as long as you have only one _all_ or %nbdev_add2all in an exported cell, you can put any other code you like in this cell.

For more exmaples, please see this demo page

Use code completion when adding to __all__

It used to be the case that using unquoted names in _all_ caused problems:

%nbdev_export
_all_ = [func, func2 func3]

This ↑ will work fine now as names get quoted when writing to your library ↓:

__all__ = ['other','things','you','have','exported','func','func2','func3']

Import anything needed by show_doc

When building docs, nbdev runs all show_doc and %nbdev_show_doc cells so that stale output doesn't make it into your docs.

So that names are available when making show_doc calls, nbdev runs cells containing:

  • a "library import" (zero indent import from current library) e.g. from LIB_NAME.core import *

If running these cells raises an exception, the build will stop.

nbdev also runs cells containing zero indented imports. e.g.

  • from module import * or
  • import module

If running these cells raises an exception, the build will not stop.

If you need to show_doc something, please make sure it's imported via a cell that does not depend on previous cells being run. The easiest way to do this is to use a cell that contains nothing but imports.

Use %nbdev_show_doc to

For me, one of the best things about show_doc is not having to use it (o: because nbdev automatically adds show_doc for exported functions and classes.

To make it possible to do everything via magic flags and provide a few shortcuts, we've added %nbdev_show_doc. While show_doc will most often be the best choice, here are a few ways in which %nbdev_show_doc can help:

Here are a few lines from 00_torch_core.ipynb:

show_doc(TitledInt, title_level=3)
show_doc(TitledStr, title_level=3)
show_doc(TitledFloat, title_level=3)

That could be written with one %nbdev_show_doc:

%nbdev_show_doc TitledInt,TitledStr,TitledFloat,title_level=3

Here are some of the DataLoaders show_doc calls from 03_data.core.ipynb:

show_doc(DataLoaders.train, name="DataLoaders.train")
show_doc(DataLoaders.valid, name="DataLoaders.valid")

This ↑ is not typical fastai - which might make it a good time to quote Sylvain:"I'm less convinced by %nbdev_show_doc since it mostly seems useful for calling show_doc on several functions at once and we usually insert markdown between two show_doc calls". Having said that (o: all of the DataLoaders show_doc calls could be written with one %nbdev_show_doc without having to pass name=:

%nbdev_show_doc DataLoaders . __getitem__ train valid train_ds valid_ds

Or even more succinctly as:

show_doc with wildcard

In the example above ↑

  • The * tells nbdev_show_doc to show all public members but ...
  • we dont show the output for all members here as it's quite a long list

For more examples, please see this demo page