├── nbs
├── .nojekyll
├── explanations
│ ├── .nodoc
│ ├── .notest
│ ├── images
│ │ └── github-actions-pages.png
│ ├── index.qmd
│ ├── why_nbdev.ipynb
│ └── config.ipynb
├── tutorials
│ ├── .nodoc
│ ├── .notest
│ ├── images
│ │ ├── github-repo-empty.png
│ │ ├── jupyter-home-page.png
│ │ ├── github-actions-pages.png
│ │ ├── github-enable-pages.png
│ │ ├── marie-curie-notebook.jpg
│ │ ├── github-actions-initial.png
│ │ ├── github-create-new-repo.png
│ │ ├── jupyter-blank-terminal.png
│ │ ├── jupyter-friendly-conflict.png
│ │ ├── jupyter-unreadable-notebook.png
│ │ └── nbdev-hello-world-site-initial.png
│ ├── index.qmd
│ ├── docs_only.ipynb
│ ├── renderscript.qmd.py
│ ├── qmd_intro.qmd
│ └── pre_commit.ipynb
├── CNAME
├── .gitignore
├── favicon.png
├── images
│ ├── bom.png
│ ├── card.png
│ ├── favicon.png
│ ├── david-berg.jpeg
│ ├── overstory.png
│ ├── chris-lattner.png
│ ├── erik-gaasedelen.jpeg
│ ├── fernando-pérez.jpeg
│ ├── roxanna-pourzand.jpeg
│ ├── hugo-bowne-anderson.jpeg
│ ├── testing.svg
│ ├── netflix.svg
│ ├── docs.svg
│ ├── vscode.svg
│ ├── amd.svg
│ ├── packaging.svg
│ ├── git.svg
│ ├── lyft.svg
│ ├── circles.svg.py
│ ├── transform.svg
│ ├── outerbounds.svg
│ ├── novetta.svg
│ └── jupyter.svg
├── api
│ ├── images
│ │ └── token.png
│ ├── index.qmd
│ ├── 17_serve.ipynb
│ ├── 15_qmd.ipynb
│ ├── 06_sync.ipynb
│ ├── 04_export.ipynb
│ └── 09_frontmatter.ipynb
├── blog
│ ├── posts
│ │ ├── 2022-07-28-nbdev2
│ │ │ ├── patch.png
│ │ │ ├── lang_rank.png
│ │ │ ├── logo_lyft.png
│ │ │ ├── logo_ob.png
│ │ │ ├── nb_quarto.png
│ │ │ ├── logo_netflix.png
│ │ │ └── logo_transform.png
│ │ ├── 2022-11-07-spaces
│ │ │ ├── final_app.png
│ │ │ ├── blog_cover.jpeg
│ │ │ ├── create_space.png
│ │ │ ├── gradio_local.png
│ │ │ └── app.py
│ │ └── 2022-08-25-jupyter-git
│ │ │ ├── friendly-conflict.png
│ │ │ └── unreadable-notebook.png
│ └── index.qmd
├── custom.yml
├── nbdev.yml
├── styles.css
├── _quarto.yml
├── index.css
└── index.qmd.py
├── tests
├── .nodoc
├── .notest
├── notest
│ ├── .notest
│ └── nb_ignore.ipynb
├── .gitignore
├── 2022-09-06-homeschooling.md
├── black.ipynb
├── minimal.ipynb
├── 00_some.thing.ipynb
├── metadata.ipynb
├── example.ipynb.broken
├── old_directives.ipynb
├── 2020-01-14-test-markdown-post.md
├── directives.ipynb
├── showdoc_test.ipynb
└── 01_everything.ipynb
├── nbdev
├── __init__.py
├── imports.py
├── serve_drv.py
├── extract_attachments.py
├── frontmatter.py
├── export.py
├── sync.py
├── serve.py
├── qmd.py
├── cli.py
├── merge.py
├── test.py
├── process.py
└── migrate.py
├── MANIFEST.in
├── TODO.md
├── .github
├── workflows
│ ├── deploy.yaml
│ ├── test.yaml
│ └── deploy-manual.yaml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── .pre-commit-hooks.yaml
├── CONTRIBUTING.md
├── settings.ini
├── .gitignore
├── setup.py
└── CODE_OF_CONDUCT.md
/nbs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/.nodoc:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/.notest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nbs/explanations/.nodoc:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nbs/tutorials/.nodoc:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nbs/tutorials/.notest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/notest/.notest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nbs/CNAME:
--------------------------------------------------------------------------------
1 | nbdev.fast.ai
2 |
--------------------------------------------------------------------------------
/nbs/explanations/.notest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------
1 | _docs_test_files/
2 |
--------------------------------------------------------------------------------
/nbs/.gitignore:
--------------------------------------------------------------------------------
1 | site_libs/
2 | docs/
3 | /.quarto/
4 |
--------------------------------------------------------------------------------
/nbs/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/favicon.png
--------------------------------------------------------------------------------
/nbs/images/bom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/bom.png
--------------------------------------------------------------------------------
/nbs/images/card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/card.png
--------------------------------------------------------------------------------
/nbs/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/favicon.png
--------------------------------------------------------------------------------
/nbs/api/images/token.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/api/images/token.png
--------------------------------------------------------------------------------
/nbs/images/david-berg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/david-berg.jpeg
--------------------------------------------------------------------------------
/nbs/images/overstory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/overstory.png
--------------------------------------------------------------------------------
/nbs/images/chris-lattner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/chris-lattner.png
--------------------------------------------------------------------------------
/nbs/images/erik-gaasedelen.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/erik-gaasedelen.jpeg
--------------------------------------------------------------------------------
/nbs/images/fernando-pérez.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/fernando-pérez.jpeg
--------------------------------------------------------------------------------
/nbs/images/roxanna-pourzand.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/roxanna-pourzand.jpeg
--------------------------------------------------------------------------------
/nbs/images/hugo-bowne-anderson.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/images/hugo-bowne-anderson.jpeg
--------------------------------------------------------------------------------
/nbdev/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.3.12"
2 |
3 | from .doclinks import nbdev_export
4 | from .showdoc import show_doc
5 |
6 |
--------------------------------------------------------------------------------
/nbdev/imports.py:
--------------------------------------------------------------------------------
1 | from fastcore.imports import *
2 | from fastcore.foundation import *
3 | from fastcore.utils import *
4 |
5 |
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/patch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/patch.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/github-repo-empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/github-repo-empty.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/jupyter-home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/jupyter-home-page.png
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include settings.ini
2 | include LICENSE
3 | include CONTRIBUTING.md
4 | include README.md
5 | recursive-exclude * __pycache__
6 |
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/lang_rank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/lang_rank.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/logo_lyft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/logo_lyft.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/logo_ob.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/logo_ob.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/nb_quarto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/nb_quarto.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-11-07-spaces/final_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-11-07-spaces/final_app.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/github-actions-pages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/github-actions-pages.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/github-enable-pages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/github-enable-pages.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/marie-curie-notebook.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/marie-curie-notebook.jpg
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-11-07-spaces/blog_cover.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-11-07-spaces/blog_cover.jpeg
--------------------------------------------------------------------------------
/nbs/explanations/images/github-actions-pages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/explanations/images/github-actions-pages.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/github-actions-initial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/github-actions-initial.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/github-create-new-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/github-create-new-repo.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/jupyter-blank-terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/jupyter-blank-terminal.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/logo_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/logo_netflix.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-07-28-nbdev2/logo_transform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-07-28-nbdev2/logo_transform.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-11-07-spaces/create_space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-11-07-spaces/create_space.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-11-07-spaces/gradio_local.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-11-07-spaces/gradio_local.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/jupyter-friendly-conflict.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/jupyter-friendly-conflict.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/jupyter-unreadable-notebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/jupyter-unreadable-notebook.png
--------------------------------------------------------------------------------
/nbs/tutorials/images/nbdev-hello-world-site-initial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/tutorials/images/nbdev-hello-world-site-initial.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-08-25-jupyter-git/friendly-conflict.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-08-25-jupyter-git/friendly-conflict.png
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-08-25-jupyter-git/unreadable-notebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hugetim/nbdev/master/nbs/blog/posts/2022-08-25-jupyter-git/unreadable-notebook.png
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | ## Docs export
2 |
3 | - `show_doc` changes:
4 | - support `#default_cls_lvl`
5 | - pick header level for functions/methods
6 | - implement stuff from nbdev export2html, e.g.(?):
7 | - `remove_widget_state`
8 | - fastcore conversion scripts + instuctions
9 |
10 |
--------------------------------------------------------------------------------
/nbs/custom.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 | output-dir: _docs
4 |
5 | website:
6 | title: "nbdev"
7 | site-url: "https://nbdev.fast.ai/"
8 | description: "Create delightful software with Jupyter Notebooks"
9 | repo-url: "https://github.com/fastai/nbdev"
10 |
11 |
--------------------------------------------------------------------------------
/nbs/nbdev.yml:
--------------------------------------------------------------------------------
1 | project:
2 | output-dir: _docs
3 |
4 | website:
5 | title: "nbdev"
6 | site-url: "https://nbdev.fast.ai/"
7 | description: "Create delightful software with Jupyter Notebooks"
8 | repo-branch: master
9 | repo-url: "https://github.com/fastai/nbdev"
10 |
--------------------------------------------------------------------------------
/nbs/tutorials/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: Tutorials
4 | listing:
5 | fields: [title, description]
6 | type: table
7 | sort-ui: false
8 | filter-ui: false
9 | ---
10 |
11 | Click through to any of these tutorials to get started with nbdev's features.
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 | on:
3 | push:
4 | branches: [master]
5 | workflow_dispatch:
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: fastai/workflows/quarto-ghp@master
11 | # with: {pre: 1}
12 |
--------------------------------------------------------------------------------
/nbs/blog/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: nbdev Blog
3 | subtitle: News, tips, and commentary about all things nbdev
4 | listing:
5 | sort: "date desc"
6 | contents: "posts"
7 | sort-ui: false
8 | filter-ui: false
9 | categories: true
10 | feed: true
11 | page-layout: full
12 | ---
--------------------------------------------------------------------------------
/nbs/explanations/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: Explanations
4 | listing:
5 | fields: [title, description]
6 | type: table
7 | sort-ui: false
8 | filter-ui: false
9 | ---
10 |
11 | These explanations provide background information on how nbdev is designed and how it works. They are designed to help people who want to more deeply understand the nbdev system.
12 |
13 |
--------------------------------------------------------------------------------
/tests/2022-09-06-homeschooling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Essential Work-From-Home Advice: Cheap and Easy Ergonomic Setups"
3 | summary: "You can permanently damage your back, neck, and wrists from working without an ergonomic setup. Learn how to create one for less at home."
4 | image: '/images/ergonomic1-short.jpg'
5 | author: 'Rachel Thomas'
6 | tags: advice health
7 | ---
8 |
9 | Lorem ipsum
10 |
--------------------------------------------------------------------------------
/.pre-commit-hooks.yaml:
--------------------------------------------------------------------------------
1 | - &hook
2 | id: nbdev_clean
3 | name: nbdev_clean
4 | entry: nbdev_clean
5 | description: "Clean metadata from notebooks to avoid merge conflicts"
6 | language: python
7 | always_run: true
8 | pass_filenames: false
9 |
10 | - <<: *hook
11 | id: nbdev_export
12 | name: nbdev_export
13 | entry: nbdev_export
14 | description: "Export notebooks to modules and build modidx"
15 |
--------------------------------------------------------------------------------
/nbs/api/index.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | order: 9
3 | title: API
4 | listing:
5 | fields: [title, description]
6 | type: table
7 | sort-ui: false
8 | filter-ui: false
9 | ---
10 |
11 | This section contains API details for each of nbdev's python submodules. This reference documentation is mainly useful for people looking to customise or build on top of nbdev, or wanting detailed information about how nbdev works.
12 |
13 |
--------------------------------------------------------------------------------
/nbs/images/testing.svg:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/black.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "b9cc4e2f-fa2a-4e25-b078-98bc84ecbb0b",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "j = [1,\n",
11 | " 2,\n",
12 | " 3\n",
13 | "]"
14 | ]
15 | }
16 | ],
17 | "metadata": {
18 | "kernelspec": {
19 | "display_name": "Python 3 (ipykernel)",
20 | "language": "python",
21 | "name": "python3"
22 | }
23 | },
24 | "nbformat": 4,
25 | "nbformat_minor": 5
26 | }
27 |
--------------------------------------------------------------------------------
/tests/notest/nb_ignore.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "3832a663-51f6-47db-846c-156086a0fc0d",
6 | "metadata": {},
7 | "source": [
8 | "This notebook should not be tested due to a sibling file called `.notest`"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "3de2ee33-9ee9-4376-a602-d8ae1e6ca4e7",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "assert 123 == 593"
19 | ]
20 | }
21 | ],
22 | "metadata": {
23 | "kernelspec": {
24 | "display_name": "Python 3 (ipykernel)",
25 | "language": "python",
26 | "name": "python3"
27 | }
28 | },
29 | "nbformat": 4,
30 | "nbformat_minor": 5
31 | }
32 |
--------------------------------------------------------------------------------
/nbs/blog/posts/2022-11-07-spaces/app.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: app.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['iface', 'size']
5 |
6 | # %% app.ipynb 6
7 | import gradio as gr
8 | from fastcore.net import urljson, HTTPError
9 |
10 | # %% app.ipynb 8
11 | def size(repo:str):
12 | "Returns the size in GB of a HuggingFace Dataset."
13 | url = f'https://huggingface.co/api/datasets/{repo}'
14 | try: resp = urljson(f'{url}/treesize/main')
15 | except HTTPError: return f'Did not find repo: {url}'
16 | gb = resp['size'] / 1e9
17 | return f'{gb:.2f} GB'
18 |
19 | # %% app.ipynb 12
20 | iface = gr.Interface(fn=size, inputs=gr.Text(value="tglcourse/CelebA-faces-cropped-128"), outputs="text")
21 | iface.launch(height=450, width=500)
22 |
--------------------------------------------------------------------------------
/nbdev/serve_drv.py:
--------------------------------------------------------------------------------
1 | import os
2 | from execnb.nbio import read_nb,write_nb
3 | from io import StringIO
4 | from contextlib import redirect_stdout
5 |
6 | def exec_scr(src, dst, md):
7 | f = StringIO()
8 | g = {}
9 | with redirect_stdout(f): exec(compile(src.read_text(), src, 'exec'), g)
10 | res = ""
11 | if md: res += "---\n" + md + "\n---\n\n"
12 | dst.write_text(res + f.getvalue())
13 |
14 | def exec_nb(src, dst, cb):
15 | nb = read_nb(src)
16 | cb()(nb)
17 | write_nb(nb, dst)
18 |
19 | def main(o):
20 | src,dst,x = o
21 | os.environ["IN_TEST"] = "1"
22 | if src.suffix=='.ipynb': exec_nb(src, dst, x)
23 | elif src.suffix=='.py': exec_scr(src, dst, x)
24 | else: raise Exception(src)
25 | del os.environ["IN_TEST"]
26 |
27 |
--------------------------------------------------------------------------------
/tests/minimal.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## A minimal notebook"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [
15 | {
16 | "data": {
17 | "text/plain": [
18 | "2"
19 | ]
20 | },
21 | "execution_count": null,
22 | "metadata": {},
23 | "output_type": "execute_result"
24 | }
25 | ],
26 | "source": [
27 | "# Do some arithmetic\n",
28 | "1+1"
29 | ]
30 | }
31 | ],
32 | "metadata": {
33 | "kernelspec": {
34 | "display_name": "Python 3 (ipykernel)",
35 | "language": "python",
36 | "name": "python3"
37 | }
38 | },
39 | "nbformat": 4,
40 | "nbformat_minor": 4
41 | }
42 |
--------------------------------------------------------------------------------
/tests/00_some.thing.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "This notebook is used to demonstrate exporting to an existing module. See the notebooks in `nbs` for how it's used."
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "#|hide\n",
17 | "#|default_exp some.thing"
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "#|export\n",
27 | "a=1"
28 | ]
29 | }
30 | ],
31 | "metadata": {
32 | "kernelspec": {
33 | "display_name": "Python 3 (ipykernel)",
34 | "language": "python",
35 | "name": "python3"
36 | }
37 | },
38 | "nbformat": 4,
39 | "nbformat_minor": 4
40 | }
41 |
--------------------------------------------------------------------------------
/nbs/images/netflix.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/nbs/images/docs.svg:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | workflow_dispatch:
4 | pull_request:
5 | push:
6 | branches: [master]
7 |
8 | jobs:
9 | test:
10 | strategy:
11 | fail-fast: true
12 | matrix:
13 | os: [ubuntu, macos]
14 | version: ["3.7", "3.8", "3.9", "3.10"]
15 | runs-on: ${{ matrix.os }}-latest
16 | steps:
17 | - uses: fastai/workflows/nbdev-ci@master
18 | with:
19 | version: ${{ matrix.version }}
20 | pre: 1
21 | - name: test docs build
22 | if: ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && matrix.version == '3.9' && matrix.os == 'ubuntu' }}
23 | run: |
24 | set -ux
25 | wget -q $(curl https://latest.fast.ai/pre/quarto-dev/quarto-cli/linux-amd64.deb)
26 | sudo dpkg -i quarto*.deb
27 | nbdev_docs
28 | if [ -f "_docs/index.html" ]; then
29 | echo "docs built successfully."
30 | else
31 | echo "index page not found in rendered docs."
32 | ls -la
33 | ls -la _docs
34 | exit 1
35 | fi
36 |
--------------------------------------------------------------------------------
/nbs/images/vscode.svg:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/nbs/styles.css:
--------------------------------------------------------------------------------
1 | .cell {
2 | margin-bottom: 1rem;
3 | }
4 |
5 | .cell > .sourceCode {
6 | margin-bottom: 0;
7 | }
8 |
9 | .cell-output > pre {
10 | margin-bottom: 0;
11 | }
12 |
13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre {
14 | margin-left: 0.8rem;
15 | margin-top: 0;
16 | background: none;
17 | border-left: 2px solid lightsalmon;
18 | border-top-left-radius: 0;
19 | border-top-right-radius: 0;
20 | }
21 |
22 | .cell-output > .sourceCode {
23 | border: none;
24 | }
25 |
26 | .cell-output > .sourceCode {
27 | background: none;
28 | margin-top: 0;
29 | }
30 |
31 | div.description {
32 | padding-left: 2px;
33 | padding-top: 5px;
34 | font-style: italic;
35 | font-size: 135%;
36 | opacity: 70%;
37 | }
38 |
39 | /* show_doc signature */
40 | blockquote > pre {
41 | font-size: 14px;
42 | }
43 |
44 | .table {
45 | font-size: 16px;
46 | /* disable striped tables */
47 | --bs-table-striped-bg: var(--bs-table-bg);
48 | }
49 |
50 | .quarto-figure-center > figure > figcaption {
51 | text-align: center;
52 | }
53 |
54 | .figure-caption {
55 | font-size: 75%;
56 | font-style: italic;
57 | }
--------------------------------------------------------------------------------
/nbs/images/amd.svg:
--------------------------------------------------------------------------------
1 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-manual.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 | deploy:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v3
9 | - uses: actions/setup-python@v3
10 | - name: Install Dependencies
11 | shell: bash
12 | run: |
13 | python -m pip install --upgrade pip
14 | pip install -Uq git+https://github.com/fastai/ghapi.git # you need this for enabling pages
15 | pip install -Uq git+https://github.com/fastai/fastcore.git
16 | pip install -Uq git+https://github.com/fastai/execnb.git
17 | pip install -e ".[dev]"
18 | wget -nv https://www.quarto.org/download/latest/quarto-linux-amd64.deb
19 | sudo dpkg -i quarto*.deb
20 | nbdev_docs
21 | - name: Deploy to GitHub Pages
22 | uses: peaceiris/actions-gh-pages@v3
23 | with:
24 | github_token: ${{ github.token }}
25 | force_orphan: true
26 | publish_dir: ${{ env.NBDEV_DOCS }}
27 | user_name: github-actions[bot]
28 | user_email: 41898282+github-actions[bot]@users.noreply.github.com
29 |
30 |
--------------------------------------------------------------------------------
/nbs/images/packaging.svg:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/nbs/images/git.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
--------------------------------------------------------------------------------
/tests/metadata.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## A notebook with metadata"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {
14 | "hide_input": true,
15 | "my_extra_cell_key": "foo",
16 | "my_removed_cell_key": "foo",
17 | "meta": 37
18 | },
19 | "outputs": [
20 | {
21 | "data": {
22 | "text/plain": [
23 | "2"
24 | ]
25 | },
26 | "execution_count": 1,
27 | "metadata": {},
28 | "output_type": "execute_result"
29 | }
30 | ],
31 | "source": [
32 | "# A cell with metadata\n",
33 | "1+1"
34 | ]
35 | }
36 | ],
37 | "metadata": {
38 | "kernelspec": {
39 | "display_name": "Python 3 (ipykernel)",
40 | "language": "python",
41 | "name": "python3"
42 | },
43 | "language_info": {
44 | "codemirror_mode": {
45 | "name": "ipython",
46 | "version": 3
47 | },
48 | "file_extension": ".py",
49 | "mimetype": "text/x-python",
50 | "name": "python",
51 | "nbconvert_exporter": "python",
52 | "pygments_lexer": "ipython3",
53 | "version": "3.7.13"
54 | },
55 | "my_extra_key": "foo",
56 | "my_removed_key": "foo",
57 | "jekyll": "some_meta",
58 | "meta": 37
59 | },
60 | "nbformat": 4,
61 | "nbformat_minor": 4
62 | }
63 |
--------------------------------------------------------------------------------
/tests/example.ipynb.broken:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 6,
6 | "metadata": {},
7 | "outputs": [
8 | {
9 | "data": {
10 | "text/plain": [
11 | "3"
12 | ]
13 | },
14 | "execution_count": 6,
15 | "metadata": {},
16 | "output_type": "execute_result"
17 | }
18 | ],
19 | "source": [
20 | <<<<<<< HEAD
21 | "z=3\n",
22 | =======
23 | "z=2\n",
24 | >>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35
25 | "z"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": 7,
31 | "execution_count": 5,
32 | "metadata": {},
33 | "outputs": [
34 | {
35 | "data": {
36 | "text/plain": [
37 | "6"
38 | ]
39 | },
40 | <<<<<<< HEAD
41 | "execution_count": 7,
42 | =======
43 | "execution_count": 5,
44 | >>>>>>> a7ec1b0bfb8e23b05fd0a2e6cafcb41cd0fb1c35
45 | "metadata": {},
46 | "output_type": "execute_result"
47 | }
48 | ],
49 | "source": [
50 | "x=3\n",
51 | "y=3\n",
52 | "x+y"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {},
59 | "outputs": [],
60 | "source": []
61 | }
62 | ],
63 | "metadata": {
64 | "kernelspec": {
65 | "display_name": "Python 3",
66 | "language": "python",
67 | "name": "python3"
68 | }
69 | },
70 | "nbformat": 4,
71 | "nbformat_minor": 2
72 | }
73 |
--------------------------------------------------------------------------------
/nbs/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 | preview:
4 | port: 3000
5 | browser: false
6 |
7 | format:
8 | html:
9 | theme: cosmo
10 | css: styles.css
11 | toc: true
12 | toc-depth: 4
13 |
14 | website:
15 | twitter-card: true
16 | open-graph: true
17 | repo-actions: [issue]
18 | sidebar:
19 | style: floating
20 | contents:
21 | - auto: "/*.ipynb"
22 | - section: Tutorials
23 | contents: tutorials/*
24 | - section: Explanations
25 | contents: explanations/*
26 | - section: API
27 | contents: api/*
28 | favicon: favicon.png
29 | navbar:
30 | background: primary
31 | search: true
32 | collapse-below: lg
33 | left:
34 | - text: "Get Started"
35 | href: getting_started.ipynb
36 | - text: "Tutorial"
37 | href: tutorials/tutorial.ipynb
38 | - text: "Blog"
39 | href: blog/index.qmd
40 | - text: "Help"
41 | menu:
42 | - text: "Report an Issue"
43 | icon: bug
44 | href: https://github.com/fastai/nbdev/issues
45 | - text: "Fast.ai Forum"
46 | icon: chat-right-text
47 | href: https://forums.fast.ai/
48 | - text: "FAQ"
49 | icon: question-circle
50 | href: getting_started.ipynb#faq
51 | right:
52 | - icon: github
53 | href: "https://github.com/fastai/nbdev"
54 | - icon: twitter
55 | href: https://twitter.com/fastdotai
56 | aria-label: Fast.ai Twitter
57 |
58 | metadata-files: [nbdev.yml]
59 |
60 |
--------------------------------------------------------------------------------
/tests/old_directives.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "23fe7353-8bac-4ac3-a7df-d80ff3186e0f",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "#hide\n",
11 | " # export"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "d4419c73-e4df-40d2-a6a9-8f9299f5f832",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "#hide\n",
22 | " # export\n",
23 | "def there_is_code(): ..."
24 | ]
25 | },
26 | {
27 | "cell_type": "code",
28 | "execution_count": null,
29 | "id": "82d2db47-24d7-43ba-9a5a-d61af1a06b12",
30 | "metadata": {},
31 | "outputs": [],
32 | "source": [
33 | "a = 'no directives'"
34 | ]
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "id": "183e982d-7f38-417b-b774-ccadd6b76e2e",
39 | "metadata": {},
40 | "source": [
41 | "some md"
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": null,
47 | "id": "20b94777-1b99-4a0f-bf76-766343470047",
48 | "metadata": {},
49 | "outputs": [],
50 | "source": [
51 | "#eval: false\n",
52 | "assert 1 == 2"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "id": "d4b7b3db-6831-46b7-aa3f-4e6cc0bc0a24",
59 | "metadata": {},
60 | "outputs": [],
61 | "source": [
62 | "# notest\n",
63 | "assert 1 == 2"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "3f49b654-cda4-49f1-8763-1724e18b1def",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "not_a_directive='#export'"
74 | ]
75 | }
76 | ],
77 | "metadata": {
78 | "kernelspec": {
79 | "display_name": "Python 3 (ipykernel)",
80 | "language": "python",
81 | "name": "python3"
82 | }
83 | },
84 | "nbformat": 4,
85 | "nbformat_minor": 5
86 | }
87 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a minimal reproducible example to help us improve
4 | labels: ["bug"]
5 |
6 | ---
7 |
8 | # Provide a minimally reproducible example
9 |
10 | To maximize the chances of your issue being fixed, you should share a minimally reproducible example that allows us to reproduce your error. A good way to do this is to setup a new nbdev project which containts the minimal amount of code that reproduces your error.
11 |
12 | If we are not able to reproduce your error, we may close your issue.
13 |
14 | ## Some background on minimal reproducible examples [^1]
15 |
16 | The first benefit of a public reproduction is to prove that the problem is not caused by your environment or by a setting you left out of your description, thinking it wasn't relevant. If there were any doubts about whether you'd found a genuine problem before, they are usually removed once a reproduction is made.
17 |
18 | Next, when a reproduction has minimal config and code, it can often let us narrow down or even identify the root cause, suggest workarounds, etc. This means we can often help you from code inspection alone.
19 |
20 | Finally, by making the code/dependencies minimal, it usually makes the problem feasible to step through using a debugging if code inspection wasn't sufficient. Production repositories or non-minimal reproductions are often very difficult to debug because break points get triggered dozens or hundreds or times.
21 |
22 | The basic idea of a minimal reproduction is to use the least amount of both code and config to trigger missing or wrong behavior. A minimal reproduction helps the developers see where the bug or missing feature is, and allows us to verify that the new code meets the requirements.
23 |
24 | ## Where to host your mimimal reproducible example
25 |
26 | A new, **public** GitHub repo is the best place to host your minimal reproducible example.
27 |
28 | [^1]: the below guidance was adapted [from this language](https://github.com/renovatebot/renovate/blob/main/docs/development/minimal-reproductions.md)
29 |
--------------------------------------------------------------------------------
/nbs/images/lyft.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/nbdev/extract_attachments.py:
--------------------------------------------------------------------------------
1 | """ A preprocessor that extracts all of the attachments from the notebook file.
2 | The extracted attachments are returned in the 'resources' dictionary.
3 |
4 | Based on the ExtractOutputsProcessor in nbconvert... the license for nbconvert is:
5 |
6 | # Licensing terms
7 |
8 | This project is licensed under the terms of the Modified BSD License
9 | (also known as New or Revised or 3-Clause BSD), as follows:
10 |
11 | - Copyright (c) 2001-2015, IPython Development Team
12 | - Copyright (c) 2015-, Jupyter Development Team
13 |
14 | All rights reserved. """
15 |
16 | import sys,os
17 | from binascii import a2b_base64
18 | from traitlets import Unicode, Set
19 | from nbconvert.preprocessors.base import Preprocessor
20 |
21 | class ExtractAttachmentsPreprocessor(Preprocessor):
22 | "Extracts all of the outputs from the notebook file."
23 | output_filename_template = Unicode( "attach_{cell_index}_{name}").tag(config=True)
24 | extract_output_types = Set( {'image/png', 'image/jpeg', 'image/svg+xml', 'application/pdf'}).tag(config=True)
25 |
26 | def preprocess_cell(self, cell, resources, cell_index):
27 | output_files_dir = resources.get('output_files_dir', None)
28 | if not isinstance(resources['outputs'], dict): resources['outputs'] = {}
29 |
30 | for name, attach in cell.get("attachments", {}).items():
31 | for mime, data in attach.items():
32 | if mime not in self.extract_output_types: continue
33 | # Binary files are base64-encoded, SVG is already XML
34 | if mime in {'image/png', 'image/jpeg', 'application/pdf'}: data = a2b_base64(data)
35 | elif sys.platform == 'win32': data = data.replace('\n', '\r\n').encode("UTF-8")
36 | else: data = data.encode("UTF-8")
37 | filename = self.output_filename_template.format( cell_index=cell_index, name=name)
38 | if output_files_dir is not None: filename = os.path.join(output_files_dir, filename)
39 | if name.endswith(".gif") and mime == "image/png": filename = filename.replace(".gif", ".png")
40 | resources['outputs'][filename] = data
41 | attach_str = "attachment:"+name
42 | if attach_str in cell.source: cell.source = cell.source.replace(attach_str, filename)
43 |
44 | return cell, resources
45 |
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | ## How to get started
4 |
5 | Before anything else, please install the git hooks that run automatic scripts during each commit and merge to strip the notebooks of superfluous metadata (and avoid merge conflicts). After cloning the repository, run the following command inside it:
6 | ```
7 | nbdev_install_hooks
8 | ```
9 |
10 | ## Did you find a bug?
11 |
12 | * Ensure the bug was not already reported by searching on GitHub under Issues.
13 | * If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
14 | * Be sure to add the complete error messages.
15 |
16 | #### Did you write a patch that fixes a bug?
17 |
18 | * Open a new GitHub pull request with the patch.
19 | * Ensure that your PR includes a test that fails without your patch, and pass with it.
20 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
21 |
22 | ## PR submission guidelines
23 |
24 | * Keep each PR focused. While it's more convenient, do not combine several unrelated fixes together. Create as many branches as needing to keep each PR focused.
25 | * Do not mix style changes/fixes with "functional" changes. It's very difficult to review such PRs and it most likely get rejected.
26 | * Do not add/remove vertical whitespace. Preserve the original style of the file you edit as much as you can.
27 | * Do not turn an already submitted PR into your development playground. If after you submitted PR, you discovered that more work is needed - close the PR, do the required work and then submit a new PR. Otherwise each of your commits requires attention from maintainers of the project.
28 | * If, however, you submitted a PR and received a request for changes, you should proceed with commits inside that PR, so that the maintainer can see the incremental fixes and won't need to review the whole PR again. In the exception case where you realize it'll take many many commits to complete the requests, then it's probably best to close the PR, do the work and then submit it again. Use common sense where you'd choose one way over another.
29 |
30 | ## Do you want to contribute to the documentation?
31 |
32 | * Docs are automatically created from the notebooks in the nbs folder.
33 |
34 |
--------------------------------------------------------------------------------
/nbdev/frontmatter.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/09_frontmatter.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['FrontmatterProc']
5 |
6 | # %% ../nbs/api/09_frontmatter.ipynb 2
7 | from .imports import *
8 | from .process import *
9 | from .doclinks import _nbpath2html
10 |
11 | from execnb.nbio import *
12 | from fastcore.imports import *
13 | import yaml
14 |
15 | # %% ../nbs/api/09_frontmatter.ipynb 5
16 | _RE_FM_BASE=r'''^---\s*
17 | (.*?\S+.*?)
18 | ---\s*'''
19 |
20 | _re_fm_nb = re.compile(_RE_FM_BASE+'$', flags=re.DOTALL)
21 | _re_fm_md = re.compile(_RE_FM_BASE, flags=re.DOTALL)
22 |
23 | def _fm2dict(s:str, nb=True):
24 | "Load YAML frontmatter into a `dict`"
25 | re_fm = _re_fm_nb if nb else _re_fm_md
26 | match = re_fm.search(s.strip())
27 | return yaml.safe_load(match.group(1)) if match else {}
28 |
29 | def _md2dict(s:str):
30 | "Convert H1 formatted markdown cell to frontmatter dict"
31 | if '#' not in s: return {}
32 | m = re.search(r'^#\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
33 | if not m: return {}
34 | res = {'title': m.group(1)}
35 | m = re.search(r'^>\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
36 | if m: res['description'] = m.group(1)
37 | r = re.findall(r'^-\s+(\S.*:.*\S)\s*$', s, flags=re.MULTILINE)
38 | if r:
39 | try: res.update(yaml.safe_load('\n'.join(r)))
40 | except Exception as e: warn(f'Failed to create YAML dict for:\n{r}\n\n{e}\n')
41 | return res
42 |
43 | # %% ../nbs/api/09_frontmatter.ipynb 6
44 | def _dict2fm(d): return f'---\n{yaml.dump(d)}\n---\n\n'
45 | def _insertfm(nb, fm): nb.cells.insert(0, mk_cell(_dict2fm(fm), 'raw'))
46 |
47 | class FrontmatterProc(Processor):
48 | "A YAML and formatted-markdown frontmatter processor"
49 | def begin(self): self.fm = getattr(self.nb, 'frontmatter_', {})
50 |
51 | def _update(self, f, cell):
52 | s = cell.get('source')
53 | if not s: return
54 | d = f(s)
55 | if not d: return
56 | self.fm.update(d)
57 | cell.source = None
58 |
59 | def cell(self, cell):
60 | if cell.cell_type=='raw': self._update(_fm2dict, cell)
61 | elif cell.cell_type=='markdown' and 'title' not in self.fm: self._update(_md2dict, cell)
62 |
63 | def end(self):
64 | self.nb.frontmatter_ = self.fm
65 | if not self.fm: return
66 | self.fm.update({'output-file': _nbpath2html(Path(self.nb.path_)).name})
67 | _insertfm(self.nb, self.fm)
68 |
--------------------------------------------------------------------------------
/settings.ini:
--------------------------------------------------------------------------------
1 | [DEFAULT]
2 | lib_name = nbdev
3 | repo = nbdev
4 | description = Create delightful software with Jupyter Notebooks
5 | copyright = 2020 onwards, Jeremy Howard
6 | keywords = nbdev fastai jupyter notebook export
7 | user = fastai
8 | author = Jeremy Howard and Hamel Husain
9 | author_email = j@fast.ai
10 | branch = master
11 | min_python = 3.7
12 | version = 2.3.12
13 | audience = Developers
14 | language = English
15 | custom_sidebar = True
16 | license = apache2
17 | status = 2
18 | requirements = fastcore>=1.5.27 execnb>=0.1.4 astunparse ghapi>=1.0.3 watchdog asttokens
19 | pip_requirements = PyYAML
20 | conda_requirements = pyyaml
21 | conda_user = fastai
22 | dev_requirements = nbdev-numpy nbdev-stdlib pandas matplotlib black svg.py jupyter
23 | console_scripts = nbdev_create_config=nbdev.config:nbdev_create_config
24 | nbdev_update=nbdev.sync:nbdev_update
25 | nbdev_export=nbdev.doclinks:nbdev_export
26 | nbdev_fix=nbdev.merge:nbdev_fix
27 | nbdev_merge=nbdev.merge:nbdev_merge
28 | nbdev_trust=nbdev.clean:nbdev_trust
29 | nbdev_clean=nbdev.clean:nbdev_clean
30 | nbdev_install_hooks=nbdev.clean:nbdev_install_hooks
31 | nbdev_filter=nbdev.cli:nbdev_filter
32 | nbdev_sidebar=nbdev.quarto:nbdev_sidebar
33 | nbdev_test=nbdev.test:nbdev_test
34 | nbdev_new=nbdev.cli:nbdev_new
35 | nbdev_migrate=nbdev.migrate:nbdev_migrate
36 | nbdev_install_quarto=nbdev.quarto:install_quarto
37 | nbdev_install=nbdev.quarto:install
38 | nbdev_docs=nbdev.quarto:nbdev_docs
39 | nbdev_preview=nbdev.quarto:nbdev_preview
40 | nbdev_prepare=nbdev.quarto:prepare
41 | nbdev_readme=nbdev.quarto:nbdev_readme
42 | nbdev_release_gh=nbdev.release:release_gh
43 | nbdev_release_git=nbdev.release:release_git
44 | nbdev_changelog=nbdev.release:changelog
45 | nbdev_pypi=nbdev.release:release_pypi
46 | nbdev_conda=nbdev.release:release_conda
47 | nbdev_release_both=nbdev.release:release_both
48 | nbdev_bump_version=nbdev.release:nbdev_bump_version
49 | nbdev_proc_nbs=nbdev.quarto:nbdev_proc_nbs
50 | nbdev_help=nbdev.cli:chelp
51 | tst_flags = notest
52 | nbs_path = nbs
53 | doc_path = _docs
54 | recursive = True
55 | doc_host = https://nbdev.fast.ai
56 | doc_baseurl = /
57 | git_url = https://github.com/fastai/nbdev
58 | lib_path = nbdev
59 | title = nbdev
60 | black_formatting = False
61 | readme_nb = getting_started.ipynb
62 | allowed_metadata_keys =
63 | allowed_cell_metadata_keys =
64 | jupyter_hooks = True
65 | clean_ids = False
66 | clear_all = False
67 | put_version_in_init = True
68 |
69 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _publish.yml
2 | _quarto.yml
3 | _proc/
4 | tests/settings.ini
5 | idx/
6 | .install
7 | conda/
8 | _site/
9 | _docs/
10 | test_settings.ini
11 |
12 | *.bak
13 | .gitattributes
14 | .last_checked
15 | .gitconfig
16 | *.bak
17 | *.log
18 | *~
19 | ~*
20 | _tmp*
21 | tmp*
22 | tags
23 |
24 | # Byte-compiled / optimized / DLL files
25 | __pycache__/
26 | *.py[cod]
27 | *$py.class
28 |
29 | # C extensions
30 | *.so
31 |
32 | # Distribution / packaging
33 | .Python
34 | env/
35 | build/
36 | develop-eggs/
37 | dist/
38 | downloads/
39 | eggs/
40 | .eggs/
41 | lib/
42 | lib64/
43 | parts/
44 | sdist/
45 | var/
46 | wheels/
47 | *.egg-info/
48 | .installed.cfg
49 | *.egg
50 |
51 | # PyInstaller
52 | # Usually these files are written by a python script from a template
53 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
54 | *.manifest
55 | *.spec
56 |
57 | # Installer logs
58 | pip-log.txt
59 | pip-delete-this-directory.txt
60 |
61 | # Unit test / coverage reports
62 | htmlcov/
63 | .tox/
64 | .coverage
65 | .coverage.*
66 | .cache
67 | nosetests.xml
68 | coverage.xml
69 | *.cover
70 | .hypothesis/
71 |
72 | # Translations
73 | *.mo
74 | *.pot
75 |
76 | # Django stuff:
77 | *.log
78 | local_settings.py
79 |
80 | # Flask stuff:
81 | instance/
82 | .webassets-cache
83 |
84 | # Scrapy stuff:
85 | .scrapy
86 |
87 | # Sphinx documentation
88 | docs/_build/
89 |
90 | # PyBuilder
91 | target/
92 |
93 | # Jupyter Notebook
94 | .ipynb_checkpoints
95 |
96 | # pyenv
97 | .python-version
98 |
99 | # celery beat schedule file
100 | celerybeat-schedule
101 |
102 | # SageMath parsed files
103 | *.sage.py
104 |
105 | # dotenv
106 | .env
107 |
108 | # virtualenv
109 | .venv
110 | venv/
111 | ENV/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 |
126 | .vscode
127 | *.swp
128 |
129 | # osx generated files
130 | .DS_Store
131 | .DS_Store?
132 | .Trashes
133 | ehthumbs.db
134 | Thumbs.db
135 | .idea
136 |
137 | # pytest
138 | .pytest_cache
139 |
140 | # tools/trust-doc-nbs
141 | docs_src/.last_checked
142 |
143 | # symlinks to fastai
144 | docs_src/fastai
145 | tools/fastai
146 |
147 | # link checker
148 | checklink/cookies.txt
149 |
150 | # .gitconfig is now autogenerated
151 | .gitconfig
152 |
153 |
--------------------------------------------------------------------------------
/tests/2020-01-14-test-markdown-post.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | toc: true
4 | layout: post
5 | description: A minimal example of using markdown with fastpages.
6 | categories: [markdown]
7 | title: An Example Markdown Post
8 |
9 |
10 | ---
11 |
12 | # Example Markdown Post
13 |
14 | ## Basic setup
15 |
16 | Jekyll requires blog post files to be named according to the following format:
17 |
18 | `YEAR-MONTH-DAY-filename.md`
19 |
20 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `filename` is whatever file name you choose, to remind yourself what this post is about. `.md` is the file extension for markdown files.
21 |
22 | The first line of the file should start with a single hash character, then a space, then your title. This is how you create a "*level 1 heading*" in markdown. Then you can create level 2, 3, etc headings as you wish but repeating the hash character, such as you see in the line `## File names` above.
23 |
24 | ## Basic formatting
25 |
26 | You can use *italics*, **bold**, `code font text`, and create [links](https://www.markdownguide.org/cheat-sheet/). Here's a footnote [^1]. Here's a horizontal rule:
27 |
28 | ---
29 |
30 | ## Lists
31 |
32 | Here's a list:
33 |
34 | - item 1
35 | - item 2
36 |
37 | And a numbered list:
38 |
39 | 1. item 1
40 | 1. item 2
41 |
42 | ## Boxes and stuff
43 |
44 | > This is a quotation
45 |
46 | {% include alert.html text="You can include alert boxes" %}
47 |
48 | ...and...
49 |
50 | {% include info.html text="You can include info boxes" %}
51 |
52 | ## Images
53 |
54 | 
55 |
56 | ## Code
57 |
58 | You can format text and code per usual
59 |
60 | General preformatted text:
61 |
62 | # Do a thing
63 | do_thing()
64 |
65 | Python code and output:
66 |
67 | ```python
68 | # Prints '2'
69 | print(1+1)
70 | ```
71 |
72 | 2
73 |
74 | Formatting text as shell commands:
75 |
76 | ```shell
77 | echo "hello world"
78 | ./some_script.sh --option "value"
79 | wget https://example.com/cat_photo1.png
80 | ```
81 |
82 | Formatting text as YAML:
83 |
84 | ```yaml
85 | key: value
86 | - another_key: "another value"
87 | ```
88 |
89 |
90 | ## Tables
91 |
92 | | Column 1 | Column 2 |
93 | |-|-|
94 | | A thing | Another thing |
95 |
96 |
97 | ## Tweetcards
98 |
99 | {% twitter https://twitter.com/jakevdp/status/1204765621767901185?s=20 %}
100 |
101 |
102 | ## Footnotes
103 |
104 |
105 |
106 | [^1]: This is the footnote.
107 |
108 |
--------------------------------------------------------------------------------
/nbdev/export.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['ExportModuleProc', 'black_format', 'nb_export']
5 |
6 | # %% ../nbs/api/04_export.ipynb 2
7 | from .config import *
8 | from .maker import *
9 | from .imports import *
10 | from .process import *
11 |
12 | from fastcore.script import *
13 | from fastcore.basics import *
14 | from fastcore.imports import *
15 |
16 | from collections import defaultdict
17 |
18 | # %% ../nbs/api/04_export.ipynb 4
19 | class ExportModuleProc:
20 | "A processor which exports code to a module"
21 | def begin(self): self.modules,self.in_all = defaultdict(L),defaultdict(L)
22 | def _default_exp_(self, cell, exp_to): self.default_exp = exp_to
23 | def _exporti_(self, cell, exp_to=None): self.modules[ifnone(exp_to, '#')].append(cell)
24 | def _export_(self, cell, exp_to=None):
25 | self._exporti_(cell, exp_to)
26 | self.in_all[ifnone(exp_to, '#')].append(cell)
27 | _exports_=_export_
28 |
29 | # %% ../nbs/api/04_export.ipynb 7
30 | def black_format(cell, # Cell to format
31 | force=False): # Turn black formatting on regardless of settings.ini
32 | "Processor to format code with `black`"
33 | try: cfg = get_config()
34 | except FileNotFoundError: return
35 | if (not cfg.black_formatting and not force) or cell.cell_type != 'code': return
36 | try: import black
37 | except: raise ImportError("You must install black: `pip install black` if you wish to use black formatting with nbdev")
38 | else:
39 | _format_str = partial(black.format_str, mode = black.Mode())
40 | try: cell.source = _format_str(cell.source).strip()
41 | except: pass
42 |
43 | # %% ../nbs/api/04_export.ipynb 9
44 | def nb_export(nbname, lib_path=None, procs=black_format, debug=False, mod_maker=ModuleMaker, name=None):
45 | "Create module(s) from notebook"
46 | if lib_path is None: lib_path = get_config().lib_path
47 | exp = ExportModuleProc()
48 | nb = NBProcessor(nbname, [exp]+L(procs), debug=debug)
49 | nb.process()
50 | for mod,cells in exp.modules.items():
51 | all_cells = exp.in_all[mod]
52 | nm = ifnone(name, getattr(exp, 'default_exp', None) if mod=='#' else mod)
53 | if not nm:
54 | warn(f"Notebook '{nbname}' uses `#|export` without `#|default_exp` cell.\n"
55 | "Note nbdev2 no longer supports nbdev1 syntax. Run `nbdev_migrate` to upgrade.\n"
56 | "See https://nbdev.fast.ai/getting_started.html for more information.")
57 | return
58 | mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#')
59 | mm.make(cells, all_cells, lib_path=lib_path)
60 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from pkg_resources import parse_version
2 | from configparser import ConfigParser
3 | import setuptools,shlex
4 | assert parse_version(setuptools.__version__)>=parse_version('36.2')
5 |
6 | # note: all settings are in settings.ini; edit there, not here
7 | config = ConfigParser(delimiters=['='])
8 | config.read('settings.ini')
9 | cfg = config['DEFAULT']
10 |
11 | cfg_keys = 'version description keywords author author_email'.split()
12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split()
13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o)
14 | setup_cfg = {o:cfg[o] for o in cfg_keys}
15 |
16 | licenses = {
17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'),
18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'),
19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'),
20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'),
21 | 'agpl3': ('GNU Affero General Public License v3', 'OSI Approved :: GNU Affero General Public License (AGPLv3)'),
22 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'),
23 | }
24 | statuses = [ '0 - Pre-Planning', '1 - Planning', '2 - Pre-Alpha', '3 - Alpha',
25 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ]
26 | py_versions = '3.7 3.8 3.9 3.10'.split()
27 |
28 | requirements = shlex.split(cfg.get('requirements', ''))
29 | if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', ''))
30 | min_python = cfg['min_python']
31 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None))
32 | dev_requirements = (cfg.get('dev_requirements') or '').split()
33 | project_urls = {}
34 | if cfg.get('doc_host'): project_urls["Documentation"] = cfg['doc_host'] + cfg.get('doc_baseurl', '')
35 |
36 | setuptools.setup(
37 | name = cfg['lib_name'],
38 | license = lic[0],
39 | classifiers = [
40 | 'Development Status :: ' + statuses[int(cfg['status'])],
41 | 'Intended Audience :: ' + cfg['audience'].title(),
42 | 'Natural Language :: ' + cfg['language'].title(),
43 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []),
44 | url = cfg['git_url'],
45 | packages = setuptools.find_packages(),
46 | include_package_data = True,
47 | install_requires = requirements,
48 | extras_require={ 'dev': dev_requirements },
49 | dependency_links = cfg.get('dep_links','').split(),
50 | python_requires = '>=' + cfg['min_python'],
51 | long_description = open('README.md', encoding="utf8").read(),
52 | long_description_content_type = 'text/markdown',
53 | zip_safe = False,
54 | entry_points = {
55 | 'console_scripts': cfg.get('console_scripts','').split(),
56 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d']
57 | },
58 | project_urls = project_urls,
59 | **setup_cfg)
60 |
61 |
--------------------------------------------------------------------------------
/nbs/images/circles.svg.py:
--------------------------------------------------------------------------------
1 | """---
2 | ---"""
3 |
4 | # Original copyright 2022 Gram, MIT license; https://github.com/orsinium-labs/generative-art; Changes copyright Jeremy Howard
5 |
6 | import math,svg
7 | from random import randint,random,seed
8 | from typing import NamedTuple
9 |
10 | class Point(NamedTuple):
11 | x: float
12 | y: float
13 | def distance_to(self, p):
14 | dx = abs(self.x - p.x)
15 | dy = abs(self.y - p.y)
16 | return math.sqrt(dx ** 2 + dy ** 2)
17 |
18 |
19 | class Circle(NamedTuple):
20 | c: Point
21 | r: float
22 | def contains(self, point): return self.c.distance_to(point) <= self.r
23 | def min_distance(self, other): return abs(self.r - self.c.distance_to(other))
24 | def render(self, stroke= "none", fill= "none"):
25 | return svg.Circle( cx=self.c.x, cy=self.c.y, r=self.r, stroke=stroke, stroke_width=1, fill=fill,)
26 |
27 |
28 | class Generator:
29 | size=800; min_shift=20; max_shift=60; min_width=20; min_circles=200; max_circles=300; min_radius=4
30 | def generate(self): return svg.SVG( width=self.size, height=self.size, xmlns='http://www.w3.org/2000/svg', elements=list(self.iter_elements()),)
31 | def color(self): return f'hsl({randint(0, 360)}, {randint(75, 100)}%, 50%)'
32 |
33 | def __init__(self):
34 | outer_r = self.size//2
35 | outer_center = Point(outer_r, outer_r)
36 | self.outer = Circle(c=outer_center, r=outer_r)
37 |
38 | def inner(self):
39 | inner_x = self.outer.r + randint(self.min_shift, self.max_shift)
40 | inner_y = self.outer.r + randint(self.min_shift, self.max_shift)
41 | inner_center = Point(inner_x, inner_y)
42 | r = self.outer.min_distance(inner_center) - self.min_width
43 | return Circle(c=inner_center, r=r)
44 |
45 | def iter_elements(self):
46 | assert self.inner().r < self.outer.r
47 | yield self.outer.render(fill="white")
48 | circles_count = randint(self.min_circles, self.max_circles)
49 | min_distance = math.ceil(self.inner().min_distance(Point(self.outer.r, self.outer.r)))
50 | circles = [self.inner()]
51 | for _ in range(circles_count):
52 | for _ in range(40):
53 | circle = self.get_random_circle(min_distance, circles)
54 | if circle is not None:
55 | circles.append(circle)
56 | yield circle.render(stroke=self.color())
57 | break
58 |
59 | def get_random_circle(self, min_distance: int, circles: list):
60 | distance = randint(min_distance, math.floor(self.outer.r))
61 | angle = random() * math.pi * 2
62 | cx = self.outer.r + math.cos(angle) * distance
63 | cy = self.outer.r + math.sin(angle) * distance
64 | center = Point(cx, cy)
65 | for other in circles:
66 | if other.contains(center): return None
67 | r = self.outer.min_distance(center)
68 | for other in circles: r = min(r, other.min_distance(center))
69 | if r < self.min_radius: return None
70 | return Circle(c=center, r=r)
71 |
72 | seed(42)
73 | print(Generator().generate())
74 |
--------------------------------------------------------------------------------
/nbdev/sync.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/06_sync.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['absolute_import', 'nbdev_update']
5 |
6 | # %% ../nbs/api/06_sync.ipynb 3
7 | from .imports import *
8 | from .config import *
9 | from .maker import *
10 | from .process import *
11 | from .process import _partition_cell
12 | from .export import *
13 | from .doclinks import _iter_py_cells
14 |
15 | from execnb.nbio import *
16 | from fastcore.script import *
17 | from fastcore.xtras import *
18 |
19 | import ast
20 | from importlib import import_module
21 |
22 | # %% ../nbs/api/06_sync.ipynb 5
23 | def absolute_import(name, fname, level):
24 | "Unwarps a relative import in `name` according to `fname`"
25 | if not level: return name
26 | mods = fname.split(os.path.sep)
27 | if not name: return '.'.join(mods)
28 | return '.'.join(mods[:len(mods)-level+1]) + f".{name}"
29 |
30 | # %% ../nbs/api/06_sync.ipynb 7
31 | @functools.lru_cache(maxsize=None)
32 | def _mod_files():
33 | midx = import_module(f'{get_config().lib_path.name}._modidx')
34 | return L(files for mod in midx.d['syms'].values() for _,files in mod.values()).unique()
35 |
36 | # %% ../nbs/api/06_sync.ipynb 8
37 | _re_import = re.compile("from\s+\S+\s+import\s+\S")
38 |
39 | # %% ../nbs/api/06_sync.ipynb 10
40 | def _to_absolute(code, py_path, lib_dir):
41 | if not _re_import.search(code): return code
42 | res = update_import(code, ast.parse(code).body, str(py_path.relative_to(lib_dir).parent), absolute_import)
43 | return ''.join(res) if res else code
44 |
45 | # %% ../nbs/api/06_sync.ipynb 11
46 | def _update_nb(nb_path, cells, lib_dir):
47 | "Update notebook `nb_path` with contents from `cells`"
48 | nbp = NBProcessor(nb_path, ExportModuleProc(), rm_directives=False)
49 | nbp.process()
50 | for cell in cells:
51 | assert cell.nb_path == nb_path
52 | nbcell = nbp.nb.cells[cell.idx]
53 | dirs,_ = _partition_cell(nbcell, 'python')
54 | nbcell.source = ''.join(dirs) + _to_absolute(cell.code, cell.py_path, lib_dir)
55 | write_nb(nbp.nb, nb_path)
56 |
57 | # %% ../nbs/api/06_sync.ipynb 12
58 | def _update_mod(py_path, lib_dir):
59 | "Propagate changes from cells in module `py_path` to corresponding notebooks"
60 | py_cells = L(_iter_py_cells(py_path)).filter(lambda o: o.nb != 'auto')
61 | for nb_path,cells in groupby(py_cells, 'nb_path').items(): _update_nb(nb_path, cells, lib_dir)
62 |
63 | # %% ../nbs/api/06_sync.ipynb 14
64 | @call_parse
65 | def nbdev_update(fname:str=None): # A Python file name to update
66 | "Propagate change in modules matching `fname` to notebooks that created them"
67 | if fname and fname.endswith('.ipynb'): raise ValueError("`nbdev_update` operates on .py files. If you wish to convert notebooks instead, see `nbdev_export`.")
68 | if os.environ.get('IN_TEST',0): return
69 | cfg = get_config()
70 | fname = Path(fname or cfg.lib_path)
71 | lib_dir = cfg.lib_path.parent
72 | files = globtastic(fname, file_glob='*.py', skip_folder_re='^[_.]').filter(lambda x: str(Path(x).absolute().relative_to(lib_dir) in _mod_files()))
73 | files.map(_update_mod, lib_dir=lib_dir)
74 |
--------------------------------------------------------------------------------
/nbs/explanations/why_nbdev.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "ffd2b608-1808-4661-b29d-c01800ccac6d",
6 | "metadata": {},
7 | "source": [
8 | "# Why nbdev\n",
9 | "\n",
10 | "> Why we develop software with nbdev\n",
11 | "\n",
12 | "- order: 4"
13 | ]
14 | },
15 | {
16 | "cell_type": "markdown",
17 | "id": "169c521b-1343-4ade-b978-47d7507e5b9a",
18 | "metadata": {},
19 | "source": [
20 | "Popular Python documentation standards such as [numpy docstrings](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard) and [sphinx](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html) facilitate documentation of source code with docstrings, which are limited in their expressiveness and functionality. Notebooks, on the other hand, offer a richer medium for authoring documentation (with markdown and code cells) compared to docstrings, and unlock new ways of documenting your code that are otherwhise infeasible.\n",
21 | "\n",
22 | "Furthermore, traditional testing frameworks such as [pytest](https://docs.pytest.org/) and [unittest](https://docs.python.org/3/library/unittest.html) encourage writing tests in a separate context that is disjointed from the associated source code and documentation. With nbdev, you write tests in the same context as your source code and documentation, such that tests can optionally become part of the narrative within your documentation. \n",
23 | "\n",
24 | "Since nbdev allows your colleagues and users to view the tests, code and documentation in a single context with a REPL that invites experimentation, the way you author code, documentation and tests in nbdev are very different than traditional software development workflows."
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "3087ce55-ed59-4251-9dd2-a1a2a1968f98",
30 | "metadata": {},
31 | "source": [
32 | "In [Notebook Best Practices](/tutorials/best_practices.ipynb#putting-it-all-together-an-annotated-example) we compare a function coded, tested, and documented in nbdev versus ordinary .py files. Here are a few of the challenges we faced with other approaches that led us to using nbdev. In .py files:\n",
33 | "\n",
34 | "1. Docstrings repeat information that is already contained in the function signature, such as names of parameters, default values, and types\n",
35 | "2. Examples are manually entered. This requires the author to copy and paste both the code and outputs. Furthermore, the author must manually re-enter or change these examples anytime the code changes, which is an error-prone process\n",
36 | "3. Examples are limited to short code snippets and cannot contain rich output like plots or graphics\n",
37 | "4. Examples cannot have prose interleaved with code except for code comments\n",
38 | "5. Readers of your code must copy and paste contents of the docstring if they wish to reproduce the examples."
39 | ]
40 | }
41 | ],
42 | "metadata": {
43 | "kernelspec": {
44 | "display_name": "Python 3 (ipykernel)",
45 | "language": "python",
46 | "name": "python3"
47 | }
48 | },
49 | "nbformat": 4,
50 | "nbformat_minor": 5
51 | }
52 |
--------------------------------------------------------------------------------
/nbdev/serve.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/17_serve.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['proc_nbs']
5 |
6 | # %% ../nbs/api/17_serve.ipynb 2
7 | import ast,subprocess,threading,sys
8 | from shutil import rmtree,copy2
9 |
10 | from fastcore.utils import *
11 | from fastcore.parallel import parallel
12 | from fastcore.script import call_parse
13 | from fastcore.meta import delegates
14 |
15 | from .config import get_config
16 | from .doclinks import nbglob_cli,nbglob
17 | from .processors import FilterDefaults
18 | import nbdev.serve_drv
19 |
20 | # %% ../nbs/api/17_serve.ipynb 4
21 | def _is_qpy(path:Path):
22 | "Is `path` a py script starting with frontmatter?"
23 | path = Path(path)
24 | if not path.suffix=='.py': return
25 | p = ast.parse(path.read_text())
26 | # try: p = ast.parse(path.read_text())
27 | # except: return
28 | if not p.body: return
29 | a = p.body[0]
30 | if isinstance(a, ast.Expr) and isinstance(a.value, ast.Constant):
31 | v = a.value.value.strip()
32 | vl = v.splitlines()
33 | if vl[0]=='---' and vl[-1]=='---': return '\n'.join(vl[1:-1])
34 |
35 | # %% ../nbs/api/17_serve.ipynb 5
36 | def _proc_file(s, cache, path, mtime=None):
37 | skips = ('_proc', '_docs', '_site')
38 | if not s.is_file() or any(o[0]=='.' or o in skips for o in s.parts): return
39 | d = cache/s.relative_to(path)
40 | if s.suffix=='.py': d = d.with_suffix('')
41 | if d.exists():
42 | dtime = d.stat().st_mtime
43 | if mtime: dtime = max(dtime, mtime)
44 | if s.stat().st_mtime<=dtime: return
45 |
46 | d.parent.mkdir(parents=True, exist_ok=True)
47 | if s.suffix=='.ipynb': return s,d,FilterDefaults
48 | md = _is_qpy(s)
49 | if md is not None: return s,d,md.strip()
50 | else: copy2(s,d)
51 |
52 | # %% ../nbs/api/17_serve.ipynb 7
53 | @delegates(nbglob_cli)
54 | def proc_nbs(
55 | path:str='', # Path to notebooks
56 | n_workers:int=defaults.cpus, # Number of workers
57 | force:bool=False, # Ignore cache and build all
58 | file_glob:str='', # Only include files matching glob
59 | file_re:str='', # Only include files matching glob
60 | **kwargs):
61 | "Process notebooks in `path` for docs rendering"
62 | cfg = get_config()
63 | cache = cfg.config_path/'_proc'
64 | path = Path(path or cfg.nbs_path)
65 | files = nbglob(path, func=Path, file_glob='', file_re='', **kwargs)
66 | if (path/'_quarto.yml').exists(): files.append(path/'_quarto.yml')
67 | if (path/'_extensions').exists(): files.extend(nbglob(path/'_extensions', func=Path, file_glob='', file_re='', skip_file_re='^[.]'))
68 |
69 | # If settings.ini or filter script newer than cache folder modified, delete cache
70 | chk_mtime = max(cfg.config_file.stat().st_mtime, Path(__file__).stat().st_mtime)
71 | cache.mkdir(parents=True, exist_ok=True)
72 | cache_mtime = cache.stat().st_mtime
73 | if force or (cache.exists and cache_mtime How to create nbdev powered docs without a library!\n",
11 | "- order: 9"
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "id": "504ef133-7e39-4ef8-a9d5-35a9ca6fbb9b",
17 | "metadata": {},
18 | "source": [
19 | "## Background\n",
20 | "\n",
21 | "While nbdev is great for authoring software, you may wish to utilize the power of nbdev for the purposes of documenting existing code, or **use various utilities of nbdev without having to write a python library**. For example, you can use the following features of nbdev without creating a python package:\n",
22 | "\n",
23 | "- Custom [nbdev directives](../explanations/directives.ipynb) such as [`#|hide_line`](../explanations/directives.ipynb#hide_line).\n",
24 | "- Testing with `nbdev_test`.\n",
25 | "- Automated entity linking with [doclinks](best_practices.ipynb#reference-related-symbols-with-doclinks).\n",
26 | "- Rendering API documentation with [docments and show_doc](best_practices.ipynb#document-parameters-with-docments).\n",
27 | "\n",
28 | "## Setup\n",
29 | "\n",
30 | "To setup a documentation only site, you can follow these steps:\n",
31 | "\n",
32 | "1. Create a nbdev repo the usual way, using `nbdev_new`\n",
33 | "2. Remove library files\n",
34 | "\n",
35 | "```bash\n",
36 | "rm setup.py .github/workflows/test.yaml nbs/00_core.ipynb\n",
37 | "```\n",
38 | "\n",
39 | "3. Remove your library folder (this will be the `lib_path` field in `settings.ini`):\n",
40 | "\n",
41 | "```bash\n",
42 | "rm -rf \n",
43 | "```\n",
44 | "\n",
45 | "## Usage\n",
46 | "\n",
47 | "After setting up your project, you can use various nbdev utilities per usual:\n",
48 | "\n",
49 | "- `nbdev_preview` for previewing your site\n",
50 | "- `nbdev_test` for testing your docs locally\n",
51 | "- Custom [nbdev directives](../explanations/directives.ipynb) will be available to you (but you must be careful not to use irrelevant ones like `#|export`).\n",
52 | "- If you created your nbdev docs site on GitHub, GitHub Actions will publish your docs for you automatically [as described here](../explanations/docs.ipynb#Deploying-Docs-With-GitHub-Actions).\n",
53 | "- You can publish your docs on other platforms as described [here](../explanations/docs.ipynb#Deploying-Your-Docs-On-Other-Platforms)."
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "id": "6c7c8ede-f3af-417d-8aa6-7658b5052288",
59 | "metadata": {},
60 | "source": [
61 | "## Demo\n",
62 | "\n",
63 | "A minimal example of a documentation-only site is located [here](https://github.com/hamelsmu/nolib_nbdev)."
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "60e597c1-9950-4927-ae5a-79fcc46497bd",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": []
73 | }
74 | ],
75 | "metadata": {
76 | "kernelspec": {
77 | "display_name": "Python 3 (ipykernel)",
78 | "language": "python",
79 | "name": "python3"
80 | }
81 | },
82 | "nbformat": 4,
83 | "nbformat_minor": 5
84 | }
85 |
--------------------------------------------------------------------------------
/tests/directives.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "23fe7353-8bac-4ac3-a7df-d80ff3186e0f",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "#|hide\n",
11 | " #| export"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "82d2db47-24d7-43ba-9a5a-d61af1a06b12",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "a = 'no directives'"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "id": "183e982d-7f38-417b-b774-ccadd6b76e2e",
27 | "metadata": {},
28 | "source": [
29 | "some md"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "id": "20b94777-1b99-4a0f-bf76-766343470047",
36 | "metadata": {},
37 | "outputs": [
38 | {
39 | "ename": "AssertionError",
40 | "evalue": "",
41 | "output_type": "error",
42 | "traceback": [
43 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
44 | "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
45 | "\u001b[0;32m/var/folders/jj/xl1rktcs6mn7ms6b8vvx8k4r0000gn/T/ipykernel_38804/2756147105.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m#|eval: false\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
46 | "\u001b[0;31mAssertionError\u001b[0m: "
47 | ]
48 | }
49 | ],
50 | "source": [
51 | "#|eval: false\n",
52 | "assert 1 == 2"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "id": "d4b7b3db-6831-46b7-aa3f-4e6cc0bc0a24",
59 | "metadata": {},
60 | "outputs": [
61 | {
62 | "ename": "AssertionError",
63 | "evalue": "",
64 | "output_type": "error",
65 | "traceback": [
66 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
67 | "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
68 | "\u001b[0;32m/var/folders/jj/xl1rktcs6mn7ms6b8vvx8k4r0000gn/T/ipykernel_38804/441260613.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m#| notest\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
69 | "\u001b[0;31mAssertionError\u001b[0m: "
70 | ]
71 | }
72 | ],
73 | "source": [
74 | "#| notest\n",
75 | "assert 1 == 2"
76 | ]
77 | },
78 | {
79 | "cell_type": "markdown",
80 | "id": "be48928b-20cf-4c18-be86-a6350a80e443",
81 | "metadata": {},
82 | "source": [
83 | "- more md"
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "id": "c87f288d-b021-4104-b436-1b7aab1a9e42",
90 | "metadata": {},
91 | "outputs": [],
92 | "source": []
93 | }
94 | ],
95 | "metadata": {
96 | "kernelspec": {
97 | "display_name": "Python 3 (ipykernel)",
98 | "language": "python",
99 | "name": "python3"
100 | }
101 | },
102 | "nbformat": 4,
103 | "nbformat_minor": 5
104 | }
105 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | Examples of unacceptable behavior by participants include:
13 |
14 | * The use of sexualized language or imagery and unwelcome sexual attention or
15 | advances
16 | * Trolling, insulting/derogatory comments, and personal or political attacks
17 | * Public or private harassment
18 | * Publishing others' private information, such as a physical or electronic
19 | address, without explicit permission
20 |
21 | These examples of unacceptable behaviour are requirements; we will not allow them
22 | in any fast.ai project, including this one.
23 |
24 | ## Our Standards
25 |
26 | Examples of behavior that contributes to creating a positive environment
27 | include:
28 |
29 | * Using welcoming and inclusive language
30 | * Being respectful of differing viewpoints and experiences
31 | * Gracefully accepting constructive criticism
32 | * Focusing on what is best for the community
33 | * Showing empathy towards other community members
34 |
35 | These examples are shown only to help you participate effectively -- they are not
36 | requirements, just requests and guidance.
37 |
38 | ## Our Responsibilities
39 |
40 | Project maintainers are responsible for clarifying the standards of acceptable
41 | behavior and are expected to take appropriate and fair corrective action in
42 | response to any instances of unacceptable behavior.
43 |
44 | Project maintainers have the right and responsibility to remove, edit, or
45 | reject comments, commits, code, wiki edits, issues, and other contributions
46 | that are not aligned to this Code of Conduct, or to ban temporarily or
47 | permanently any contributor for other behaviors that they deem inappropriate,
48 | threatening, offensive, or harmful.
49 |
50 | ## Scope
51 |
52 | This Code of Conduct applies both within project spaces and in public spaces
53 | when an individual is representing the project or its community. Examples of
54 | representing a project or community include using an official project e-mail
55 | address, posting via an official social media account or acting as an appointed
56 | representative at an online or offline event. Representation of a project may be
57 | further defined and clarified by project maintainers.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing or otherwise unacceptable behavior may be
62 | reported by contacting the project team at info@fast.ai. All
63 | complaints will be reviewed and investigated and will result in a response that
64 | is deemed necessary and appropriate to the circumstances. The project team is
65 | obligated to maintain confidentiality with regard to the reporter of an incident.
66 | Further details of specific enforcement policies may be posted separately.
67 |
68 | Project maintainers who do not follow or enforce the Code of Conduct in good
69 | faith may face temporary or permanent repercussions as determined by other
70 | members of the project's leadership.
71 |
72 | ## Attribution
73 |
74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
75 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
76 |
77 | [homepage]: https://www.contributor-covenant.org
78 |
--------------------------------------------------------------------------------
/tests/showdoc_test.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "c2ac41fe-6c3c-48c1-9531-490927859346",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from nbdev.showdoc import show_doc\n",
11 | "from fastcore.utils import patch\n",
12 | "from fastcore.meta import delegates"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "id": "f67bf2b2-88cf-446b-9241-f1351b129739",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "#|export\n",
23 | "class Foo:\n",
24 | " def __init__(self,\n",
25 | " a, # A thing\n",
26 | " b:int=None): # Another thing\n",
27 | " ..."
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": null,
33 | "id": "1f970abb-dfd0-41f0-9610-7e8b8ecdd552",
34 | "metadata": {},
35 | "outputs": [
36 | {
37 | "data": {
38 | "text/markdown": [
39 | "---\n",
40 | "\n",
41 | "### Foo\n",
42 | "\n",
43 | "> Foo (a, b:int=None)\n",
44 | "\n",
45 | "Initialize self. See help(type(self)) for accurate signature.\n",
46 | "\n",
47 | "| | **Type** | **Default** | **Details** |\n",
48 | "| -- | -------- | ----------- | ----------- |\n",
49 | "| a | | | A thing |\n",
50 | "| b | int | None | Another thing |"
51 | ],
52 | "text/plain": [
53 | ""
54 | ]
55 | },
56 | "execution_count": null,
57 | "metadata": {},
58 | "output_type": "execute_result"
59 | }
60 | ],
61 | "source": [
62 | "show_doc(Foo)"
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "id": "49cdb1d8-579f-4d3a-a93e-e52a35430810",
69 | "metadata": {},
70 | "outputs": [],
71 | "source": [
72 | "#|export\n",
73 | "@patch\n",
74 | "def a_method(self:Foo, a:list,b:dict,c):\n",
75 | " \"This is a method\"\n",
76 | " ..."
77 | ]
78 | },
79 | {
80 | "cell_type": "code",
81 | "execution_count": null,
82 | "id": "f8be1e1a",
83 | "metadata": {},
84 | "outputs": [],
85 | "source": [
86 | "#|export\n",
87 | "@delegates()\n",
88 | "class B(Foo):\n",
89 | " def __init__(self, c, d:str=\"a\", **kwargs): ..."
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "id": "87d056c5",
96 | "metadata": {},
97 | "outputs": [
98 | {
99 | "data": {
100 | "text/markdown": [
101 | "---\n",
102 | "\n",
103 | "### B\n",
104 | "\n",
105 | "> B (c, d:str='a', b:int=None)\n",
106 | "\n",
107 | "Initialize self. See help(type(self)) for accurate signature."
108 | ],
109 | "text/plain": [
110 | ""
111 | ]
112 | },
113 | "execution_count": null,
114 | "metadata": {},
115 | "output_type": "execute_result"
116 | }
117 | ],
118 | "source": [
119 | "show_doc(B)"
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "id": "e3c091c2",
126 | "metadata": {},
127 | "outputs": [],
128 | "source": []
129 | }
130 | ],
131 | "metadata": {
132 | "kernelspec": {
133 | "display_name": "Python 3 (ipykernel)",
134 | "language": "python",
135 | "name": "python3"
136 | }
137 | },
138 | "nbformat": 4,
139 | "nbformat_minor": 5
140 | }
141 |
--------------------------------------------------------------------------------
/nbs/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background: #33373F;
3 | --background-2: #41454E;
4 | --foreground: white;
5 | --foreground-2: #484d54;
6 | --content-width: 1000px;
7 | }
8 |
9 | html {
10 | font-family: Inter, SF Pro Display, -apple-system, sans-serif;
11 | }
12 |
13 | body {
14 | background: var(--background);
15 | color: var(--foreground);
16 | text-align: center;
17 | }
18 |
19 | #title-block-header {
20 | display: none;
21 | }
22 |
23 | .navbar {
24 | background: var(--background);
25 | text-align: left;
26 | font-size: 16px;
27 | }
28 |
29 | .title, .navbar-title {
30 | font-weight: bold;
31 | padding: 4px 8px;
32 | border-radius: 8px;
33 | margin-right: 4px;
34 | }
35 |
36 | .navbar-title {
37 | background: linear-gradient(240.01deg, #4B555B 15.29%, rgba(67, 78, 84, 0) 138.75%);
38 | }
39 |
40 | .title {
41 | background: linear-gradient(240.01deg, #78858C 15.29%, rgba(105, 117, 123, 0) 138.75%);
42 | }
43 |
44 | .content-block {
45 | margin-left: 30px;
46 | margin-right: 30px;
47 | overflow-x: hidden;
48 | }
49 |
50 | @media(min-width: 900px) {
51 | .content-block {
52 | margin-left: 50px;
53 | margin-right: 50px;
54 | }
55 | }
56 |
57 | @media(min-width: 1200px) {
58 | .content-block {
59 | max-width: var(--content-width);
60 | margin-left: auto;
61 | margin-right: auto;
62 | }
63 | }
64 |
65 | .hero-banner {
66 | padding-top: 20px;
67 | padding-bottom: 10px;
68 | margin-bottom: 30px;
69 | display: flex;
70 | flex-direction: column;
71 | align-items: center;
72 | }
73 |
74 | .hero-banner h1 {
75 | font-size: 56px;
76 | font-weight: bold;
77 | margin-top: 10px;
78 | margin-bottom: 40px;
79 | }
80 |
81 | .hero-banner h2 {
82 | border: none;
83 | font-size: 40px;
84 | font-weight: bold;
85 | margin-top: 10px;
86 | margin-bottom: 40px;
87 | }
88 |
89 | .hero-banner h3 {
90 | color: var(--foreground);
91 | font-size: 20px;
92 | max-width: 90%;
93 | margin-left: auto;
94 | margin-right: auto;
95 | margin-top: 0;
96 | margin-bottom: 40px;
97 | }
98 |
99 | @media(min-width: 900px) {
100 | .hero-banner h3 {
101 | max-width: 60%;
102 | }
103 | }
104 |
105 | @media(min-width: 1200px) {
106 | .hero-banner h3 {
107 | max-width: 75%;
108 | }
109 | }
110 |
111 | .mid-content {
112 | width: 100%;
113 | padding: 0;
114 | padding-top: 80px !important;
115 | padding-bottom: 80px;
116 | background: linear-gradient(235.87deg, #656A75 12.29%, #33373F 107.53%);
117 | overflow-x: hidden;
118 | }
119 |
120 | .testimonial {
121 | background: rgba(51, 55, 63, 0.4);
122 | border-radius: 12px;
123 | padding: 30px;
124 | text-align: left;
125 | }
126 |
127 | .testimonial p {
128 | margin: 0;
129 | padding: 0;
130 | }
131 |
132 | .testimonial img {
133 | margin: 0;
134 | padding: 0;
135 | display: inline;
136 | float: left;
137 | width: 50px;
138 | margin-right: 20px;
139 | border-radius: 8px;
140 | }
141 |
142 | .testimonial h1 {
143 | font-size: 16px;
144 | font-weight: bold;
145 | padding: 0;
146 | margin: 0;
147 | margin-bottom: 4px;
148 | }
149 |
150 | .testimonial h2 {
151 | border: none;
152 | font-size: 16px;
153 | padding: 0;
154 | margin: 0;
155 | color: #A2A8B4;
156 | }
157 |
158 | .testimonial h3 {
159 | margin: 0;
160 | margin-top: 30px;
161 | font-size: 16px;
162 | }
163 |
164 | .feature {
165 | font-size: 16px;
166 | height: 210px;
167 | background: var(--background-2);
168 | border-radius: 12px;
169 | padding: 30px;
170 | text-align: left;
171 | display: flex;
172 | flex-direction: column;
173 | }
174 |
175 | .feature img {
176 | margin-bottom: 20px;
177 | width: 48px;
178 | }
179 |
180 | .footer {
181 | color: #A2A8B4;
182 | font-size: 16px;
183 | margin-top: 40px;
184 | padding-top: 30px !important;
185 | border-top: 1px solid var(--foreground-2);
186 | }
187 |
188 | .btn-action-primary {
189 | color: #F9FAFB;
190 | background: #009AF1;
191 | font-weight: 700;
192 | line-height: 20px; /* identical to box height */
193 | }
194 |
195 | .btn-action-primary:hover, .btn-action-primary:active, .btn-action-primary:focus {
196 | background: #35acf0;
197 | }
198 |
199 | .btn-action {
200 | min-width: 165px;
201 | border-radius: 8px;
202 | padding: 20px 48px;
203 | border: none;
204 | }
205 |
206 | /* Below are needed to make .ipynb equivalent to .qmd */
207 |
208 | .figure {
209 | display: inline;
210 | }
211 |
212 | .quarto-figure {
213 | margin: 0;
214 | }
215 |
--------------------------------------------------------------------------------
/nbs/images/transform.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/nbdev/cli.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/13_cli.ipynb.
2 |
3 | # %% ../nbs/api/13_cli.ipynb 2
4 | from __future__ import annotations
5 | import warnings
6 |
7 | from .config import *
8 | from .process import *
9 | from .processors import *
10 | from .doclinks import *
11 | from .test import *
12 | from .clean import *
13 | from .quarto import nbdev_readme, refresh_quarto_yml
14 | from .frontmatter import FrontmatterProc
15 |
16 | from execnb.nbio import *
17 | from fastcore.meta import *
18 | from fastcore.utils import *
19 | from fastcore.script import *
20 | from fastcore.style import S
21 | from fastcore.shutil import rmtree,move
22 |
23 | from urllib.error import HTTPError
24 | from contextlib import redirect_stdout
25 | import os, tarfile, sys
26 |
27 | # %% auto 0
28 | __all__ = ['nbdev_filter', 'extract_tgz', 'nbdev_new', 'chelp']
29 |
30 | # %% ../nbs/api/13_cli.ipynb 5
31 | @call_parse
32 | def nbdev_filter(
33 | nb_txt:str=None, # Notebook text (uses stdin if not provided)
34 | fname:str=None, # Notebook to read (uses `nb_txt` if not provided)
35 | printit:bool_arg=True, # Print to stdout?
36 | ):
37 | "A notebook filter for Quarto"
38 | os.environ["IN_TEST"] = "1"
39 | try: filt = globals()[get_config().get('exporter', 'FilterDefaults')]()
40 | except FileNotFoundError: filt = FilterDefaults()
41 | if fname: nb_txt = Path(fname).read_text()
42 | elif not nb_txt: nb_txt = sys.stdin.read()
43 | nb = dict2nb(loads(nb_txt))
44 | if printit:
45 | with open(os.devnull, 'w') as dn:
46 | with redirect_stdout(dn): filt(nb)
47 | else: filt(nb)
48 | res = nb2str(nb)
49 | del os.environ["IN_TEST"]
50 | if printit: print(res, flush=True)
51 | else: return res
52 |
53 | # %% ../nbs/api/13_cli.ipynb 8
54 | def extract_tgz(url, dest='.'):
55 | from fastcore.net import urlopen
56 | with urlopen(url) as u: tarfile.open(mode='r:gz', fileobj=u).extractall(dest)
57 |
58 | # %% ../nbs/api/13_cli.ipynb 9
59 | def _render_nb(fn, cfg):
60 | "Render templated values like `{{lib_name}}` in notebook at `fn` from `cfg`"
61 | txt = fn.read_text()
62 | txt = txt.replace('from your_lib.core', f'from {cfg.lib_path}.core') # for compatibility with old templates
63 | for k,v in cfg.d.items(): txt = txt.replace('{{'+k+'}}', v)
64 | fn.write_text(txt)
65 |
66 | # %% ../nbs/api/13_cli.ipynb 10
67 | def _update_repo_meta(cfg):
68 | "Enable gh pages and update the homepage and description in your GitHub repo."
69 | token=os.getenv('GITHUB_TOKEN')
70 | if token:
71 | from ghapi.core import GhApi
72 | api = GhApi(owner=cfg.user, repo=cfg.repo, token=token)
73 | try: api.repos.update(homepage=f'{cfg.doc_host}{cfg.doc_baseurl}', description=cfg.description)
74 | except HTTPError:print(f"Could not update the description & URL on the repo: {cfg.user}/{cfg.repo} using $GITHUB_TOKEN.\n"
75 | "Use a token with the correction permissions or perform these steps manually.")
76 |
77 | # %% ../nbs/api/13_cli.ipynb 11
78 | @call_parse
79 | @delegates(nbdev_create_config)
80 | def nbdev_new(**kwargs):
81 | "Create an nbdev project."
82 | from ghapi.core import GhApi
83 | nbdev_create_config.__wrapped__(**kwargs)
84 | cfg = get_config()
85 | _update_repo_meta(cfg)
86 |
87 | path = Path()
88 | with warnings.catch_warnings():
89 | warnings.simplefilter('ignore', UserWarning)
90 | tag = GhApi(gh_host='https://api.github.com', authenticate=False).repos.get_latest_release('fastai', 'nbdev-template').tag_name
91 | url = f"https://github.com/fastai/nbdev-template/archive/{tag}.tar.gz"
92 | extract_tgz(url)
93 | tmpl_path = path/f'nbdev-template-{tag}'
94 |
95 | cfg.nbs_path.mkdir(exist_ok=True)
96 | nbexists = bool(first(cfg.nbs_path.glob('*.ipynb')))
97 | _nbs_path_sufs = ('.ipynb','.css')
98 | for o in tmpl_path.ls():
99 | p = cfg.nbs_path if o.suffix in _nbs_path_sufs else path
100 | if o.name == '_quarto.yml': continue
101 | if o.name == 'index.ipynb': _render_nb(o, cfg)
102 | if o.name == '00_core.ipynb' and not nbexists: move(o, p)
103 | elif not (path/o.name).exists(): move(o, p)
104 | rmtree(tmpl_path)
105 |
106 | refresh_quarto_yml()
107 |
108 | nbdev_export.__wrapped__()
109 | nbdev_readme.__wrapped__()
110 |
111 | # %% ../nbs/api/13_cli.ipynb 15
112 | @call_parse
113 | def chelp():
114 | "Show help for all console scripts"
115 | from fastcore.xtras import console_help
116 | console_help('nbdev')
117 |
--------------------------------------------------------------------------------
/nbdev/merge.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/07_merge.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['conf_re', 'unpatch', 'nbdev_fix', 'nbdev_merge']
5 |
6 | # %% ../nbs/api/07_merge.ipynb 2
7 | from .imports import *
8 | from .config import *
9 | from .export import *
10 | from .sync import *
11 |
12 | from execnb.nbio import *
13 | from fastcore.script import *
14 | from fastcore import shutil
15 |
16 | import subprocess
17 | from difflib import SequenceMatcher
18 |
19 | # %% ../nbs/api/07_merge.ipynb 16
20 | _BEG,_MID,_END = '<'*7,'='*7,'>'*7
21 | conf_re = re.compile(rf'^{_BEG}\s+(\S+)\n(.*?)^{_MID}\n(.*?)^{_END}\s+([\S ]+)\n', re.MULTILINE|re.DOTALL)
22 |
23 | def _unpatch_f(before, cb1, cb2, c, r):
24 | if cb1 is not None and cb1 != cb2: raise Exception(f'Branch mismatch: {cb1}/{cb2}')
25 | r.append(before)
26 | r.append(c)
27 | return cb2
28 |
29 | # %% ../nbs/api/07_merge.ipynb 17
30 | def unpatch(s:str):
31 | "Takes a string with conflict markers and returns the two original files, and their branch names"
32 | *main,last = conf_re.split(s)
33 | r1,r2,c1b,c2b = [],[],None,None
34 | for before,c1_branch,c1,c2,c2_branch in chunked(main, 5):
35 | c1b = _unpatch_f(before, c1b, c1_branch, c1, r1)
36 | c2b = _unpatch_f(before, c2b, c2_branch, c2, r2)
37 | return ''.join(r1+[last]), ''.join(r2+[last]), c1b, c2b
38 |
39 | # %% ../nbs/api/07_merge.ipynb 22
40 | def _make_md(code): return [dict(source=f'`{code}`', cell_type="markdown", metadata={})]
41 | def _make_conflict(a,b, branch1, branch2):
42 | return _make_md(f'{_BEG} {branch1}') + a+_make_md(_MID)+b + _make_md(f'{_END} {branch2}')
43 |
44 | def _merge_cells(a, b, brancha, branchb, theirs):
45 | matches = SequenceMatcher(None, a, b).get_matching_blocks()
46 | res,prev_sa,prev_sb,conflict = [],0,0,False
47 | for sa,sb,sz in matches:
48 | ca,cb = a[prev_sa:sa],b[prev_sb:sb]
49 | if ca or cb:
50 | res += _make_conflict(ca, cb, brancha, branchb)
51 | conflict = True
52 | if sz: res += b[sb:sb+sz] if theirs else a[sa:sa+sz]
53 | prev_sa,prev_sb = sa+sz,sb+sz
54 | return res,conflict
55 |
56 | # %% ../nbs/api/07_merge.ipynb 23
57 | @call_parse
58 | def nbdev_fix(nbname:str, # Notebook filename to fix
59 | outname:str=None, # Filename of output notebook (defaults to `nbname`)
60 | nobackup:bool_arg=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided
61 | theirs:bool=False, # Use their outputs and metadata instead of ours
62 | noprint:bool=False): # Do not print info about whether conflicts are found
63 | "Create working notebook from conflicted notebook `nbname`"
64 | nbname = Path(nbname)
65 | if not nobackup and not outname: shutil.copy(nbname, nbname.with_suffix('.ipynb.bak'))
66 | nbtxt = nbname.read_text()
67 | a,b,branch1,branch2 = unpatch(nbtxt)
68 | ac,bc = dict2nb(loads(a)),dict2nb(loads(b))
69 | dest = bc if theirs else ac
70 | cells,conflict = _merge_cells(ac.cells, bc.cells, branch1, branch2, theirs=theirs)
71 | dest.cells = cells
72 | write_nb(dest, ifnone(outname, nbname))
73 | if not noprint:
74 | if conflict: print("One or more conflict remains in the notebook, please inspect manually.")
75 | else: print("Successfully merged conflicts!")
76 | return conflict
77 |
78 | # %% ../nbs/api/07_merge.ipynb 27
79 | def _git_branch_merge():
80 | try: return only(v for k,v in os.environ.items() if k.startswith('GITHEAD'))
81 | except ValueError: return
82 |
83 | # %% ../nbs/api/07_merge.ipynb 28
84 | def _git_rebase_head():
85 | for d in ('apply','merge'):
86 | d = Path(f'.git/rebase-{d}')
87 | if d.is_dir():
88 | cmt = (d/'orig-head').read_text()
89 | msg = run(f'git show-branch --no-name {cmt}')
90 | return f'{cmt[:7]} ({msg})'
91 |
92 | # %% ../nbs/api/07_merge.ipynb 29
93 | def _git_merge_file(base, ours, theirs):
94 | "`git merge-file` with expected labels depending on if a `merge` or `rebase` is in-progress"
95 | l_theirs = _git_rebase_head() or _git_branch_merge() or 'THEIRS'
96 | cmd = f"git merge-file -L HEAD -L BASE -L '{l_theirs}' {ours} {base} {theirs}"
97 | return subprocess.run(cmd, shell=True, capture_output=True, text=True)
98 |
99 | # %% ../nbs/api/07_merge.ipynb 30
100 | @call_parse
101 | def nbdev_merge(base:str, ours:str, theirs:str, path:str):
102 | "Git merge driver for notebooks"
103 | if not _git_merge_file(base, ours, theirs).returncode: return
104 | theirs = str2bool(os.environ.get('THEIRS', False))
105 | return nbdev_fix.__wrapped__(ours, theirs=theirs)
106 |
--------------------------------------------------------------------------------
/nbdev/test.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/12_test.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['test_nb', 'nbdev_test']
5 |
6 | # %% ../nbs/api/12_test.ipynb 2
7 | import time,os,sys,traceback,contextlib, inspect
8 | from fastcore.basics import *
9 | from fastcore.imports import *
10 | from fastcore.foundation import *
11 | from fastcore.parallel import *
12 | from fastcore.script import *
13 | from fastcore.meta import delegates
14 |
15 | from .config import *
16 | from .doclinks import *
17 | from .process import NBProcessor, nb_lang
18 | from .frontmatter import FrontmatterProc
19 |
20 | from execnb.nbio import *
21 | from execnb.shell import *
22 |
23 | # %% ../nbs/api/12_test.ipynb 3
24 | def test_nb(fn, # file name of notebook to test
25 | skip_flags=None, # list of flags marking cells to skip
26 | force_flags=None, # list of flags marking cells to always run
27 | do_print=False, # print completion?
28 | showerr=True, # print errors to stderr?
29 | basepath=None): # path to add to sys.path
30 | "Execute tests in notebook in `fn` except those with `skip_flags`"
31 | if basepath: sys.path.insert(0, str(basepath))
32 | if not IN_NOTEBOOK: os.environ["IN_TEST"] = '1'
33 | flags=set(L(skip_flags)) - set(L(force_flags))
34 | nb = NBProcessor(fn, procs=FrontmatterProc, process=True).nb
35 | fm = getattr(nb, 'frontmatter_', {})
36 | if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
37 |
38 | def _no_eval(cell):
39 | if cell.cell_type != 'code': return True
40 | if 'nbdev_export'+'(' in cell.source: return True
41 | direc = getattr(cell, 'directives_', {}) or {}
42 | if direc.get('eval:', [''])[0].lower() == 'false': return True
43 | return flags & direc.keys()
44 |
45 | start = time.time()
46 | k = CaptureShell(fn)
47 | if do_print: print(f'Starting {fn}')
48 | try:
49 | with working_directory(fn.parent):
50 | k.run_all(nb, exc_stop=True, preproc=_no_eval)
51 | res = True
52 | except:
53 | if showerr: sys.stderr.write(k.prettytb(fname=fn)+'\n')
54 | res=False
55 | if do_print: print(f'- Completed {fn}')
56 | return res,time.time()-start
57 |
58 | # %% ../nbs/api/12_test.ipynb 8
59 | def _keep_file(p:Path, # filename for which to check for `indicator_fname`
60 | ignore_fname:str # filename that will result in siblings being ignored
61 | ) -> bool:
62 | "Returns False if `indicator_fname` is a sibling to `fname` else True"
63 | if p.exists(): return not bool(p.parent.ls().attrgot('name').filter(lambda x: x == ignore_fname))
64 | else: True
65 |
66 | # %% ../nbs/api/12_test.ipynb 10
67 | @call_parse
68 | @delegates(nbglob_cli)
69 | def nbdev_test(
70 | path:str=None, # A notebook name or glob to test
71 | flags:str='', # Space separated list of test flags to run that are normally ignored
72 | n_workers:int=None, # Number of workers
73 | timing:bool=False, # Time each notebook to see which are slow
74 | do_print:bool=False, # Print start and end of each notebook
75 | pause:float=0.01, # Pause time (in seconds) between notebooks to avoid race conditions
76 | ignore_fname:str='.notest', # Filename that will result in siblings being ignored
77 | **kwargs):
78 | "Test in parallel notebooks matching `path`, passing along `flags`"
79 | skip_flags = get_config().tst_flags.split()
80 | force_flags = flags.split()
81 | files = nbglob(path, as_path=True, **kwargs)
82 | files = [f.absolute() for f in sorted(files) if _keep_file(f, ignore_fname)]
83 | if len(files)==0: return print('No files were eligible for testing')
84 |
85 | if n_workers is None: n_workers = 0 if len(files)==1 else min(num_cpus(), 8)
86 | if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' else {'method':'forkserver'}
87 | else: kw = {}
88 | with working_directory(get_config().nbs_path):
89 | results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers,
90 | basepath=get_config().config_path, pause=pause, do_print=do_print, **kw)
91 | passed,times = zip(*results)
92 | if all(passed): print("Success.")
93 | else:
94 | _fence = '='*50
95 | failed = '\n\t'.join(f.name for p,f in zip(passed,files) if not p)
96 | sys.stderr.write(f"\nnbdev Tests Failed On The Following Notebooks:\n{_fence}\n\t{failed}\n")
97 | sys.exit(1)
98 | if timing:
99 | for i,t in sorted(enumerate(times), key=lambda o:o[1], reverse=True): print(f"{files[i].name}: {int(t)} secs")
100 |
--------------------------------------------------------------------------------
/nbs/tutorials/renderscript.qmd.py:
--------------------------------------------------------------------------------
1 | """---
2 | title: RenderScripts
3 | description: Introduction to RenderScripts -- create web pages using python
4 | order: 3
5 | ---"""
6 |
7 | from nbdev import qmd
8 | def im(fn, width, **kw): return qmd.img(f"/images/{fn}", width=f"{width}%", **kw)
9 |
10 | print("""*RenderScripts* are regular Python scripts, except that:
11 |
12 | - Rather than just having the extension `.py`, they have an extension like `.qmd.py`
13 | - They contain a module docstring containing [frontmatter](https://quarto.org/docs/tools/jupyter-lab.html#yaml-front-matter) (i.e three hyphens on a line, then some yaml, then another three hyphens on a line).
14 |
15 | These scripts are run when your site is rendered. Anything that they print to stdout becomes a new file in your site. The name of the file is the same as the name of the .py script, but without the `.py` extension. For instance, the page you're reading right now page is created by a script called `renderscript.qmd.py`, which you'll [find here](https://github.com/fastai/nbdev/blob/master/nbs/tutorials/renderscript.qmd.py).
16 |
17 | Hot/live reloading even works with these .py scripts -- so as soon as you save the script, you'll see the new output in your web browser.
18 |
19 | This approach can be particularly helpful for generating data-driven documents. For instance, consider this table, containing a list of the people with testimonials on nbdev's home page:
20 |
21 | """)
22 |
23 | testimonials = [
24 | ('chris-lattner.png', 'Chris Lattner', 'Inventor of Swift and LLVM'),
25 | ('fernando-pérez.jpeg', 'Fernando Pérez', 'Creator of Jupyter'),
26 | ('david-berg.jpeg', 'David Berg', 'Software Engineer, Netflix'),
27 | ('erik-gaasedelen.jpeg', 'Erik Gaasedelen', 'Software Engineer, Lyft'),
28 | ('roxanna-pourzand.jpeg', 'Roxanna Pourzand', 'Product Manager, Transform'),
29 | ('hugo-bowne-anderson.jpeg', 'Hugo Bowne-Anderson', 'Head of Developer Relations, Outerbounds')
30 | ]
31 | print(qmd.tbl_row(['','Name','Position']))
32 | print(qmd.tbl_sep([1,3,4]))
33 | for fname,name,position in testimonials:
34 | print(qmd.tbl_row([im(fname, 60), name, position]))
35 |
36 | print("""
37 |
38 | When creating a table like this, it can be tricky to ensure that markdown is correct and consistent for every row. It can be easier and more maintainable to programatically generate it. The table above is generated from the following python list:
39 |
40 | ::: {.column-screen-inset-right}
41 | ```python
42 | testimonials = [
43 | ('chris-lattner.png', 'Chris Lattner', 'Inventor of Swift and LLVM'),
44 | ('fernando-pérez.jpeg', 'Fernando Pérez', 'Creator of Jupyter'),
45 | ('david-berg.jpeg', 'David Berg', 'Software Engineer, Netflix'),
46 | ('erik-gaasedelen.jpeg', 'Erik Gaasedelen', 'Software Engineer, Lyft'),
47 | ('roxanna-pourzand.jpeg', 'Roxanna Pourzand', 'Product Manager, Transform'),
48 | ('hugo-bowne-anderson.jpeg', 'Hugo Bowne-Anderson', 'Head of Developer Relations, Outerbounds')
49 | ]
50 | ```
51 | :::
52 |
53 | To produce the table from this python list, the following four lines of code are used:
54 |
55 | ```python
56 | print(qmd.tbl_row(['','Name','Position']))
57 | print(qmd.tbl_sep([1,3,4]))
58 | for fname,name,position in testimonials:
59 | print(qmd.tbl_row([im(fname, 60), name, position]))
60 | ```
61 |
62 | [`tbl_hdr`](https://nbdev.fast.ai/api/qmd.html#tbl_hdr) and [`tbl_row`](https://nbdev.fast.ai/api/qmd.html#tbl_row) are two functions imported from the module `nbdev.qmd`. [`nbdev.qmd`](https://nbdev.fast.ai/api/qmd.html) is a small module that has some convenient functions for creating `.qmd` documents, such as the table creation functions used above. You can see more examples of their use in [index.qmd.py](https://github.com/fastai/nbdev/blob/master/nbs/index.qmd.py), which is the RenderScript which creates the [nbdev home page](https://nbdev.fast.ai). The nbdev home page is a more idiomatic example of how to use RenderScripts than the current page's source code -- we're only using RenderScript for the current page to provide a more simple example. In practice, we find that RenderScripts are best used for pages containing a lot of data-driven content, reusable components, and so forth.
63 |
64 | You can use RenderScripts to create any kind of file. For instance, the SVG below is created dynamically using [this script](https://github.com/fastai/nbdev/blob/master/nbs/images/circles.svg.py):
65 |
66 | """)
67 |
68 | print(im('circles.svg', 40))
69 |
70 | print("""
71 | Once you've run `nbdev_preview` or `nbdev_docs` you'll find your rendered document in the `_proc` directory, along with all of your processed notebooks. This can be helpful for debugging. You can also simply call your script directly from the shell (e.g. `python renderscript.qmd.py`) to view the printed output.""")
72 |
73 |
--------------------------------------------------------------------------------
/nbs/images/outerbounds.svg:
--------------------------------------------------------------------------------
1 |
14 |
15 |
--------------------------------------------------------------------------------
/nbs/images/novetta.svg:
--------------------------------------------------------------------------------
1 |
11 |
12 |
--------------------------------------------------------------------------------
/nbs/tutorials/qmd_intro.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Qmd Documents
3 | description: Introduction to qmd -- markdown on steroids
4 | order: 3
5 | execute:
6 | echo: false
7 | output: asis
8 | ---
9 |
10 | *Qmd documents* are [Markdown]() documents, but with loads of extra functionality provided by [Quarto](https://quarto.org/) and [Pandoc](https://pandoc.org/). nbdev uses Quarto to render its pages (with some extra functionality), and Quarto uses Pandoc to render its pages (with some extra functionality). Every markdown cell in an nbdev notebook is treated as qmd, and nbdev can publish plain qmd text files, and qmd [RenderScripts](/tutorials/renderscript.html). Therefore, it's a good idea to be familiar with the main features of qmd.
11 |
12 | Just like with RenderScripts, you can use hot/live reloading with plain qmd text files -- so as soon as you save the file, you'll see the new output in your web browser (assuming you've got `nbdev_preview` running).
13 |
14 | ## Computations
15 |
16 | You can generate data-driven documents using qmd files. For instance, consider this table (also shown in the RenderScript tutorial for comparison), containing a list of the people with testimonials on nbdev's home page:
17 |
18 | ```{python}
19 | from nbdev import qmd
20 | def im(fn, width, **kw): return qmd.img(f"/images/{fn}", width=f"{width}%", **kw)
21 |
22 | testimonials = [
23 | ('chris-lattner.png', 'Chris Lattner', 'Inventor of Swift and LLVM'),
24 | ('fernando-pérez.jpeg', 'Fernando Pérez', 'Creator of Jupyter'),
25 | ('david-berg.jpeg', 'David Berg', 'Software Engineer, Netflix'),
26 | ('erik-gaasedelen.jpeg', 'Erik Gaasedelen', 'Software Engineer, Lyft'),
27 | ('roxanna-pourzand.jpeg', 'Roxanna Pourzand', 'Product Manager, Transform'),
28 | ('hugo-bowne-anderson.jpeg', 'Hugo Bowne-Anderson', 'Head of Developer Relations, Outerbounds')
29 | ]
30 | print(qmd.tbl_row(['','Name','Position']))
31 | print(qmd.tbl_sep([1,3,4]))
32 | for fname,name,position in testimonials:
33 | print(qmd.tbl_row([im(fname, 60), name, position]))
34 | ```
35 |
36 | The table above is generated using an embedded [qmd computation block](https://quarto.org/docs/computations/python.html) from the following python list:
37 |
38 | ::: {.column-screen-inset-right}
39 | ```python
40 | testimonials = [
41 | ('chris-lattner.png', 'Chris Lattner', 'Inventor of Swift and LLVM'),
42 | ('fernando-pérez.jpeg', 'Fernando Pérez', 'Creator of Jupyter'),
43 | ('david-berg.jpeg', 'David Berg', 'Software Engineer, Netflix'),
44 | ('erik-gaasedelen.jpeg', 'Erik Gaasedelen', 'Software Engineer, Lyft'),
45 | ('roxanna-pourzand.jpeg', 'Roxanna Pourzand', 'Product Manager, Transform'),
46 | ('hugo-bowne-anderson.jpeg', 'Hugo Bowne-Anderson', 'Head of Developer Relations, Outerbounds')
47 | ]
48 | ```
49 | :::
50 |
51 | Just like in the RenderScript example, to produce the table from this python list, the following four lines of code are used:
52 |
53 | ```python
54 | print(qmd.tbl_row(['','Name','Position']))
55 | print(qmd.tbl_sep([1,3,4]))
56 | for fname,name,position in testimonials:
57 | print(qmd.tbl_row([im(fname, 60), name, position]))
58 | ```
59 |
60 | For data-driven documents such as this one, we add the following to the YAML frontmatter, which hides the code used to produce outputs, and also does not add any extra formatting to outputs:
61 |
62 | ```
63 | ---
64 | execute:
65 | echo: false
66 | output: asis
67 | ---
68 | ```
69 |
70 | Compare the source code of the RenderScript example and of the current page to see how computations are used in RenderScripts compared to plain qmd text files. We find that we like to use Notebooks for most pages we build, since they've got so much helpful functionality (such as pasting images directly into cells). We use RenderScripts for complex web pages like the nbdev home page, and qmd files for pages that are mainly markdown and don't need any notebook functionality.
71 |
72 | ## Formatting
73 |
74 | In addition to the [standard markdown formatting](https://quarto.org/docs/authoring/markdown-basics.html), Quarto qmd adds many additional features. Look at the full quarto docs to see everything it can do -- we'll just highlight a few of our favorites here.
75 |
76 | ### Divs and classes
77 |
78 | You can create HTML [divs](https://quarto.org/docs/authoring/markdown-basics.html#divs-and-spans), by surrounding lines with `:::`. Divs can include classes by placing `{.classname}` after the opening `:::`. Here's an example:
79 |
80 | ``` markdown
81 | ::: {.border}
82 | This content can be styled with a border
83 | :::
84 | ```
85 |
86 | This is how that's rendered:
87 |
88 | ::: {.border}
89 | This content can be styled with a border
90 | :::
91 |
92 | You might be wondering where that `border` class comes from... Quarto comes with support for [Bootstrap 5 and Bootswatch themes](https://quarto.org/docs/output-formats/html-themes.html) so there's lots of classes available you can use in your documents. Remember, all notebook markdown cells are also considered qmd, and can also use all the formatting tricks discussed in this section.
93 |
94 | ### Callouts
95 |
96 | A special kind of block you can use is the [callout block](https://quarto.org/docs/authoring/callouts.html). Here's an example:
97 |
98 | ```markdown
99 | :::{.callout-note}
100 | Note that there are five types of callouts, including:
101 | `note`, `warning`, `important`, `tip`, and `caution`.
102 | :::
103 | ```
104 |
105 | ...and here's how it's rendered:
106 |
107 | :::{.callout-note}
108 | Note that there are five types of callouts, including: `note`, `warning`, `important`, `tip`, and `caution`.
109 | :::
110 |
111 | ### Images
112 |
113 | You can add images (quarto calls them [figures](https://quarto.org/docs/authoring/figures.html) to your document, along with captions, and you can even arrange them into layouts. Here's an example:
114 |
115 | ```markdown
116 | ::: {layout-ncol=3}
117 | 
118 |
119 | 
120 |
121 | 
122 | :::
123 | ```
124 |
125 | ::: {layout-ncol=3}
126 | {width=100px fig-align="left"}
127 |
128 | {width=100px fig-align="left"}
129 |
130 | {width=100px fig-align="left"}
131 | :::
132 |
133 |
--------------------------------------------------------------------------------
/nbdev/process.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_process.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['langs', 'nb_lang', 'first_code_ln', 'extract_directives', 'opt_set', 'instantiate', 'NBProcessor', 'Processor']
5 |
6 | # %% ../nbs/api/03_process.ipynb 2
7 | from .config import *
8 | from .maker import *
9 | from .imports import *
10 |
11 | from execnb.nbio import *
12 | from fastcore.script import *
13 | from fastcore.imports import *
14 |
15 | from collections import defaultdict
16 |
17 | # %% ../nbs/api/03_process.ipynb 6
18 | # from https://github.com/quarto-dev/quarto-cli/blob/main/src/resources/jupyter/notebook.py
19 | langs = defaultdict(
20 | lambda: '#', r = "#", python = "#", julia = "#", scala = "//", matlab = "%", csharp = "//", fsharp = "//",
21 | c = ["/*","*/"], css = ["/*","*/"], sas = ["*",";"], powershell = "#", bash = "#", sql = "--", mysql = "--", psql = "--",
22 | lua = "--", cpp = "//", cc = "//", stan = "#", octave = "#", fortran = "!", fortran95 = "!", awk = "#", gawk = "#", stata = "*",
23 | java = "//", groovy = "//", sed = "#", perl = "#", ruby = "#", tikz = "%", javascript = "//", js = "//", d3 = "//", node = "//",
24 | sass = "//", coffee = "#", go = "//", asy = "//", haskell = "--", dot = "//", apl = "⍝")
25 |
26 | # %% ../nbs/api/03_process.ipynb 7
27 | def nb_lang(nb): return nested_attr(nb, 'metadata.kernelspec.language', 'python')
28 |
29 | # %% ../nbs/api/03_process.ipynb 9
30 | def _dir_pre(lang=None): return fr"\s*{langs[lang]}\s*\|"
31 | def _quarto_re(lang=None): return re.compile(_dir_pre(lang) + r'\s*[\w|-]+\s*:')
32 |
33 | # %% ../nbs/api/03_process.ipynb 11
34 | def _directive(s, lang='python'):
35 | s = re.sub('^'+_dir_pre(lang), f"{langs[lang]}|", s)
36 | if ':' in s: s = s.replace(':', ': ')
37 | s = (s.strip()[2:]).strip().split()
38 | if not s: return None
39 | direc,*args = s
40 | return direc,args
41 |
42 | # %% ../nbs/api/03_process.ipynb 12
43 | def _norm_quarto(s, lang='python'):
44 | "normalize quarto directives so they have a space after the colon"
45 | m = _quarto_re(lang).match(s)
46 | return m.group(0) + ' ' + _quarto_re(lang).sub('', s).lstrip() if m else s
47 |
48 | # %% ../nbs/api/03_process.ipynb 14
49 | _cell_mgc = re.compile(r"^\s*%%\w+")
50 |
51 | def first_code_ln(code_list, re_pattern=None, lang='python'):
52 | "get first line number where code occurs, where `code_list` is a list of code"
53 | if re_pattern is None: re_pattern = _dir_pre(lang)
54 | return first(i for i,o in enumerate(code_list) if o.strip() != '' and not re.match(re_pattern, o) and not _cell_mgc.match(o))
55 |
56 | # %% ../nbs/api/03_process.ipynb 17
57 | def _partition_cell(cell, lang):
58 | if not cell.source: return [],[]
59 | lines = cell.source.splitlines(True)
60 | first_code = first_code_ln(lines, lang=lang)
61 | return lines[:first_code],lines[first_code:]
62 |
63 | # %% ../nbs/api/03_process.ipynb 18
64 | def extract_directives(cell, remove=True, lang='python'):
65 | "Take leading comment directives from lines of code in `ss`, remove `#|`, and split"
66 | dirs,code = _partition_cell(cell, lang)
67 | if not dirs: return {}
68 | if remove:
69 | # Leave Quarto directives and cell magic in place for later processing
70 | cell['source'] = ''.join([_norm_quarto(o, lang) for o in dirs if _quarto_re(lang).match(o) or _cell_mgc.match(o)] + code)
71 | return dict(L(_directive(s, lang) for s in dirs).filter())
72 |
73 | # %% ../nbs/api/03_process.ipynb 22
74 | def opt_set(var, newval):
75 | "newval if newval else var"
76 | return newval if newval else var
77 |
78 | # %% ../nbs/api/03_process.ipynb 23
79 | def instantiate(x, **kwargs):
80 | "Instantiate `x` if it's a type"
81 | return x(**kwargs) if isinstance(x,type) else x
82 |
83 | def _mk_procs(procs, nb): return L(procs).map(instantiate, nb=nb)
84 |
85 | # %% ../nbs/api/03_process.ipynb 24
86 | def _is_direc(f): return getattr(f, '__name__', '-')[-1]=='_'
87 |
88 | # %% ../nbs/api/03_process.ipynb 25
89 | class NBProcessor:
90 | "Process cells and nbdev comments in a notebook"
91 | def __init__(self, path=None, procs=None, nb=None, debug=False, rm_directives=True, process=False):
92 | self.nb = read_nb(path) if nb is None else nb
93 | self.lang = nb_lang(self.nb)
94 | for cell in self.nb.cells: cell.directives_ = extract_directives(cell, remove=rm_directives, lang=self.lang)
95 | self.procs = _mk_procs(procs, nb=self.nb)
96 | self.debug,self.rm_directives = debug,rm_directives
97 | if process: self.process()
98 |
99 | def _process_cell(self, proc, cell):
100 | if not hasattr(cell,'source'): return
101 | if cell.cell_type=='code' and cell.directives_:
102 | # Option 1: `proc` is directive name with `_` suffix
103 | f = getattr(proc, '__name__', '-').rstrip('_')
104 | if f in cell.directives_: self._process_comment(proc, cell, f)
105 |
106 | # Option 2: `proc` contains a method named `_{directive}_`
107 | for cmd in cell.directives_:
108 | f = getattr(proc, f'_{cmd}_', None)
109 | if f: self._process_comment(f, cell, cmd)
110 | if callable(proc) and not _is_direc(proc): cell = opt_set(cell, proc(cell))
111 |
112 | def _process_comment(self, proc, cell, cmd):
113 | args = cell.directives_[cmd]
114 | if self.debug: print(cmd, args, proc)
115 | return proc(cell, *args)
116 |
117 | def _proc(self, proc):
118 | if hasattr(proc,'begin'): proc.begin()
119 | for cell in self.nb.cells: self._process_cell(proc, cell)
120 | if hasattr(proc,'end'): proc.end()
121 | self.nb.cells = [c for c in self.nb.cells if c and getattr(c,'source',None) is not None]
122 | for i,cell in enumerate(self.nb.cells): cell.idx_ = i
123 |
124 | def process(self):
125 | "Process all cells with all processors"
126 | for proc in self.procs: self._proc(proc)
127 |
128 | # %% ../nbs/api/03_process.ipynb 35
129 | class Processor:
130 | "Base class for processors"
131 | def __init__(self, nb): self.nb = nb
132 | def cell(self, cell): pass
133 | def __call__(self, cell): return self.cell(cell)
134 |
--------------------------------------------------------------------------------
/nbs/index.qmd.py:
--------------------------------------------------------------------------------
1 | """---
2 | title: Home
3 | pagetitle: nbdev – Create delightful software with Jupyter Notebooks
4 | page-layout: custom
5 | section-divs: false
6 | css: index.css
7 | toc: false
8 | image: https://nbdev.fast.ai/images/card.png
9 | description: Write, test, document, and distribute software packages and technical articles — all in one place, your notebook.
10 | ---"""
11 |
12 | from fastcore.foundation import L
13 | from nbdev import qmd
14 |
15 | def img(fname, classes=None, **kwargs): return qmd.img(f"images/{fname}", classes=classes, **kwargs)
16 | def btn(txt, link): return qmd.btn(txt, link=link, classes=['btn-action-primary', 'btn-action', 'btn', 'btn-success', 'btn-lg'])
17 | def banner(txt, classes=None, style=None): return qmd.div(txt, L('hero-banner')+classes, style=style)
18 |
19 | features = L(
20 | ('docs', 'Beautiful technical documentation and scientific articles with Quarto'),
21 | ('testing', 'Out-of-the-box continuous integration with GitHub Actions'),
22 | ('packaging', 'Publish code to PyPI and conda, and prose to GitHub Pages'),
23 | ('vscode', 'Two-way sync with your favourite IDEs'),
24 | ('jupyter', 'Write prose, code, and tests in notebooks — no context-switching'),
25 | ('git', 'Git-friendly notebooks: human-readable merge conflicts; no unwanted metadata')
26 | )
27 |
28 | testms = L(
29 | ('chris-lattner.png', 'Chris Lattner', 'Inventor of Swift and LLVM', 'I really do think [nbdev] is a huge step forward for programming environments.'),
30 | ('fernando-pérez.jpeg', 'Fernando Pérez', 'Creator of Jupyter', '[nbdev] should be celebrated and used a lot more — I have kept a tab with your original nbdev blog post open for months in Chrome because of how often I refer to it and point others to this work.'),
31 | ('david-berg.jpeg', 'David Berg', 'Software Engineer, Netflix', 'Prior to using nbdev, documentation was the most cumbersome aspect of our software development process… Using nbdev allows us to spend more time creating rich prose around the many code snippets guaranteeing the whole experience is robust.
nbdev has turned what was once a chore into a natural extension of the notebook-based testing we were already doing.'),
32 | ('erik-gaasedelen.jpeg', 'Erik Gaasedelen', 'Software Engineer, Lyft', 'I use this in production at my company. It’s an awesome tool… nbdev streamlines everything so I can write docs, tests, and code all in one place… The packaging is also really well thought out.
From my point of view it is close to a Pareto improvement over traditional Python library development.'),
33 | ('roxanna-pourzand.jpeg', 'Roxanna Pourzand', 'Product Manager, Transform', 'We’re so excited about using nbdev. Our product is technical so our resulting documentation includes a lot of code-based examples. Before nbdev, we had no way of maintaining our code examples and ensuring that it was up-to-date for both command inputs and outputs. It was all manual. With nbdev, we now have this under control in a sustainable way. Since we’ve deployed these docs, we also had a situation where we were able to identify a bug in one of our interfaces, which we found by seeing the error that was output in the documentation.'),
34 | ('hugo-bowne-anderson.jpeg', 'Hugo Bowne-Anderson', 'Head of Developer Relations, Outerbounds', 'Nbdev has transformed the way we write documentation. Gone are the days of worrying about broken code examples when our API changes or due to human errors associated with copying & pasting code into markdown files. The authoring experience of nbdev is also powerful, allowing us to write prose and live code in a unified interface, which allows more experimentation with technical content. On top of this, nbdev allows us to include unit tests in our documentation which mitigates the burden of maintaining the docs over time.')
35 | )
36 |
37 | def industry(im, **kwargs): return qmd.div(img(im, **kwargs), ["g-col-12", "g-col-sm-6", "g-col-md-3"])
38 |
39 | def testm(im, nm, detl, txt):
40 | return qmd.div(f"""{img(im, link=True)}
41 |
42 | # {nm}
43 |
44 | ## {detl}
45 |
46 | ### {txt}""", ["testimonial", "g-col-12", "g-col-md-6"])
47 |
48 | expert_d = qmd.div('\n'.join(testms.starmap(testm)), ['content-block', 'grid', 'gap-4'])
49 |
50 | def feature(im, desc): return qmd.div(f"{img(im+'.svg')}\n\n{desc}\n", ['feature', 'g-col-12', 'g-col-sm-6', 'g-col-md-4'])
51 |
52 | feature_d = qmd.div('\n'.join(features.starmap(feature)), ['grid', 'gap-4'], style={"padding-bottom": "60px"})
53 |
54 | def b(*args, **kwargs): print(banner (*args, **kwargs))
55 | def d(*args, **kwargs): print(qmd.div(*args, **kwargs))
56 |
57 | ###
58 | # Output section
59 | ###
60 |
61 | b(f"""# Create delightful software with Jupyter Notebooks
62 |
63 | ### Write, test, document, and distribute software packages and technical articles — all in one place, your notebook.
64 |
65 | {btn('Get started', '/getting_started.ipynb')}
66 |
67 | {img('card.png', style={"margin-top": "40px", "margin-bottom": "40px"}, link=True)}""", "content-block")
68 |
69 | industries = '\n'.join([
70 | industry('netflix.svg', height=26, relative=("top",1)),
71 | industry('transform.svg', height=26, relative=("bottom",1)),
72 | industry('outerbounds.svg', height=26, relative=("bottom",1)),
73 | industry('novetta.svg', height=30, relative=("top",1)),
74 | industry('amd.svg', height=22),
75 | industry('overstory.png', height=26),
76 | industry('bom.png', height=46, relative=("bottom",12)),
77 | industry('lyft.svg', height=34),
78 | ])
79 | industries = qmd.div(industries, 'grid', style={"column-gap": "50px"})
80 |
81 | b(f"""## Trusted in industry
82 |
83 | {industries}""", "mid-content")
84 |
85 | feature_h = banner(f"""## Interactive programming without compromise
86 |
87 | ### Traditional programming environments throw away the result of your exploration in REPLs or notebooks. nbdev makes exploration an integral part of your workflow, all while promoting software engineering best practices.""")
88 |
89 | d(feature_h+feature_d, "content-block")
90 |
91 | expert_b = banner("## Here's what experts are saying")
92 |
93 | d(expert_b+expert_d, "mid-content")
94 |
95 | b(f"""## Get started in seconds
96 |
97 | {btn('Install nbdev', '/getting_started.ipynb')}""", 'content-block', style={"margin-top": "40px"})
98 |
99 |
--------------------------------------------------------------------------------
/nbs/images/jupyter.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/nbs/api/17_serve.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "36769638",
6 | "metadata": {},
7 | "source": [
8 | "# serve\n",
9 | "\n",
10 | "> A parallel ipynb processor (experimental)\n",
11 | "- order: 17"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "f305fea5",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "#| default_exp serve"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "id": "6899a335",
28 | "metadata": {},
29 | "outputs": [],
30 | "source": [
31 | "#|export\n",
32 | "import ast,subprocess,threading,sys\n",
33 | "from shutil import rmtree,copy2\n",
34 | "\n",
35 | "from fastcore.utils import *\n",
36 | "from fastcore.parallel import parallel\n",
37 | "from fastcore.script import call_parse\n",
38 | "from fastcore.meta import delegates\n",
39 | "\n",
40 | "from nbdev.config import get_config\n",
41 | "from nbdev.doclinks import nbglob_cli,nbglob\n",
42 | "from nbdev.processors import FilterDefaults\n",
43 | "import nbdev.serve_drv"
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": null,
49 | "id": "32c11e55",
50 | "metadata": {},
51 | "outputs": [],
52 | "source": [
53 | "#|hide\n",
54 | "__file__ = 'serve.ipynb'"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "id": "65766a33",
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "#|export\n",
65 | "def _is_qpy(path:Path):\n",
66 | " \"Is `path` a py script starting with frontmatter?\"\n",
67 | " path = Path(path)\n",
68 | " if not path.suffix=='.py': return\n",
69 | " p = ast.parse(path.read_text())\n",
70 | "# try: p = ast.parse(path.read_text())\n",
71 | "# except: return\n",
72 | " if not p.body: return\n",
73 | " a = p.body[0]\n",
74 | " if isinstance(a, ast.Expr) and isinstance(a.value, ast.Constant):\n",
75 | " v = a.value.value.strip()\n",
76 | " vl = v.splitlines()\n",
77 | " if vl[0]=='---' and vl[-1]=='---': return '\\n'.join(vl[1:-1])"
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": null,
83 | "id": "abc3835a",
84 | "metadata": {},
85 | "outputs": [],
86 | "source": [
87 | "#|export\n",
88 | "def _proc_file(s, cache, path, mtime=None):\n",
89 | " skips = ('_proc', '_docs', '_site')\n",
90 | " if not s.is_file() or any(o[0]=='.' or o in skips for o in s.parts): return\n",
91 | " d = cache/s.relative_to(path)\n",
92 | " if s.suffix=='.py': d = d.with_suffix('')\n",
93 | " if d.exists():\n",
94 | " dtime = d.stat().st_mtime\n",
95 | " if mtime: dtime = max(dtime, mtime)\n",
96 | " if s.stat().st_mtime<=dtime: return\n",
97 | "\n",
98 | " d.parent.mkdir(parents=True, exist_ok=True)\n",
99 | " if s.suffix=='.ipynb': return s,d,FilterDefaults\n",
100 | " md = _is_qpy(s)\n",
101 | " if md is not None: return s,d,md.strip()\n",
102 | " else: copy2(s,d)"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": null,
108 | "id": "8b3b08f7",
109 | "metadata": {},
110 | "outputs": [],
111 | "source": [
112 | "#|hide\n",
113 | "# __file__ = '../tutorials/circles.svg.py'\n",
114 | "# p = Path(__file__).resolve()\n",
115 | "# cfg = get_config()\n",
116 | "# cache = cfg.config_path/'_proc'\n",
117 | "# path = Path(cfg.nbs_path)\n",
118 | "# _proc_file(p, cache, path)"
119 | ]
120 | },
121 | {
122 | "cell_type": "code",
123 | "execution_count": null,
124 | "id": "14463227",
125 | "metadata": {},
126 | "outputs": [],
127 | "source": [
128 | "#|export\n",
129 | "@delegates(nbglob_cli)\n",
130 | "def proc_nbs(\n",
131 | " path:str='', # Path to notebooks\n",
132 | " n_workers:int=defaults.cpus, # Number of workers\n",
133 | " force:bool=False, # Ignore cache and build all\n",
134 | " file_glob:str='', # Only include files matching glob\n",
135 | " file_re:str='', # Only include files matching glob\n",
136 | " **kwargs):\n",
137 | " \"Process notebooks in `path` for docs rendering\"\n",
138 | " cfg = get_config()\n",
139 | " cache = cfg.config_path/'_proc'\n",
140 | " path = Path(path or cfg.nbs_path)\n",
141 | " files = nbglob(path, func=Path, file_glob='', file_re='', **kwargs)\n",
142 | " if (path/'_quarto.yml').exists(): files.append(path/'_quarto.yml')\n",
143 | " if (path/'_extensions').exists(): files.extend(nbglob(path/'_extensions', func=Path, file_glob='', file_re='', skip_file_re='^[.]'))\n",
144 | "\n",
145 | " # If settings.ini or filter script newer than cache folder modified, delete cache\n",
146 | " chk_mtime = max(cfg.config_file.stat().st_mtime, Path(__file__).stat().st_mtime)\n",
147 | " cache.mkdir(parents=True, exist_ok=True)\n",
148 | " cache_mtime = cache.stat().st_mtime\n",
149 | " if force or (cache.exists and cache_mtime Basic qmd generation helpers (experimental)\n",
11 | "- order: 15"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "2b548a67",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "#|default_exp qmd"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "id": "6a35c7c4-748f-4c82-a9bf-c780a8d83e90",
28 | "metadata": {},
29 | "outputs": [],
30 | "source": [
31 | "#|export\n",
32 | "from __future__ import annotations\n",
33 | "import sys,os,inspect\n",
34 | "\n",
35 | "from fastcore.utils import *\n",
36 | "from fastcore.meta import delegates"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "id": "1e623a3d-3e77-44c6-adf3-4768b78328c5",
43 | "metadata": {},
44 | "outputs": [],
45 | "source": [
46 | "#|hide\n",
47 | "from fastcore.test import *"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": null,
53 | "id": "5a64f1f4",
54 | "metadata": {},
55 | "outputs": [],
56 | "source": [
57 | "#| export\n",
58 | "def meta(md, # Markdown to add meta to\n",
59 | " classes=None, # List of CSS classes to add\n",
60 | " style=None, # Dict of CSS styles to add\n",
61 | " **kwargs): # Additional attributes to add to meta\n",
62 | " \"A metadata section for qmd div in `{}`\"\n",
63 | " if style: kwargs['style'] = \"; \".join(f'{k}: {v}' for k,v in style.items())\n",
64 | " props = ' '.join(f'{k}=\"{v}\"' for k,v in kwargs.items())\n",
65 | " classes = ' '.join('.'+c for c in L(classes))\n",
66 | " meta = []\n",
67 | " if classes: meta.append(classes)\n",
68 | " if props: meta.append(props)\n",
69 | " meta = ' '.join(meta)\n",
70 | " return md + (\"{\" + meta + \"}\" if meta else \"\")"
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": null,
76 | "id": "52637a70",
77 | "metadata": {},
78 | "outputs": [],
79 | "source": [
80 | "#| export\n",
81 | "def div(txt, # Markdown to add meta to\n",
82 | " classes=None, # List of CSS classes to add\n",
83 | " style=None, # Dict of CSS styles to add\n",
84 | " **kwargs):\n",
85 | " \"A qmd div with optional metadata section\"\n",
86 | " return meta(\"::: \", classes=classes, style=style, **kwargs) + f\"\\n\\n{txt}\\n\\n:::\\n\\n\""
87 | ]
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "id": "f9f499f4",
93 | "metadata": {},
94 | "outputs": [],
95 | "source": [
96 | "#| export\n",
97 | "def img(fname, # Image to link to\n",
98 | " classes=None, # List of CSS classes to add\n",
99 | " style=None, # Dict of CSS styles to add\n",
100 | " height=None, # Height attribute\n",
101 | " relative=None, # Tuple of (position,px)\n",
102 | " link=False, # Hyperlink to this image\n",
103 | " **kwargs):\n",
104 | " \"A qmd image\"\n",
105 | " kwargs,style = kwargs or {}, style or {}\n",
106 | " if height: kwargs[\"height\"]= f\"{height}px\"\n",
107 | " if relative:\n",
108 | " pos,px = relative\n",
109 | " style[\"position\"] = \"relative\"\n",
110 | " style[pos] = f\"{px}px\"\n",
111 | " res = meta(f'', classes=classes, style=style, **kwargs)\n",
112 | " return f'[{res}]({fname})' if link else res"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "id": "16d7aa5f",
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "#| export\n",
123 | "def btn(txt, # Button text\n",
124 | " link, # Button link URL\n",
125 | " classes=None, # List of CSS classes to add\n",
126 | " style=None, # Dict of CSS styles to add\n",
127 | " **kwargs):\n",
128 | " \"A qmd button\"\n",
129 | " return meta(f'[{txt}]({link})', classes=classes, style=style, role=\"button\")"
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": null,
135 | "id": "e414ed33",
136 | "metadata": {},
137 | "outputs": [],
138 | "source": [
139 | "#| export\n",
140 | "def tbl_row(cols:list, # Auto-stringified columns to show in the row\n",
141 | " ):\n",
142 | " \"Create a markdown table row from `cols`\"\n",
143 | " return '|' + '|'.join(str(c or '') for c in cols) + '|'"
144 | ]
145 | },
146 | {
147 | "cell_type": "code",
148 | "execution_count": null,
149 | "id": "38f0641b",
150 | "metadata": {},
151 | "outputs": [],
152 | "source": [
153 | "#| export\n",
154 | "def tbl_sep(sizes:int|list=3 # List of column sizes, or single `int` if all sizes the same\n",
155 | " ):\n",
156 | " \"Create a markdown table separator with relative column size `sizes`\"\n",
157 | " if isinstance(sizes,int): sizes = [3]*sizes\n",
158 | " return tbl_row('-'*s for s in sizes)"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "a0d6553e-05ab-4db2-b739-aeee59a9ff31",
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "#| export\n",
169 | "def _install_nbdev():\n",
170 | " return div('''#### pip\n",
171 | "\n",
172 | "```sh\n",
173 | "pip install -U nbdev\n",
174 | "```\n",
175 | "\n",
176 | "#### conda\n",
177 | "\n",
178 | "```sh\n",
179 | "conda install -c fastai nbdev\n",
180 | "```\n",
181 | "''', ['panel-tabset'])"
182 | ]
183 | },
184 | {
185 | "cell_type": "markdown",
186 | "id": "aa35b010",
187 | "metadata": {},
188 | "source": [
189 | "## Export -"
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": null,
195 | "id": "3d8031ce",
196 | "metadata": {},
197 | "outputs": [],
198 | "source": [
199 | "#|hide\n",
200 | "import nbdev; nbdev.nbdev_export()"
201 | ]
202 | },
203 | {
204 | "cell_type": "code",
205 | "execution_count": null,
206 | "id": "12588a26-43a6-42c4-bacd-896293c871ab",
207 | "metadata": {},
208 | "outputs": [],
209 | "source": []
210 | }
211 | ],
212 | "metadata": {
213 | "kernelspec": {
214 | "display_name": "Python 3 (ipykernel)",
215 | "language": "python",
216 | "name": "python3"
217 | }
218 | },
219 | "nbformat": 4,
220 | "nbformat_minor": 5
221 | }
222 |
--------------------------------------------------------------------------------
/nbs/tutorials/pre_commit.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "2f2a6752-fa19-4096-8bd2-5ca82e1a9ffb",
6 | "metadata": {},
7 | "source": [
8 | "# Pre-Commit Hooks\n",
9 | "\n",
10 | "> How to use nbdev's git pre-commit hooks\n",
11 | "- order: 6"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "6b6629a1-e679-48ec-8cd8-21d65d5422b2",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "#| hide\n",
22 | "from nbdev.qmd import _install_nbdev"
23 | ]
24 | },
25 | {
26 | "cell_type": "markdown",
27 | "id": "f9c2fff2-db3a-4ba2-ba16-8a6b1d9aa9e1",
28 | "metadata": {},
29 | "source": [
30 | "We provide hooks for the [pre-commit framework](https://pre-commit.com/) to catch and fix uncleaned and unexported notebooks, locally, without having to wait for continuous integration pipelines to run. "
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "id": "4411d7f9-5dc1-4ba8-b7a3-8e822ad21901",
36 | "metadata": {},
37 | "source": [
38 | "They might also be useful as an alternative to the [Jupyter clean hook](/tutorials/git_friendly_jupyter.ipynb#nbdev_clean-on-saving-notebooks-in-jupyter) if you're using a notebook editor that isn't yet supported (e.g. VSCode)."
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "id": "0c3c7192-892a-4ff9-acb0-a89fe8a2e4bc",
44 | "metadata": {},
45 | "source": [
46 | "## Install pre-commit"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "id": "d161041d-7585-4177-beb2-06d2fb3919e9",
52 | "metadata": {},
53 | "source": [
54 | "...Install pre-commit (check [their latest instructions](https://pre-commit.com/#install) if you have any difficulty with these commands):\n",
55 | "\n",
56 | "::: {.panel-tabset}\n",
57 | "\n",
58 | "#### pip\n",
59 | "\n",
60 | "```sh\n",
61 | "pip install pre-commit\n",
62 | "```\n",
63 | "\n",
64 | "#### conda\n",
65 | "\n",
66 | "```sh\n",
67 | "conda install -c conda-forge pre-commit\n",
68 | "```\n",
69 | "\n",
70 | "#### homebrew (macOS)\n",
71 | "\n",
72 | "```sh\n",
73 | "brew install pre-commit\n",
74 | "```\n",
75 | "\n",
76 | ":::"
77 | ]
78 | },
79 | {
80 | "cell_type": "markdown",
81 | "id": "e94e4d29-e137-4f08-b7ee-b6aa06dc49a9",
82 | "metadata": {},
83 | "source": [
84 | "## Configure pre-commit for your repo"
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "id": "ebd3c984-8419-4fa9-9c5a-abca1924f88d",
90 | "metadata": {},
91 | "source": [
92 | "Create a file named `.pre-commit-config.yaml` in the root of your repo, with the following contents:\n",
93 | "\n",
94 | "```yaml\n",
95 | "repos:\n",
96 | "- repo: https://github.com/fastai/nbdev\n",
97 | " rev: 2.2.10\n",
98 | " hooks:\n",
99 | " - id: nbdev_clean\n",
100 | " - id: nbdev_export\n",
101 | "```\n",
102 | "\n",
103 | "Include only the hook(s) you'd like to run, as well as any other [supported hooks](https://pre-commit.com/hooks.html)."
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "id": "16dab296-26f5-47a3-8230-e63ea403f320",
109 | "metadata": {},
110 | "source": [
111 | "::: {.callout-tip}\n",
112 | "If you expect all collaborators to use pre-commit, add the `.pre-commit-config.yaml` file to your repo. Otherwise, add it to your `.gitignore`.\n",
113 | ":::"
114 | ]
115 | },
116 | {
117 | "cell_type": "markdown",
118 | "id": "a801d92a-30f5-4c87-b57f-ad0e11b72c0e",
119 | "metadata": {},
120 | "source": [
121 | "Install pre-commit hooks into your repo:\n",
122 | "\n",
123 | "```sh\n",
124 | "pre-commit install\n",
125 | "```"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "id": "a4a7cd01-6106-48fb-96fe-26d0f0e71572",
131 | "metadata": {},
132 | "source": [
133 | "## Make a commit and enjoy pre-commit in action"
134 | ]
135 | },
136 | {
137 | "cell_type": "markdown",
138 | "id": "4f04112c-38d3-4422-a819-bf08342214d4",
139 | "metadata": {},
140 | "source": [
141 | "When you do a `git commit` in a repo that has pre-commit hooks installed, your new workflow will be as follows:\n",
142 | "\n",
143 | "1. pre-commit runs each hook on your _staged_ changes (as in, changes that you `git add`ed)\n",
144 | "2. If a hook changes files -- for example, if a commited notebook wasn't cleaned -- pre-commit stops the commit, leaving those changes as _unstaged_\n",
145 | "3. You can now stage those changes and make any edits required to get pre-commit to pass\n",
146 | "4. Redo the `git commit`, and if it succeeds, your commit will be created."
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "id": "436f12f9-81e6-4c16-a407-c23a510e7327",
152 | "metadata": {},
153 | "source": [
154 | "Using it in practice isn't as complicated as it might sound. The best way to figure out if it works for you is to give it a try."
155 | ]
156 | },
157 | {
158 | "cell_type": "markdown",
159 | "id": "dc9b349e-db45-42cd-9268-4f87f754ce51",
160 | "metadata": {},
161 | "source": [
162 | "## How to override pre-commit if you get stuck"
163 | ]
164 | },
165 | {
166 | "cell_type": "markdown",
167 | "id": "570e52db-3608-47cb-9a23-70b44b9b71ad",
168 | "metadata": {},
169 | "source": [
170 | "If you struggle to get pre-commit to pass a commit that you absolutely think is correct, you can [temporarily disable a hook](https://pre-commit.com/#temporarily-disabling-hooks) like this:\n",
171 | "\n",
172 | "```sh\n",
173 | "SKIP=hook git commit\n",
174 | "```\n",
175 | "\n",
176 | "...where `hook` refers to a valid hook in your configuration, for example, to disable the `nbdev_export` hook:\n",
177 | "\n",
178 | "```sh\n",
179 | "SKIP=nbdev_export git commit\n",
180 | "```\n",
181 | "\n",
182 | "You can also disable pre-commit entirely with the `--no-verify` flag:\n",
183 | "\n",
184 | "```sh\n",
185 | "git commit --no-verify\n",
186 | "```"
187 | ]
188 | },
189 | {
190 | "cell_type": "markdown",
191 | "id": "09492b76-359a-40a8-9c3d-a19d3d80bd19",
192 | "metadata": {},
193 | "source": [
194 | "Finally, if you decide it's not for you, you can completely remove pre-commit hooks from your repo with:\n",
195 | "\n",
196 | "```sh\n",
197 | "pre-commit uninstall\n",
198 | "```"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": null,
204 | "id": "2607c475-3efc-4e9d-a956-e3be801ecbf7",
205 | "metadata": {},
206 | "outputs": [],
207 | "source": []
208 | }
209 | ],
210 | "metadata": {
211 | "kernelspec": {
212 | "display_name": "Python 3 (ipykernel)",
213 | "language": "python",
214 | "name": "python3"
215 | }
216 | },
217 | "nbformat": 4,
218 | "nbformat_minor": 5
219 | }
220 |
--------------------------------------------------------------------------------
/nbs/explanations/config.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Settings.ini\n",
8 | "\n",
9 | "> The nbdev configuration file\n",
10 | "- order: 1"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "metadata": {},
17 | "outputs": [],
18 | "source": [
19 | "#| hide\n",
20 | "from fastcore.utils import *\n",
21 | "from nbdev.config import *\n",
22 | "\n",
23 | "from nbdev.showdoc import *"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {},
29 | "source": [
30 | "All of nbdev's configuration is done through a file called `settings.ini` which lives in the root of your repo. It's in [ConfigParser](https://docs.python.org/3/library/configparser.html) format. For example, here's the first few lines of nbdev's settings.ini file"
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": null,
36 | "metadata": {},
37 | "outputs": [
38 | {
39 | "name": "stdout",
40 | "output_type": "stream",
41 | "text": [
42 | "[DEFAULT]\r\n",
43 | "lib_name = nbdev\r\n",
44 | "description = Create delightful software with Jupyter Notebooks\r\n",
45 | "copyright = 2020 onwards, Jeremy Howard\r\n",
46 | "keywords = nbdev fastai jupyter notebook export\r\n",
47 | "user = fastai\r\n",
48 | "author = Jeremy Howard and Hamel Husain\r\n",
49 | "author_email = j@fast.ai\r\n",
50 | "branch = master\r\n",
51 | "min_python = 3.7\r\n"
52 | ]
53 | }
54 | ],
55 | "source": [
56 | "#| exec_doc\n",
57 | "#| echo: false\n",
58 | "!head ../../settings.ini"
59 | ]
60 | },
61 | {
62 | "cell_type": "markdown",
63 | "metadata": {},
64 | "source": [
65 | "You can create this file with `nbdev_create_config` (in which case you pass the settings manually), or with `nbdev_new` (which sets it up automatically for you from your repo settings). Here are all of nbdev's settings (excluding the `path` and `cfg_name` parameters which decide where the config file is saved):"
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": null,
71 | "metadata": {},
72 | "outputs": [
73 | {
74 | "data": {
75 | "text/markdown": [
76 | "| | **Type** | **Default** | **Details** |\n",
77 | "| -- | -------- | ----------- | ----------- |\n",
78 | "| user | str | | Repo username |\n",
79 | "| author | str | | Package author's name |\n",
80 | "| author_email | str | | Package author's email address |\n",
81 | "| description | str | | Short summary of the package |\n",
82 | "| path | str | . | Path to create config file |\n",
83 | "| cfg_name | str | settings.ini | Name of config file to create |\n",
84 | "| lib_name | str | None | Package name, defaults to local repo folder name passed to `apply_defaults` |\n",
85 | "| branch | str | master | Repo default branch passed to `apply_defaults` |\n",
86 | "| git_url | str | https://github.com/%(user)s/%(lib_name)s | Repo URL passed to `apply_defaults` |\n",
87 | "| custom_sidebar | bool_arg | False | Use a custom sidebar.yml? passed to `apply_defaults` |\n",
88 | "| nbs_path | str | . | Path to notebooks passed to `apply_defaults` |\n",
89 | "| lib_path | str | %(lib_name)s | Path to package root passed to `apply_defaults` |\n",
90 | "| doc_path | str | _docs | Path to rendered docs passed to `apply_defaults` |\n",
91 | "| tst_flags | str | | Test flags passed to `apply_defaults` |\n",
92 | "| version | str | 0.0.1 | Version of this release passed to `apply_defaults` |\n",
93 | "| doc_host | str | https://%(user)s.github.io | Hostname for docs passed to `apply_defaults` |\n",
94 | "| doc_baseurl | str | /%(lib_name)s | Base URL for docs passed to `apply_defaults` |\n",
95 | "| keywords | str | nbdev jupyter notebook python | Package keywords passed to `apply_defaults` |\n",
96 | "| license | str | apache2 | License for the package passed to `apply_defaults` |\n",
97 | "| copyright | str | None | Copyright for the package, defaults to '`current_year` onwards, `author`' passed to `apply_defaults` |\n",
98 | "| status | str | 3 | Development status PyPI classifier passed to `apply_defaults` |\n",
99 | "| min_python | str | 3.7 | Minimum Python version PyPI classifier passed to `apply_defaults` |\n",
100 | "| audience | str | Developers | Intended audience PyPI classifier passed to `apply_defaults` |\n",
101 | "| language | str | English | Language PyPI classifier passed to `apply_defaults` |\n",
102 | "| recursive | bool_arg | False | Include subfolders in notebook globs? passed to `apply_defaults` |\n",
103 | "| black_formatting | bool_arg | False | Format libraries with black? passed to `apply_defaults` |\n",
104 | "| readme_nb | str | index.ipynb | Notebook to export as repo readme passed to `apply_defaults` |\n",
105 | "| title | str | %(lib_name)s | Quarto website title passed to `apply_defaults` |\n",
106 | "| allowed_metadata_keys | str | | Preserve the list of keys in the main notebook metadata passed to `apply_defaults` |\n",
107 | "| allowed_cell_metadata_keys | str | | Preserve the list of keys in cell level metadata passed to `apply_defaults` |\n",
108 | "| jupyter_hooks | bool | True | Run Jupyter hooks? passed to `apply_defaults` |\n",
109 | "| clean_ids | bool | True | Remove ids from plaintext reprs? passed to `apply_defaults` |\n",
110 | "| custom_quarto_yml | bool | False | Use a custom _quarto.yml? passed to `apply_defaults` |"
111 | ],
112 | "text/plain": [
113 | ""
114 | ]
115 | },
116 | "execution_count": null,
117 | "metadata": {},
118 | "output_type": "execute_result"
119 | }
120 | ],
121 | "source": [
122 | "#| exec_doc\n",
123 | "#| echo: false\n",
124 | "DocmentTbl(nbdev_create_config)"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "metadata": {},
130 | "source": [
131 | "You can customise nbdev for all repositories for your user with a `~/.config/nbdev/settings.ini` file."
132 | ]
133 | },
134 | {
135 | "cell_type": "markdown",
136 | "metadata": {},
137 | "source": [
138 | "In order for Git actions to run smoothly, add `requirements` and `dev_requirements` with required packages in `settings.ini`.\n",
139 | "\n",
140 | "see [here](https://github.com/fastai/nbdev/blob/master/settings.ini) as a reference."
141 | ]
142 | },
143 | {
144 | "cell_type": "code",
145 | "execution_count": null,
146 | "metadata": {},
147 | "outputs": [],
148 | "source": []
149 | }
150 | ],
151 | "metadata": {
152 | "kernelspec": {
153 | "display_name": "Python 3 (ipykernel)",
154 | "language": "python",
155 | "name": "python3"
156 | }
157 | },
158 | "nbformat": 4,
159 | "nbformat_minor": 4
160 | }
161 |
--------------------------------------------------------------------------------
/tests/01_everything.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "raw",
5 | "metadata": {},
6 | "source": [
7 | "---\n",
8 | "title: Foo\n",
9 | "execute:\n",
10 | " echo: false\n",
11 | "---"
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "# A Title\n",
19 | "> A description"
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "This notebook is used to demonstrate and test all the features of nbdev's export functionality. See the notebooks in `nbs` for how it's used."
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "#|export\n",
36 | "from __future__ import print_function"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "#|export\n",
46 | "from __future__ import absolute_import\n",
47 | "from fastcore.utils import patch,patch_to"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": null,
53 | "metadata": {},
54 | "outputs": [],
55 | "source": [
56 | "#|hide\n",
57 | "#|default_exp everything\n",
58 | "#|default_cls_lvl 3"
59 | ]
60 | },
61 | {
62 | "cell_type": "markdown",
63 | "metadata": {},
64 | "source": [
65 | "Each symbol name below has >=2 parts, split on `_`. First part is a unique name. Second is `y` if it should be exported. Third is `nall` if it shouldn't appear in `__all__`."
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": null,
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "import nbdev.x,y,nbdev.z\n",
75 | "from nbdev.x import *"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": null,
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "#|export\n",
85 | "def a_y(): ..."
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "metadata": {},
92 | "outputs": [],
93 | "source": [
94 | "#|export\n",
95 | "def a_y(f): # should only appear once\n",
96 | " def ai_n(): ...\n",
97 | " class a2i_n(): ...\n",
98 | " return f"
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "execution_count": null,
104 | "metadata": {},
105 | "outputs": [],
106 | "source": [
107 | "#|hide\n",
108 | "#|export\n",
109 | "def b_y(a:bool)->int: ..."
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": null,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "#|exporti\n",
119 | "#just another comment\n",
120 | "def c_y_nall(): ..."
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": null,
126 | "metadata": {},
127 | "outputs": [],
128 | "source": [
129 | "#|export\n",
130 | "class d_y():\n",
131 | " def di_n(): ...\n",
132 | " class d2i_n(): ...\n",
133 | "\n",
134 | "@a_y\n",
135 | "async def e_y(): ..."
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": null,
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "#|export\n",
145 | "@patch\n",
146 | "def d3i_n(self:d_y): ..."
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": null,
152 | "metadata": {},
153 | "outputs": [],
154 | "source": [
155 | "#|export\n",
156 | "@patch_to(d_y)\n",
157 | "def d4i_n(self): ..."
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "#|export\n",
167 | "def _f_y_nall(): ..."
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": null,
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "#|export some.thing\n",
177 | "def h_n(): ..."
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": null,
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "def i_n(): ..."
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": null,
192 | "metadata": {},
193 | "outputs": [],
194 | "source": [
195 | "def j_n(): ...\n",
196 | "#|export"
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": null,
202 | "metadata": {},
203 | "outputs": [],
204 | "source": [
205 | "#export is used in a full sentence here\n",
206 | "#therefore this is not exported\n",
207 | "def k_n(): ..."
208 | ]
209 | },
210 | {
211 | "cell_type": "code",
212 | "execution_count": null,
213 | "metadata": {},
214 | "outputs": [],
215 | "source": [
216 | "#exporting\n",
217 | "def l_n(): ..."
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": null,
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "#|export\n",
227 | "m_y = n_y = 1"
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": null,
233 | "metadata": {},
234 | "outputs": [],
235 | "source": [
236 | "#|export\n",
237 | "exec(\"o_y=1\")\n",
238 | "exec(\"p_y=1\")\n",
239 | "_all_ = [o_y, 'p_y']"
240 | ]
241 | },
242 | {
243 | "cell_type": "code",
244 | "execution_count": null,
245 | "metadata": {},
246 | "outputs": [],
247 | "source": [
248 | "#|export\n",
249 | "q_y,_r_n = (1,1)"
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": null,
255 | "metadata": {},
256 | "outputs": [],
257 | "source": [
258 | "#|export\n",
259 | "a_y.test = 1"
260 | ]
261 | },
262 | {
263 | "cell_type": "code",
264 | "execution_count": null,
265 | "metadata": {},
266 | "outputs": [],
267 | "source": [
268 | "#|printme testing\n",
269 | "_tmp = \"Cell for testing processor subclass\""
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": null,
275 | "metadata": {},
276 | "outputs": [
277 | {
278 | "data": {
279 | "text/html": [
280 | "a test\n"
281 | ],
282 | "text/plain": [
283 | ""
284 | ]
285 | },
286 | "metadata": {},
287 | "output_type": "display_data"
288 | }
289 | ],
290 | "source": [
291 | "%%html\n",
292 | "a test"
293 | ]
294 | },
295 | {
296 | "cell_type": "code",
297 | "execution_count": null,
298 | "metadata": {},
299 | "outputs": [
300 | {
301 | "name": "stdout",
302 | "output_type": "stream",
303 | "text": [
304 | "\u001b[94mhello\n"
305 | ]
306 | }
307 | ],
308 | "source": [
309 | "print('\\033[94mhello')"
310 | ]
311 | },
312 | {
313 | "cell_type": "code",
314 | "execution_count": null,
315 | "metadata": {},
316 | "outputs": [
317 | {
318 | "name": "stdout",
319 | "output_type": "stream",
320 | "text": [
321 | "do not print me!\n"
322 | ]
323 | }
324 | ],
325 | "source": [
326 | "#|eval:false\n",
327 | "print(\"do not print me!\")"
328 | ]
329 | },
330 | {
331 | "cell_type": "code",
332 | "execution_count": null,
333 | "metadata": {},
334 | "outputs": [],
335 | "source": []
336 | }
337 | ],
338 | "metadata": {
339 | "kernelspec": {
340 | "display_name": "Python 3 (ipykernel)",
341 | "language": "python",
342 | "name": "python3"
343 | }
344 | },
345 | "nbformat": 4,
346 | "nbformat_minor": 4
347 | }
348 |
--------------------------------------------------------------------------------
/nbdev/migrate.py:
--------------------------------------------------------------------------------
1 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/16_migrate.ipynb.
2 |
3 | # %% auto 0
4 | __all__ = ['MigrateProc', 'fp_md_fm', 'migrate_nb', 'migrate_md', 'nbdev_migrate']
5 |
6 | # %% ../nbs/api/16_migrate.ipynb 2
7 | from .process import *
8 | from .frontmatter import *
9 | from .frontmatter import _fm2dict, _re_fm_md, _dict2fm, _insertfm
10 | from .processors import *
11 | from .config import get_config, read_nb
12 | from .sync import write_nb
13 | from .showdoc import show_doc
14 | from fastcore.all import *
15 | import shutil
16 |
17 | # %% ../nbs/api/16_migrate.ipynb 5
18 | def _cat_slug(fmdict):
19 | "Get the partial slug from the category front matter."
20 | slug = '/'.join(fmdict.get('categories', ''))
21 | return '/' + slug if slug else ''
22 |
23 | # %% ../nbs/api/16_migrate.ipynb 7
24 | def _file_slug(fname):
25 | "Get the partial slug from the filename."
26 | p = Path(fname)
27 | dt = '/'+p.name[:10].replace('-', '/')+'/'
28 | return dt + p.stem[11:]
29 |
30 | # %% ../nbs/api/16_migrate.ipynb 9
31 | def _replace_fm(d:dict, # dictionary you wish to conditionally change
32 | k:str, # key to check
33 | val:str,# value to check if d[k] == v
34 | repl_dict:dict #dictionary that will be used as a replacement
35 | ):
36 | "replace key `k` in dict `d` if d[k] == val with `repl_dict`"
37 | if str(d.get(k, '')).lower().strip() == str(val.lower()).strip():
38 | d.pop(k)
39 | d = merge(d, repl_dict)
40 | return d
41 |
42 | def _fp_fm(d):
43 | "create aliases for fastpages front matter to match Quarto front matter."
44 | d = _replace_fm(d, 'search_exclude', 'true', {'search':'false'})
45 | d = _replace_fm(d, 'hide', 'true', {'draft': 'true'})
46 | return d
47 |
48 | # %% ../nbs/api/16_migrate.ipynb 10
49 | def _fp_image(d):
50 | "Correct path of fastpages images to reference the local directory."
51 | prefix = 'images/copied_from_nb/'
52 | if d.get('image', '').startswith(prefix): d['image'] = d['image'].replace(prefix, '')
53 | return d
54 |
55 | # %% ../nbs/api/16_migrate.ipynb 11
56 | def _rm_quote(s):
57 | title = re.search('''"(.*?)"''', s)
58 | return title.group(1) if title else s
59 |
60 | def _is_jekyll_post(path): return bool(re.search(r'^\d{4}-\d{2}-\d{2}-', Path(path).name))
61 |
62 | def _fp_convert(fm:dict, path:Path):
63 | "Make fastpages frontmatter Quarto complaint and add redirects."
64 | fs = _file_slug(path)
65 | if _is_jekyll_post(path):
66 | fm = compose(_fp_fm, _fp_image)(fm)
67 | if 'permalink' in fm: fm['aliases'] = [f"{fm['permalink'].strip()}"]
68 | else: fm['aliases'] = [f'{_cat_slug(fm) + fs}']
69 | if not fm.get('date'):
70 | _,y,m,d,_ = fs.split('/')
71 | fm['date'] = f'{y}-{m}-{d}'
72 |
73 | if fm.get('summary') and not fm.get('description'): fm['description'] = fm['summary']
74 | if fm.get('tags') and not fm.get('categories'):
75 | if isinstance(fm['tags'], str): fm['categories'] = fm['tags'].split()
76 | elif isinstance(fm['tags'], list): fm['categories'] = fm['tags']
77 | for k in ['title', 'description']:
78 | if k in fm: fm[k] = _rm_quote(fm[k])
79 | if fm.get('comments'): fm.pop('comments') #true by itself is not a valid value for comments https://quarto.org/docs/output-formats/html-basics.html#commenting, and the default is true
80 | return fm
81 |
82 | # %% ../nbs/api/16_migrate.ipynb 14
83 | class MigrateProc(Processor):
84 | "Migrate fastpages front matter in notebooks to a raw cell."
85 | def begin(self):
86 | self.nb.frontmatter_ = _fp_convert(self.nb.frontmatter_, self.nb.path_)
87 | if getattr(first(self.nb.cells), 'cell_type', None) == 'raw': del self.nb.cells[0]
88 | _insertfm(self.nb, self.nb.frontmatter_)
89 |
90 | # %% ../nbs/api/16_migrate.ipynb 21
91 | def fp_md_fm(path):
92 | "Make fastpages front matter in markdown files quarto compliant."
93 | p = Path(path)
94 | md = p.read_text()
95 | fm = _fm2dict(md, nb=False)
96 | if fm:
97 | fm = _fp_convert(fm, path)
98 | return _re_fm_md.sub(_dict2fm(fm), md)
99 | else: return md
100 |
101 | # %% ../nbs/api/16_migrate.ipynb 30
102 | _alias = merge({k:'code-fold: true' for k in ['collapse', 'collapse_input', 'collapse_hide']},
103 | {'collapse_show':'code-fold: show', 'hide_input': 'echo: false', 'hide': 'include: false', 'hide_output': 'output: false'})
104 | def _subv1(s): return _alias.get(s, s)
105 |
106 | # %% ../nbs/api/16_migrate.ipynb 31
107 | def _re_v1():
108 | d = ['default_exp', 'export', 'exports', 'exporti', 'hide', 'hide_input', 'collapse_show', 'collapse',
109 | 'collapse_hide', 'collapse_input', 'hide_output', 'default_cls_lvl']
110 | d += L(get_config().tst_flags).filter()
111 | d += [s.replace('_', '-') for s in d] # allow for hyphenated version of old directives
112 | _tmp = '|'.join(list(set(d)))
113 | return re.compile(f"^[ \f\v\t]*?(#)\s*({_tmp})(?!\S)", re.MULTILINE)
114 |
115 | def _repl_directives(code_str):
116 | def _fmt(x): return f"#| {_subv1(x[2].replace('-', '_').strip())}"
117 | return _re_v1().sub(_fmt, code_str)
118 |
119 | # %% ../nbs/api/16_migrate.ipynb 33
120 | def _repl_v1dir(cell):
121 | "Replace nbdev v1 with v2 directives."
122 | if cell.get('source') and cell.get('cell_type') == 'code':
123 | ss = cell['source'].splitlines()
124 | first_code = first_code_ln(ss, re_pattern=_re_v1())
125 | if not first_code: first_code = len(ss)
126 | if not ss: pass
127 | else: cell['source'] = '\n'.join([_repl_directives(c) for c in ss[:first_code]] + ss[first_code:])
128 |
129 | # %% ../nbs/api/16_migrate.ipynb 38
130 | _re_callout = re.compile(r'^>\s(Warning|Note|Important|Tip):(.*)', flags=re.MULTILINE)
131 | def _co(x): return ":::{.callout-"+x[1].lower()+"}\n\n" + f"{x[2].strip()}\n\n" + ":::"
132 | def _convert_callout(s):
133 | "Convert nbdev v1 to v2 callouts."
134 | return _re_callout.sub(_co, s)
135 |
136 | # %% ../nbs/api/16_migrate.ipynb 45
137 | _re_video = re.compile(r'^>\syoutube:(.*)', flags=re.MULTILINE)
138 | def _v(x): return "{{< " + f"video {x[1].strip()}" + " >}}"
139 | def _convert_video(s):
140 | "Replace nbdev v1 with v2 video embeds."
141 | return _re_video.sub(_v, s)
142 |
143 | # %% ../nbs/api/16_migrate.ipynb 49
144 | _shortcuts = compose(_convert_video, _convert_callout)
145 |
146 | def _repl_v1shortcuts(cell):
147 | "Replace nbdev v1 with v2 callouts."
148 | if cell.get('source') and cell.get('cell_type') == 'markdown':
149 | cell['source'] = _shortcuts(cell['source'])
150 |
151 | # %% ../nbs/api/16_migrate.ipynb 50
152 | def migrate_nb(path, overwrite=True):
153 | "Migrate Notebooks from nbdev v1 and fastpages."
154 | nbp = NBProcessor(path, procs=[FrontmatterProc, MigrateProc, _repl_v1shortcuts, _repl_v1dir], rm_directives=False)
155 | nbp.process()
156 | if overwrite: write_nb(nbp.nb, path)
157 | return nbp.nb
158 |
159 | # %% ../nbs/api/16_migrate.ipynb 51
160 | def migrate_md(path, overwrite=True):
161 | "Migrate Markdown Files from fastpages."
162 | txt = fp_md_fm(path)
163 | if overwrite: path.write_text(txt)
164 | return txt
165 |
166 | # %% ../nbs/api/16_migrate.ipynb 52
167 | @call_parse
168 | def nbdev_migrate(
169 | path:str=None, # A path or glob containing notebooks and markdown files to migrate
170 | no_skip:bool=False, # Do not skip directories beginning with an underscore
171 | ):
172 | "Convert all markdown and notebook files in `path` from v1 to v2"
173 | _skip_re = None if no_skip else '^[_.]'
174 | if path is None: path = get_config().nbs_path
175 | for f in globtastic(path, file_re='(.ipynb$)|(.md$)', skip_folder_re=_skip_re, func=Path):
176 | try:
177 | if f.name.endswith('.ipynb'): migrate_nb(f)
178 | if f.name.endswith('.md'): migrate_md(f)
179 | except Exception as e: raise Exception(f'Error in migrating file: {f}') from e
180 |
--------------------------------------------------------------------------------
/nbs/api/06_sync.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "#|default_exp sync"
10 | ]
11 | },
12 | {
13 | "cell_type": "markdown",
14 | "metadata": {},
15 | "source": [
16 | "# sync\n",
17 | "\n",
18 | "> Propagate small changes in the library back to notebooks\n",
19 | "- order: 6"
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "The library is primarily developed in notebooks so any big changes should be made there. But sometimes, it's easier to fix small bugs or typos in the modules directly. `nbdev_update` is the function that will propagate those changes back to the corresponding notebooks. Note that you can't create new cells or reorder cells with that functionality, so your corrections should remain limited."
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "#|export\n",
36 | "from nbdev.imports import *\n",
37 | "from nbdev.config import *\n",
38 | "from nbdev.maker import *\n",
39 | "from nbdev.process import *\n",
40 | "from nbdev.process import _partition_cell\n",
41 | "from nbdev.export import *\n",
42 | "from nbdev.doclinks import _iter_py_cells\n",
43 | "\n",
44 | "from execnb.nbio import *\n",
45 | "from fastcore.script import *\n",
46 | "from fastcore.xtras import *\n",
47 | "\n",
48 | "import ast\n",
49 | "from importlib import import_module"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "metadata": {},
56 | "outputs": [],
57 | "source": [
58 | "#|hide\n",
59 | "from fastcore.test import *"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "metadata": {},
66 | "outputs": [],
67 | "source": [
68 | "#|export\n",
69 | "def absolute_import(name, fname, level):\n",
70 | " \"Unwarps a relative import in `name` according to `fname`\"\n",
71 | " if not level: return name\n",
72 | " mods = fname.split(os.path.sep)\n",
73 | " if not name: return '.'.join(mods)\n",
74 | " return '.'.join(mods[:len(mods)-level+1]) + f\".{name}\""
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "test_eq(absolute_import('xyz', 'nbdev', 0), 'xyz')\n",
84 | "test_eq(absolute_import('', 'nbdev', 1), 'nbdev')\n",
85 | "test_eq(absolute_import(None, 'nbdev', 1), 'nbdev')\n",
86 | "test_eq(absolute_import('core', 'nbdev', 1), 'nbdev.core')\n",
87 | "test_eq(absolute_import('core', 'nbdev/vision', 2), 'nbdev.core') # from ..core import *\n",
88 | "test_eq(absolute_import('transform', 'nbdev/vision', 1), 'nbdev.vision.transform') # from .transform import *\n",
89 | "test_eq(absolute_import('notebook.core', 'nbdev/data', 2), 'nbdev.notebook.core') # from ..notebook.core import *"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "metadata": {},
96 | "outputs": [],
97 | "source": [
98 | "#|export\n",
99 | "@functools.lru_cache(maxsize=None)\n",
100 | "def _mod_files():\n",
101 | " midx = import_module(f'{get_config().lib_path.name}._modidx')\n",
102 | " return L(files for mod in midx.d['syms'].values() for _,files in mod.values()).unique()"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": null,
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "#|export\n",
112 | "_re_import = re.compile(\"from\\s+\\S+\\s+import\\s+\\S\")"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "metadata": {},
119 | "outputs": [],
120 | "source": [
121 | "#|hide\n",
122 | "assert _re_import.match('from foo import bar')\n",
123 | "assert not _re_import.match('#from foo import bar')"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "metadata": {},
130 | "outputs": [],
131 | "source": [
132 | "#|export\n",
133 | "def _to_absolute(code, py_path, lib_dir):\n",
134 | " if not _re_import.search(code): return code\n",
135 | " res = update_import(code, ast.parse(code).body, str(py_path.relative_to(lib_dir).parent), absolute_import)\n",
136 | " return ''.join(res) if res else code"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": null,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "#|export\n",
146 | "def _update_nb(nb_path, cells, lib_dir):\n",
147 | " \"Update notebook `nb_path` with contents from `cells`\"\n",
148 | " nbp = NBProcessor(nb_path, ExportModuleProc(), rm_directives=False)\n",
149 | " nbp.process()\n",
150 | " for cell in cells:\n",
151 | " assert cell.nb_path == nb_path\n",
152 | " nbcell = nbp.nb.cells[cell.idx]\n",
153 | " dirs,_ = _partition_cell(nbcell, 'python')\n",
154 | " nbcell.source = ''.join(dirs) + _to_absolute(cell.code, cell.py_path, lib_dir)\n",
155 | " write_nb(nbp.nb, nb_path)"
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": null,
161 | "metadata": {},
162 | "outputs": [],
163 | "source": [
164 | "#|export\n",
165 | "def _update_mod(py_path, lib_dir):\n",
166 | " \"Propagate changes from cells in module `py_path` to corresponding notebooks\"\n",
167 | " py_cells = L(_iter_py_cells(py_path)).filter(lambda o: o.nb != 'auto')\n",
168 | " for nb_path,cells in groupby(py_cells, 'nb_path').items(): _update_nb(nb_path, cells, lib_dir)"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": null,
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "#|hide\n",
178 | "assert min(_mod_files().map(lambda x: x.endswith('.py'))) is True"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": null,
184 | "metadata": {},
185 | "outputs": [],
186 | "source": [
187 | "#|export\n",
188 | "@call_parse\n",
189 | "def nbdev_update(fname:str=None): # A Python file name to update\n",
190 | " \"Propagate change in modules matching `fname` to notebooks that created them\"\n",
191 | " if fname and fname.endswith('.ipynb'): raise ValueError(\"`nbdev_update` operates on .py files. If you wish to convert notebooks instead, see `nbdev_export`.\")\n",
192 | " if os.environ.get('IN_TEST',0): return\n",
193 | " cfg = get_config()\n",
194 | " fname = Path(fname or cfg.lib_path)\n",
195 | " lib_dir = cfg.lib_path.parent\n",
196 | " files = globtastic(fname, file_glob='*.py', skip_folder_re='^[_.]').filter(lambda x: str(Path(x).absolute().relative_to(lib_dir) in _mod_files()))\n",
197 | " files.map(_update_mod, lib_dir=lib_dir)"
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "metadata": {},
204 | "outputs": [],
205 | "source": [
206 | "#|hide\n",
207 | "# nbdev_update(\"../nbdev/sync.py\")"
208 | ]
209 | },
210 | {
211 | "cell_type": "markdown",
212 | "metadata": {},
213 | "source": [
214 | "## Export -"
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": null,
220 | "metadata": {},
221 | "outputs": [],
222 | "source": [
223 | "#|hide\n",
224 | "#|eval: false\n",
225 | "from nbdev.doclinks import nbdev_export\n",
226 | "nbdev_export()"
227 | ]
228 | },
229 | {
230 | "cell_type": "code",
231 | "execution_count": null,
232 | "metadata": {},
233 | "outputs": [],
234 | "source": []
235 | }
236 | ],
237 | "metadata": {
238 | "kernelspec": {
239 | "display_name": "Python 3 (ipykernel)",
240 | "language": "python",
241 | "name": "python3"
242 | }
243 | },
244 | "nbformat": 4,
245 | "nbformat_minor": 4
246 | }
247 |
--------------------------------------------------------------------------------
/nbs/api/04_export.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "#|hide\n",
10 | "#|default_exp export"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "metadata": {},
16 | "source": [
17 | "# export\n",
18 | "> Exporting a notebook to a library\n",
19 | "- order: 4"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "metadata": {},
26 | "outputs": [],
27 | "source": [
28 | "#|export\n",
29 | "from nbdev.config import *\n",
30 | "from nbdev.maker import *\n",
31 | "from nbdev.imports import *\n",
32 | "from nbdev.process import *\n",
33 | "\n",
34 | "from fastcore.script import *\n",
35 | "from fastcore.basics import *\n",
36 | "from fastcore.imports import *\n",
37 | "\n",
38 | "from collections import defaultdict"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "#|hide\n",
48 | "from fastcore.test import *\n",
49 | "from pdb import set_trace\n",
50 | "from importlib import reload\n",
51 | "from fastcore import shutil\n",
52 | "from execnb.nbio import read_nb"
53 | ]
54 | },
55 | {
56 | "cell_type": "code",
57 | "execution_count": null,
58 | "metadata": {},
59 | "outputs": [],
60 | "source": [
61 | "#|export\n",
62 | "class ExportModuleProc:\n",
63 | " \"A processor which exports code to a module\"\n",
64 | " def begin(self): self.modules,self.in_all = defaultdict(L),defaultdict(L)\n",
65 | " def _default_exp_(self, cell, exp_to): self.default_exp = exp_to\n",
66 | " def _exporti_(self, cell, exp_to=None): self.modules[ifnone(exp_to, '#')].append(cell)\n",
67 | " def _export_(self, cell, exp_to=None):\n",
68 | " self._exporti_(cell, exp_to)\n",
69 | " self.in_all[ifnone(exp_to, '#')].append(cell)\n",
70 | " _exports_=_export_"
71 | ]
72 | },
73 | {
74 | "cell_type": "markdown",
75 | "metadata": {},
76 | "source": [
77 | "Specify `dest` where the module(s) will be exported to, and optionally a class to use to create the module (`ModuleMaker`, by default).\n",
78 | "\n",
79 | "Exported cells are stored in a `dict` called `modules`, where the keys are the modules exported to. Those without an explicit module are stored in the `'#'` key, which will be exported to `default_exp`."
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "everything_fn = '../../tests/01_everything.ipynb'\n",
89 | "\n",
90 | "exp = ExportModuleProc()\n",
91 | "proc = NBProcessor(everything_fn, exp)\n",
92 | "proc.process()\n",
93 | "test_eq(exp.default_exp, 'everything')\n",
94 | "assert 'print_function' in exp.modules['#'][0].source\n",
95 | "assert 'h_n' in exp.in_all['some.thing'][0].source"
96 | ]
97 | },
98 | {
99 | "cell_type": "code",
100 | "execution_count": null,
101 | "metadata": {},
102 | "outputs": [],
103 | "source": [
104 | "#|export\n",
105 | "def black_format(cell, # Cell to format\n",
106 | " force=False): # Turn black formatting on regardless of settings.ini\n",
107 | " \"Processor to format code with `black`\"\n",
108 | " try: cfg = get_config()\n",
109 | " except FileNotFoundError: return\n",
110 | " if (not cfg.black_formatting and not force) or cell.cell_type != 'code': return\n",
111 | " try: import black\n",
112 | " except: raise ImportError(\"You must install black: `pip install black` if you wish to use black formatting with nbdev\")\n",
113 | " else:\n",
114 | " _format_str = partial(black.format_str, mode = black.Mode())\n",
115 | " try: cell.source = _format_str(cell.source).strip()\n",
116 | " except: pass"
117 | ]
118 | },
119 | {
120 | "cell_type": "code",
121 | "execution_count": null,
122 | "metadata": {},
123 | "outputs": [],
124 | "source": [
125 | "_cell = read_nb('../../tests/black.ipynb')['cells'][0]\n",
126 | "black_format(_cell, force=True)\n",
127 | "test_eq(_cell.source, 'j = [1, 2, 3]')"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "metadata": {},
134 | "outputs": [],
135 | "source": [
136 | "#|export\n",
137 | "def nb_export(nbname, lib_path=None, procs=black_format, debug=False, mod_maker=ModuleMaker, name=None):\n",
138 | " \"Create module(s) from notebook\"\n",
139 | " if lib_path is None: lib_path = get_config().lib_path\n",
140 | " exp = ExportModuleProc()\n",
141 | " nb = NBProcessor(nbname, [exp]+L(procs), debug=debug)\n",
142 | " nb.process()\n",
143 | " for mod,cells in exp.modules.items():\n",
144 | " all_cells = exp.in_all[mod]\n",
145 | " nm = ifnone(name, getattr(exp, 'default_exp', None) if mod=='#' else mod)\n",
146 | " if not nm:\n",
147 | " warn(f\"Notebook '{nbname}' uses `#|export` without `#|default_exp` cell.\\n\"\n",
148 | " \"Note nbdev2 no longer supports nbdev1 syntax. Run `nbdev_migrate` to upgrade.\\n\"\n",
149 | " \"See https://nbdev.fast.ai/getting_started.html for more information.\")\n",
150 | " return\n",
151 | " mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#')\n",
152 | " mm.make(cells, all_cells, lib_path=lib_path)"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "metadata": {},
158 | "source": [
159 | "Let's check we can import a test file:"
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": null,
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "#|eval: false\n",
169 | "shutil.rmtree('tmp', ignore_errors=True)\n",
170 | "nb_export('../../tests/00_some.thing.ipynb', 'tmp')\n",
171 | "\n",
172 | "g = exec_new('import tmp.some.thing')\n",
173 | "test_eq(g['tmp'].some.thing.__all__, ['a'])\n",
174 | "test_eq(g['tmp'].some.thing.a, 1)"
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "We'll also check that our 'everything' file exports correctly:"
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "metadata": {},
188 | "outputs": [],
189 | "source": [
190 | "#|eval: false\n",
191 | "nb_export(everything_fn, 'tmp')\n",
192 | "\n",
193 | "g = exec_new('import tmp.everything; from tmp.everything import *')\n",
194 | "_alls = L(\"a b d e m n o p q\".split())\n",
195 | "for s in _alls.map(\"{}_y\"): assert s in g, s\n",
196 | "for s in \"c_y_nall _f_y_nall g_n h_n i_n j_n k_n l_n\".split(): assert s not in g, s\n",
197 | "for s in _alls.map(\"{}_y\") + [\"c_y_nall\", \"_f_y_nall\"]: assert hasattr(g['tmp'].everything,s), s"
198 | ]
199 | },
200 | {
201 | "cell_type": "markdown",
202 | "metadata": {},
203 | "source": [
204 | "That notebook should also export one extra function to `tmp.some.thing`:"
205 | ]
206 | },
207 | {
208 | "cell_type": "code",
209 | "execution_count": null,
210 | "metadata": {},
211 | "outputs": [],
212 | "source": [
213 | "#|eval: false\n",
214 | "del(sys.modules['tmp.some.thing']) # remove from module cache\n",
215 | "g = exec_new('import tmp.some.thing')\n",
216 | "test_eq(g['tmp'].some.thing.__all__, ['a','h_n'])\n",
217 | "test_eq(g['tmp'].some.thing.h_n(), None)"
218 | ]
219 | },
220 | {
221 | "cell_type": "markdown",
222 | "metadata": {},
223 | "source": [
224 | "## Export -"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "metadata": {},
231 | "outputs": [],
232 | "source": [
233 | "#|eval: false\n",
234 | "Path('../nbdev/export.py').unlink(missing_ok=True)\n",
235 | "nb_export('04a_export.ipynb')\n",
236 | "\n",
237 | "g = exec_new('import nbdev.export')\n",
238 | "assert hasattr(g['nbdev'].export, 'nb_export')"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "metadata": {},
245 | "outputs": [],
246 | "source": []
247 | }
248 | ],
249 | "metadata": {
250 | "kernelspec": {
251 | "display_name": "Python 3 (ipykernel)",
252 | "language": "python",
253 | "name": "python3"
254 | }
255 | },
256 | "nbformat": 4,
257 | "nbformat_minor": 4
258 | }
259 |
--------------------------------------------------------------------------------
/nbs/api/09_frontmatter.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "f14f1df0-7110-452f-beff-430a8f0aec73",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "#|default_exp frontmatter"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "f6b8b4a6",
16 | "metadata": {},
17 | "source": [
18 | "# frontmatter\n",
19 | "\n",
20 | "> A YAML and formatted-markdown frontmatter processor\n",
21 | "- order: 9"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "id": "2398f5ef-06d3-4890-8a54-7cf4f81f3894",
28 | "metadata": {},
29 | "outputs": [],
30 | "source": [
31 | "#|export\n",
32 | "from nbdev.imports import *\n",
33 | "from nbdev.process import *\n",
34 | "from nbdev.doclinks import _nbpath2html\n",
35 | "\n",
36 | "from execnb.nbio import *\n",
37 | "from fastcore.imports import *\n",
38 | "import yaml"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "id": "ce00cb74",
45 | "metadata": {},
46 | "outputs": [],
47 | "source": [
48 | "#|hide\n",
49 | "from fastcore.test import *"
50 | ]
51 | },
52 | {
53 | "cell_type": "code",
54 | "execution_count": null,
55 | "id": "9d36f27f",
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "#|hide\n",
60 | "_test_file = '../../tests/docs_test.ipynb'"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "id": "6d13ecdb",
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "#|export\n",
71 | "_RE_FM_BASE=r'''^---\\s*\n",
72 | "(.*?\\S+.*?)\n",
73 | "---\\s*'''\n",
74 | "\n",
75 | "_re_fm_nb = re.compile(_RE_FM_BASE+'$', flags=re.DOTALL)\n",
76 | "_re_fm_md = re.compile(_RE_FM_BASE, flags=re.DOTALL)\n",
77 | "\n",
78 | "def _fm2dict(s:str, nb=True):\n",
79 | " \"Load YAML frontmatter into a `dict`\"\n",
80 | " re_fm = _re_fm_nb if nb else _re_fm_md\n",
81 | " match = re_fm.search(s.strip())\n",
82 | " return yaml.safe_load(match.group(1)) if match else {}\n",
83 | "\n",
84 | "def _md2dict(s:str):\n",
85 | " \"Convert H1 formatted markdown cell to frontmatter dict\"\n",
86 | " if '#' not in s: return {}\n",
87 | " m = re.search(r'^#\\s+(\\S.*?)\\s*$', s, flags=re.MULTILINE)\n",
88 | " if not m: return {}\n",
89 | " res = {'title': m.group(1)}\n",
90 | " m = re.search(r'^>\\s+(\\S.*?)\\s*$', s, flags=re.MULTILINE)\n",
91 | " if m: res['description'] = m.group(1)\n",
92 | " r = re.findall(r'^-\\s+(\\S.*:.*\\S)\\s*$', s, flags=re.MULTILINE)\n",
93 | " if r:\n",
94 | " try: res.update(yaml.safe_load('\\n'.join(r)))\n",
95 | " except Exception as e: warn(f'Failed to create YAML dict for:\\n{r}\\n\\n{e}\\n')\n",
96 | " return res"
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "execution_count": null,
102 | "id": "1b5d9d32",
103 | "metadata": {},
104 | "outputs": [],
105 | "source": [
106 | "#|export\n",
107 | "def _dict2fm(d): return f'---\\n{yaml.dump(d)}\\n---\\n\\n'\n",
108 | "def _insertfm(nb, fm): nb.cells.insert(0, mk_cell(_dict2fm(fm), 'raw'))\n",
109 | "\n",
110 | "class FrontmatterProc(Processor):\n",
111 | " \"A YAML and formatted-markdown frontmatter processor\"\n",
112 | " def begin(self): self.fm = getattr(self.nb, 'frontmatter_', {})\n",
113 | "\n",
114 | " def _update(self, f, cell):\n",
115 | " s = cell.get('source')\n",
116 | " if not s: return\n",
117 | " d = f(s)\n",
118 | " if not d: return\n",
119 | " self.fm.update(d)\n",
120 | " cell.source = None\n",
121 | "\n",
122 | " def cell(self, cell):\n",
123 | " if cell.cell_type=='raw': self._update(_fm2dict, cell)\n",
124 | " elif cell.cell_type=='markdown' and 'title' not in self.fm: self._update(_md2dict, cell)\n",
125 | "\n",
126 | " def end(self):\n",
127 | " self.nb.frontmatter_ = self.fm\n",
128 | " if not self.fm: return\n",
129 | " self.fm.update({'output-file': _nbpath2html(Path(self.nb.path_)).name})\n",
130 | " _insertfm(self.nb, self.fm)"
131 | ]
132 | },
133 | {
134 | "cell_type": "markdown",
135 | "id": "d1a5c7c1",
136 | "metadata": {},
137 | "source": [
138 | "YAML frontmatter can be added to notebooks in one of two ways:\n",
139 | "\n",
140 | "1. By adding a raw notebook cell with `---` as the first and last lines, and YAML between them, or\n",
141 | "2. A specially formatted markdown cell. The first line should be start with a single `#` (creating an H1 heading), and becomes the title. Then, optionally, a line beginning with `>` (creating a quote block), which becomes the description. Finally, zero or more lines beginning with `- ` (creating a list), each of which contains YAML. (If you already have \"title\" defined in frontmatter in a raw cell, then markdown cells will be ignored.)\n",
142 | "\n",
143 | "For instance, our test notebook contains the following markdown cell:\n",
144 | "\n",
145 | "```\n",
146 | "# a title\n",
147 | "> A description\n",
148 | "- key1: value1\n",
149 | "- key2: value2\n",
150 | "- categories: [c1, c2]\n",
151 | "```\n",
152 | "\n",
153 | "It also contains the following raw cell:\n",
154 | "\n",
155 | "```\n",
156 | "---\n",
157 | "execute:\n",
158 | " echo: false\n",
159 | "---\n",
160 | "```\n",
161 | "\n",
162 | "When we process with `FrontmatterProc`, these will both be removed, and a single raw cell will be added to the top, containing the combined YAML frontmatter:"
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "id": "3c0031d2",
169 | "metadata": {},
170 | "outputs": [
171 | {
172 | "name": "stdout",
173 | "output_type": "stream",
174 | "text": [
175 | "---\n",
176 | "categories:\n",
177 | "- c1\n",
178 | "- c2\n",
179 | "description: A description\n",
180 | "execute:\n",
181 | " echo: false\n",
182 | "key1: value1\n",
183 | "key2: value2\n",
184 | "output-file: docs_test.html\n",
185 | "title: a title\n",
186 | "\n",
187 | "---\n",
188 | "\n",
189 | "\n"
190 | ]
191 | }
192 | ],
193 | "source": [
194 | "nbp = NBProcessor(_test_file, procs=FrontmatterProc)\n",
195 | "nbp.process()\n",
196 | "print(nbp.nb.cells[0].source)"
197 | ]
198 | },
199 | {
200 | "cell_type": "markdown",
201 | "id": "d71f4a28",
202 | "metadata": {},
203 | "source": [
204 | "In addition, a `frontmatter_` attr will be added to the notebook, containing this information as a `dict`:"
205 | ]
206 | },
207 | {
208 | "cell_type": "code",
209 | "execution_count": null,
210 | "id": "1f592909",
211 | "metadata": {},
212 | "outputs": [
213 | {
214 | "data": {
215 | "text/plain": [
216 | "{'execute': {'echo': False},\n",
217 | " 'title': 'a title',\n",
218 | " 'description': 'A description',\n",
219 | " 'key1': 'value1',\n",
220 | " 'key2': 'value2',\n",
221 | " 'categories': ['c1', 'c2'],\n",
222 | " 'output-file': 'docs_test.html'}"
223 | ]
224 | },
225 | "execution_count": null,
226 | "metadata": {},
227 | "output_type": "execute_result"
228 | }
229 | ],
230 | "source": [
231 | "d = nbp.nb.frontmatter_\n",
232 | "d"
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": null,
238 | "id": "5e5becda",
239 | "metadata": {},
240 | "outputs": [],
241 | "source": [
242 | "#|hide\n",
243 | "test_eq(d['description'], 'A description')\n",
244 | "test_eq(d['categories'], ['c1','c2'])\n",
245 | "test_eq(d['output-file'], 'docs_test.html')"
246 | ]
247 | },
248 | {
249 | "cell_type": "markdown",
250 | "id": "4af909f4",
251 | "metadata": {},
252 | "source": [
253 | "## Export -"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": null,
259 | "id": "079a05ac",
260 | "metadata": {},
261 | "outputs": [],
262 | "source": [
263 | "#|hide\n",
264 | "import nbdev; nbdev.nbdev_export()"
265 | ]
266 | },
267 | {
268 | "cell_type": "code",
269 | "execution_count": null,
270 | "id": "d28956a7",
271 | "metadata": {},
272 | "outputs": [],
273 | "source": []
274 | }
275 | ],
276 | "metadata": {
277 | "kernelspec": {
278 | "display_name": "Python 3 (ipykernel)",
279 | "language": "python",
280 | "name": "python3"
281 | }
282 | },
283 | "nbformat": 4,
284 | "nbformat_minor": 5
285 | }
286 |
--------------------------------------------------------------------------------