{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_input": false
},
"outputs": [],
"source": [
"#default_exp tutorial\n",
"from nbdev.showdoc import show_doc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# nbdev tutorial\n",
"\n",
"> A step by step guide\n",
"\n",
"- image: images/nbdev_source.gif"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"nbdev is a system for *exploratory programming*. See the [nbdev launch post](https://www.fast.ai/2019/12/02/nbdev/) for information about what that means. In practice, programming in this way can feel very different to the kind of programming many of you will be familiar with, since we've mainly be taught coding techniques that are (at least implicitly) tied to the underlying tools we have access to. I've found that programming in a \"notebook first\" way can make me 2-3x more productive than I was before (when we used vscode, Visual Studio, vim, PyCharm, and similar tools).\n",
"\n",
"In this tutorial, I'll try to get you up and running with the basics of the nbdev system as quickly and easily as possible. You can also watch this video in which I take you through the tutorial, step by step (to view full screen, click the little square in the bottom right of the video; to view in a separate Youtube window, click the Youtube logo):"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set up Your Jupyter Server"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Jupyter Environment"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To complete this tutorial, you'll need a Jupyter Notebook Server configured on your machine. If you have not installed Jupyter before, you may find the [Anaconda Individual Edition](https://www.anaconda.com/products/individual) the simplest to install.\n",
"\n",
"If you already have experience with Jupyter, please note that everything in this tutorial must be run using the same kernel."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Install `nbdev`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No matter how you installed Jupyter, you'll need to manually install `nbdev`. There is not a `conda` package for `nbdev`, so you'll need to use `pip` to install it. And you'll need to do that from a terminal window.\n",
"\n",
"Jupyter notebook has a terminal window available, so we'll use that:\n",
"1. Start `jupyter notebook`\n",
"2. From the \"New\" dropdown on the right side, choose `Terminal`.\n",
"3. Enter \"`python -m pip install nbdev`\"\n",
"\n",
"When the command completes, you're ready to go."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set up Repo"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Template"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To create your new project repo, click here: [nbdev template](https://github.com/fastai/nbdev_template/generate) (you need to be logged in to GitHub for this link to work). Fill in the requested info and click *Create repository from template*.\n",
"\n",
"**NB:** The name of your project will become the name of the Python package generated by nbdev. For that reason, it is a good idea to pick a short, all-lowercase name with _no dashes_ between words (underscores are allowed).\n",
"\n",
"Now, open your terminal, and clone the repo you just created."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Github pages"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The nbdev system uses [jekyll](https://jekyllrb.com/) for documentation. Because [GitHub Pages supports Jekyll](https://help.github.com/en/github/working-with-github-pages/setting-up-a-github-pages-site-with-jekyll), you can host your site for free on [Github Pages](https://pages.github.com/) without any additional setup, so this is the approach we recommend (but it's not required; any jekyll hosting will work fine).\n",
"\n",
"To enable Github Pages in your project repo, click *Settings* in Github, then scroll down to *Github Pages*, and set \"Source\" to *Master branch /docs folder*. Once you've saved, if you scroll back down to that section, Github will have a link to your new website. Copy that URL, and then go back to your main repo page, click \"edit\" next to the description and paste the URL into the \"website\" section. While you're there, go ahead and put in your project description too."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Edit settings.ini"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, edit the `settings.ini` file in your cloned repo. This file contains all the necessary information for when you'll be ready to package your library. The basic structure (that can be personalized provided you change the relevant information in `settings.ini`) is that the root of the repo will contain your notebooks, the `docs` folder will contain your auto-generated docs, and a folder with a name you select will contain your auto-generated modules."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You'll see these commented out lines in `settings.ini`. Uncomment them, and set each value as needed.\n",
"\n",
"```\n",
"# lib_name = your_project_name\n",
"# user = your_github_username\n",
"# description = A description of your project\n",
"# keywords = some keywords\n",
"# author = Your Name\n",
"# author_email = email@example.com\n",
"# copyright = Your Name or Company Name\n",
"```\n",
"\n",
"We'll see some other settings we can change later."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install git hooks"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Jupyter Notebooks can cause challenges with git conflicts, but life becomes much easier when you use `nbdev`. As a first step, run `nbdev_install_git_hooks` in the terminal from your project folder. This will set up git hooks which will remove metadata from your notebooks when you commit, greatly reducing the chance you have a conflict.\n",
"\n",
"But if you do get a conflict later, simply run `nbdev_fix_merge filename.ipynb`. This will replace any conflicts in cell outputs with your version, and if there are conflicts in input cells, then both cells will be included in the merged file, along with standard conflict markers (e.g. `=====`). Then you can open the notebook in Jupyter and choose which version to keep."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Edit 00_core.ipynb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, run `jupyter notebook`, and click `00_core.ipynb` (you don't *have* to start your notebook names with a number like we do here; but we find it helpful to show the order you've created your project in). You'll see something that looks a bit like this:\n",
"\n",
"```python\n",
"#default_exp core\n",
"```\n",
"\n",
"**module name here**\n",
"\n",
"> API details.\n",
"\n",
"Let's explain what these special cells mean."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Module name and summary"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The markdown cell uses special syntax to define the title and summary of your module. Feel free to replace \"module name here\" with a title and \"API details.\" with a summary for your module.\n",
"\n",
"This cell can optionally be used to define jekyll metadata, see `get_metadata` for more details."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Add a function"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's add a function to this notebook, e.g.:\n",
"\n",
"```python\n",
"#export\n",
"def say_hello(to):\n",
" \"Say hello to somebody\"\n",
" return f'Hello {to}!'\n",
"```\n",
"\n",
"Notice how it includes `#export` at the top - this means it will be included in your module, and documentation. The documentation will look like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"def say_hello(to):\n",
" \"Say hello to somebody\"\n",
" return f'Hello {to}!'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Add examples and tests"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It's a good idea to give an example of your function in action. Just include regular code cells, and they'll appear (with output) in the docs, e.g.:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Hello Sylvain!'"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"say_hello(\"Sylvain\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Examples can output plots, images, etc, and they'll all appear in your docs, e.g.:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from IPython.display import display,SVG"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"display(SVG(''))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also include tests:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"assert say_hello(\"Jeremy\")==\"Hello Jeremy!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You should also add markdown headings as you create your notebook; one benefit of this is that a table of contents will be created in the documentation automatically."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Build lib"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you can create your python module. To do so, just run `nbdev_build_lib` from the terminal when anywhere in your project folder.\n",
"\n",
"```\n",
"$ nbdev_build_lib\n",
"Converted 00_core.ipynb.\n",
"Converted index.ipynb.\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Edit index.ipynb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you're ready to create your documentation home page and readme file; these are both generated automatically from *index.ipynb*. So click on that to open it now.\n",
"\n",
"You'll see that there's already a line there to import your library - change it to use the name you selected in `settings.ini`. Then, add information about how to use your module, including some examples. Remember, these examples should be actual notebook code cells with real outputs."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Build docs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you can create your documentation. To do so, just run `nbdev_build_docs` from the terminal when anywhere in your project folder.\n",
"\n",
"```\n",
"$ nbdev_build_docs\n",
"converting: /home/jhoward/git/nbdev/nbs/00_core.ipynb\n",
"converting: /home/jhoward/git/nbdev/nbs/index.ipynb\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Commit to Github"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can now `git commit` and `git push`. Wait a minute or two for Github to process your commit, and then head over to the Github website to look at your results."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### CI"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Back in your project's Github main page, click where it says *1 commit* (or *2 commits* or whatever). Hopefully, you'll see a green checkmark next to your latest commit. That means that your documentation site built correctly, and your module's tests all passed! This is checked for you using *continuous integration (CI)* with [GitHub actions](https://github.com/features/actions). This does the following:\n",
"\n",
"- Checks the notebooks are readable\n",
"- Checks the notebooks have been cleaned of needless metadata to avoid merge conflicts\n",
"- Checks there is no diff between the notebooks and the exported library\n",
"- Runs the tests in your notebooks\n",
"\n",
"Edit the file `.github/workflows/main.yml` if you need to modify any of the CI steps.\n",
"\n",
"If you have a red cross, that means something failed. Click on the cross, then click *Details*, and you'll be able to see what failed."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### View docs and readme"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Once everything is passing, have a look at your readme in Github. You'll see that your `index.ipynb` file has been converted to a readme automatically.\n",
"\n",
"Next, go to your documentation site (e.g. by clicking on the link next to the description that you created earlier). You should see that your index notebook has also been used here.\n",
"\n",
"Congratulations, the basics are now all in place! Let's continue and use some more advanced functionality."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add a class"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create a class in `00_core.ipynb` as follows:\n",
"\n",
"```python\n",
"#export\n",
"class HelloSayer:\n",
" \"Say hello to `to` using `say_hello`\"\n",
" def __init__(self, to): self.to = to\n",
" \n",
" def say(self):\n",
" \"Do the saying\"\n",
" return say_hello(self.to)\n",
"```\n",
"\n",
"This will automatically appear in the docs like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#export\n",
"class HelloSayer:\n",
" \"Say hello to `to` using `say_hello`\"\n",
" def __init__(self, to): self.to = to\n",
"\n",
" def say(self):\n",
" \"Do the saying\"\n",
" return say_hello(self.to)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Document with show_doc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, methods aren't automatically documented. To add method docs, use `show_doc`:\n",
"\n",
"```python\n",
"show_doc(HelloSayer.say)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'Markdown' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mshow_doc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHelloSayer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msay\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m~/git/nbdev/nbs/nbdev/showdoc.py\u001b[0m in \u001b[0;36mshow_doc\u001b[0;34m(elt, doc_string, name, title_level, disp, default_cls_level)\u001b[0m\n\u001b[1;32m 267\u001b[0m \u001b[0ms\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34mf'```\\n{s}\\n```'\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmonospace\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0madd_doc_links\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ms\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0melt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0mdoc\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 269\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mdisp\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mMarkdown\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdoc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 270\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdoc\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 271\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'Markdown' is not defined"
]
}
],
"source": [
"show_doc(HelloSayer.say)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And add some examples and/or tests:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"o = HelloSayer(\"Alexis\")\n",
"o.say()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add links with backticks"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice above there is a link from our new class documentation to our function. That's because we used backticks in the docstring:\n",
"```python\n",
" \"Say hello to `to` using `say_hello`\"\n",
"```\n",
"These are automatically converted to hyperlinks wherever possible. For instance, here are hyperlinks to `HelloSayer` and `say_hello` created using backticks."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set up autoreload"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since you'll be often updating your modules from one notebook, and using them in another, it's helpful if your notebook automatically reads in the new modules as soon as the python file changes. To make this happen, just add these lines to the top of your notebook:\n",
"\n",
"```\n",
"%load_ext autoreload\n",
"%autoreload 2\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Add in-notebook export cell"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It's helpful to be able to export all your modules directly from a notebook, rather than going to the terminal to do it. All nbdev commands are available directly from a notebook in Python. Add this line to any cell and run it to export your modules (I normally make this the last cell of my scripts).\n",
"\n",
"```python\n",
"notebook2script()\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run tests in parallel"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before you push to github or make a release, you might want to run all your tests. nbdev can run all your notebooks in parallel to check for errors. Just run `nbdev_test_nbs` in a terminal.\n",
"\n",
"```\n",
"(base) jhoward@usf3:~/git/nbdev$ nbdev_test_nbs\n",
"testing: /home/jhoward/git/nbdev/nbs/00_core.ipynb\n",
"testing: /home/jhoward/git/nbdev/nbs/index.ipynb\n",
"All tests are passing!\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## View docs locally"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you want to look at your docs locally before you push to Github, you can do so by running a jekyll server. First, install Jekyll by [following these steps](https://jekyllrb.com/docs/installation/ubuntu/). Then, install the modules needed for serving nbdev docs by `cd`ing to the `docs` directory, and typing `bundle install`. Finally, cd back to your repo root and type `make docs_serve`. This will launch a server on port 4000 (by default) which you can connect to with your browser to view your docs.\n",
"\n",
"If Github pages fails to build your docs, running locally with Jekyll is the easiest way to find out what the problem is."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set up prerequisites"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If your module requires other modules as dependencies, you can add those prerequisites to your `settings.ini` in the `requirements` section. The requirements should be separated by a space and if the module requires at least or at most a specific version of the requirement this may be specified here, too.\n",
"\n",
"For example if your module requires the `fastcore` module of at least version 1.0.5, the `torchvision` module of at most version 0.7 and any version of `matplotlib`, then the prerequisites would look like this:\n",
"```python\n",
"requirements = fastcore>=1.0.5 torchvision<0.7 matplotlib\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set up console scripts"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Behind the scenes, nbdev uses that standard package `setuptools` for handling installation of modules. One very useful feature of `setuptools` is that it can automatically create [cross-platform console scripts](https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-console-scripts-entry-point). nbdev surfaces this functionality; to use it, use the same format as `setuptools`, with whitespace between each script definition (if you have more than one).\n",
"\n",
"```\n",
"console_scripts = nbdev_build_lib=nbdev.cli:nbdev_build_lib\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test with editable install"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To test and use your modules in other projects, and use your console scripts (if you have any), the easiest approach is to use an [editable install](http://codumentary.blogspot.com/2014/11/python-tip-of-year-pip-install-editable.html). To do this, `cd` to the root of your repo in the terminal, and type:\n",
"```\n",
"pip install -e .\n",
"```\n",
"(Note that the trailing period is important.) Your module changes will be automatically picked up without reinstalling. If you add any additional console scripts, you will need to run this command again."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Upload to pypi"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you want people to be able to install your project by just typing `pip install your-project` then you need to upload it to [pypi](https://pypi.org/). The good news is, we've already created a fully pypi compliant installer for your project! So all you need to do is register at pypi (click \"Register\" on pypi) if you haven't previously done so, and then create a file called `~/.pypirc` with your login details. It should have these contents:\n",
"\n",
"```\n",
"[pypi]\n",
"username = your_pypi_username\n",
"password = your_pypi_password\n",
"```\n",
"\n",
"To upload your project to pypi, just type `make release` in your project root directory. Once it's complete, a link to your project on pypi will be printed.\n",
"\n",
"**NB**: `make release` will automatically increment the version number in `settings.py` before pushing a new release to pypi. If you don't want to do this, run `make pypi` instead."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install collapsible headings and toc2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are two jupyter notebook extensions that I highly recommend when working with projects like this. They are:\n",
"\n",
"- [Collapsible headings](https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/collapsible_headings/readme.html): This lets you fold and unfold each section in your notebook, based on its markdown headings. You can also hit left to go to the start of a section, and right to go to the end\n",
"- [TOC2](https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/toc2/README.html): This adds a table of contents to your notebooks, which you can navigate either with the `Navigate` menu item it adds to your notebooks, or the TOC sidebar it adds. These can be modified and/or hidden using its settings."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Look at nbdev \"source\" for more ideas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Don't forget that nbdev itself is written in nbdev! It's a good place to look to see how fast.ai uses it in practice, and get a few tips. You'll find the nbdev notebooks here in the [nbs folder](https://github.com/fastai/nbdev/tree/master/nbdev) on Github."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"jekyll": {
"keywords": "fastai",
"toc": "false"
},
"jupytext": {
"split_at_heading": true
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}