Things you might not know you can do with nbdev
Exploring some of the less well known nbdev features.
- Use magic flags in place of special comments
- Export a if __name__ == "__main__" clause
- Use multiple flags in a cell
- Remove all special comments / flags from the docs
- Write to a module that is created "later"
- Exclude a single notebook from doc builds
- Hide markdown cells from the docs
- Put any number of test flags in a cell
- Create code coverage reports
- Use %nbdev_add2all in place of _all_
- Use code completion when adding to __all__
- Import anything needed by show_doc
- Use %nbdev_show_doc to
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.
if __name__ == "__main__"
clause
Export a 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
- If nbdev finds both
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 createcore.py
- Which contains
-
01_data.ipynb
- Which contains
%nbdev_default_export data
to createdata.py
- Which contains
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
.
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 testcore.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.
%nbdev_add2all
in place of _all_
Use 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_add2all
s 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
__all__
Use code completion when adding to 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.
%nbdev_show_doc
to
Use 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 theDataLoaders
show_doc
calls could be written with one%nbdev_show_doc
without having to passname=
:%nbdev_show_doc DataLoaders . __getitem__ train valid train_ds valid_ds
Or even more succinctly as:
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