├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── deploy-manual.yaml │ ├── deploy.yaml │ └── test.yaml ├── .gitignore ├── .pre-commit-hooks.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── TODO.md ├── nbdev ├── __init__.py ├── _modidx.py ├── clean.py ├── cli.py ├── config.py ├── doclinks.py ├── export.py ├── extract_attachments.py ├── frontmatter.py ├── imports.py ├── maker.py ├── merge.py ├── migrate.py ├── process.py ├── processors.py ├── qmd.py ├── quarto.py ├── release.py ├── serve.py ├── serve_drv.py ├── showdoc.py ├── sync.py └── test.py ├── nbs ├── .gitignore ├── .nojekyll ├── CNAME ├── _quarto.yml ├── api │ ├── 01_config.ipynb │ ├── 02_maker.ipynb │ ├── 03_process.ipynb │ ├── 04_export.ipynb │ ├── 05_doclinks.ipynb │ ├── 06_sync.ipynb │ ├── 07_merge.ipynb │ ├── 08_showdoc.ipynb │ ├── 09_frontmatter.ipynb │ ├── 10_processors.ipynb │ ├── 11_clean.ipynb │ ├── 12_test.ipynb │ ├── 13_cli.ipynb │ ├── 14_quarto.ipynb │ ├── 15_qmd.ipynb │ ├── 16_migrate.ipynb │ ├── 17_serve.ipynb │ ├── 18_release.ipynb │ ├── images │ │ ├── release.svg │ │ └── token.png │ └── index.qmd ├── blog │ ├── index.qmd │ └── posts │ │ ├── 2022-07-28-nbdev2 │ │ ├── index.qmd │ │ ├── lang_rank.png │ │ ├── logo_lyft.png │ │ ├── logo_netflix.png │ │ ├── logo_ob.png │ │ ├── logo_transform.png │ │ ├── nb_quarto.png │ │ └── patch.png │ │ ├── 2022-08-25-jupyter-git │ │ ├── friendly-conflict.png │ │ ├── index.qmd │ │ └── unreadable-notebook.png │ │ └── 2022-11-07-spaces │ │ ├── app.py │ │ ├── blog_cover.jpeg │ │ ├── create_space.png │ │ ├── final_app.png │ │ ├── gradio_local.png │ │ └── index.qmd ├── custom.yml ├── explanations │ ├── .nodoc │ ├── .notest │ ├── config.ipynb │ ├── directives.ipynb │ ├── docs.ipynb │ ├── docs.mermaid │ ├── docs.svg │ ├── images │ │ └── github-actions-pages.png │ ├── index.qmd │ └── why_nbdev.ipynb ├── favicon.png ├── getting_started.ipynb ├── images │ ├── amd.svg │ ├── bom.png │ ├── card.png │ ├── chris-lattner.png │ ├── circles.svg.py │ ├── david-berg.jpeg │ ├── divio-overview.webp │ ├── docs.svg │ ├── erik-gaasedelen.jpeg │ ├── favicon.png │ ├── fernando-pérez.jpeg │ ├── git.svg │ ├── hugo-bowne-anderson.jpeg │ ├── jupyter.svg │ ├── lyft.svg │ ├── netflix.svg │ ├── novetta.svg │ ├── outerbounds.svg │ ├── overstory.png │ ├── packaging.svg │ ├── roxanna-pourzand.jpeg │ ├── testing.svg │ ├── transform.svg │ └── vscode.svg ├── index.css ├── index.qmd.py ├── llms.txt ├── nbdev.yml ├── styles.css └── tutorials │ ├── .nodoc │ ├── .notest │ ├── best_practices.ipynb │ ├── blogging.ipynb │ ├── docs_only.ipynb │ ├── extensions.ipynb │ ├── git_friendly_jupyter.ipynb │ ├── images │ ├── github-actions-initial.png │ ├── github-actions-pages.png │ ├── github-create-new-repo.png │ ├── github-enable-pages.png │ ├── github-repo-empty.png │ ├── jupyter-blank-terminal.png │ ├── jupyter-friendly-conflict.png │ ├── jupyter-unreadable-notebook.png │ ├── jupyter-welcome.png │ ├── marie-curie-notebook.jpg │ └── nbdev-hello-world-site-initial.png │ ├── index.qmd │ ├── migrating.ipynb │ ├── modular_nbdev.ipynb │ ├── pre_commit.ipynb │ ├── qmd_intro.qmd │ ├── renderscript.qmd.py │ └── tutorial.ipynb ├── pyproject.toml ├── settings.ini ├── setup.py └── tests ├── .gitignore ├── .nodoc ├── .notest ├── 00_some.thing.ipynb ├── 01_everything.ipynb ├── 2020-01-14-test-markdown-post.md ├── 2020-02-20-test.ipynb ├── 2020-09-01-fastcore.ipynb ├── 2022-09-06-homeschooling.md ├── APL.ipynb ├── directives.ipynb ├── docs_test.ipynb ├── example.ipynb.broken ├── export_procs.ipynb ├── image.ipynb ├── metadata.ipynb ├── minimal.ipynb ├── notest ├── .notest └── nb_ignore.ipynb ├── old_directives.ipynb └── showdoc_test.ipynb /.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 | -------------------------------------------------------------------------------- /.github/workflows/deploy-manual.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages (Manual) 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 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | env: 7 | OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: answerdotai/workflows/quarto-ghp@master 13 | # with: {pre: 1} 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | env: 9 | OBJC_DISABLE_INITIALIZE_FORK_SAFETY: YES 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | fail-fast: true 15 | matrix: 16 | os: [ubuntu, macos] 17 | version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 18 | runs-on: ${{ matrix.os }}-latest 19 | steps: 20 | - uses: answerdotai/workflows/nbdev-ci@master 21 | with: 22 | version: ${{ matrix.version }} 23 | pre: 1 24 | - name: test docs build 25 | if: ${{ (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && matrix.version == '3.10' && matrix.os == 'ubuntu' }} 26 | run: | 27 | set -ux 28 | wget -q $(curl https://latest.fast.ai/pre/quarto-dev/quarto-cli/linux-amd64.deb) 29 | sudo dpkg -i quarto*.deb 30 | nbdev_docs 31 | if [ -f "_docs/index.html" ]; then 32 | echo "docs built successfully." 33 | else 34 | echo "index page not found in rendered docs." 35 | ls -la 36 | ls -la _docs 37 | exit 1 38 | fi 39 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-exclude * __pycache__ 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nbdev/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.4.3" 2 | 3 | from .doclinks import nbdev_export 4 | from .showdoc import show_doc 5 | 6 | -------------------------------------------------------------------------------- /nbdev/cli.py: -------------------------------------------------------------------------------- 1 | """CLI commands""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/13_cli.ipynb. 4 | 5 | # %% ../nbs/api/13_cli.ipynb 2 6 | from __future__ import annotations 7 | import warnings 8 | import time 9 | 10 | from .config import * 11 | from .process import * 12 | from .processors import * 13 | from .doclinks import * 14 | from .test import * 15 | from .clean import * 16 | from .quarto import nbdev_readme, nbdev_contributing, refresh_quarto_yml, fs_watchdog 17 | from .export import nb_export 18 | from .frontmatter import FrontmatterProc 19 | 20 | from fastcore.xtras import run 21 | from execnb.nbio import * 22 | from fastcore.meta import * 23 | from fastcore.utils import * 24 | from fastcore.script import * 25 | from fastcore.style import S 26 | from fastcore.shutil import rmtree,move 27 | 28 | from urllib.error import HTTPError 29 | from contextlib import redirect_stdout 30 | import os, tarfile, sys 31 | 32 | # %% auto 0 33 | __all__ = ['mapping', 'nbdev_filter', 'extract_tgz', 'nbdev_new', 'nbdev_update_license', 'nb_export_cli', 'watch_export', 34 | 'chelp'] 35 | 36 | # %% ../nbs/api/13_cli.ipynb 37 | @call_parse 38 | def nbdev_filter( 39 | nb_txt:str=None, # Notebook text (uses stdin if not provided) 40 | fname:str=None, # Notebook to read (uses `nb_txt` if not provided) 41 | printit:bool_arg=True, # Print to stdout? 42 | ): 43 | "A notebook filter for Quarto" 44 | os.environ["IN_TEST"] = "1" 45 | try: filt = globals()[get_config().get('exporter', 'FilterDefaults')]() 46 | except FileNotFoundError: filt = FilterDefaults() 47 | if fname: nb_txt = Path(fname).read_text() 48 | elif not nb_txt: nb_txt = sys.stdin.read() 49 | nb = dict2nb(loads(nb_txt)) 50 | if printit: 51 | with open(os.devnull, 'w', encoding="utf-8") as dn: 52 | with redirect_stdout(dn): filt(nb) 53 | else: filt(nb) 54 | res = nb2str(nb) 55 | del os.environ["IN_TEST"] 56 | if printit: print(res, flush=True) 57 | else: return res 58 | 59 | # %% ../nbs/api/13_cli.ipynb 60 | def extract_tgz(url, dest='.'): 61 | from fastcore.net import urlopen 62 | with urlopen(url) as u: tarfile.open(mode='r:gz', fileobj=u).extractall(dest) 63 | 64 | # %% ../nbs/api/13_cli.ipynb 65 | def _render_nb(fn, cfg): 66 | "Render templated values like `{{lib_name}}` in notebook at `fn` from `cfg`" 67 | txt = fn.read_text() 68 | txt = txt.replace('from your_lib.core', f'from {cfg.lib_path}.core') # for compatibility with old templates 69 | for k,v in cfg.d.items(): txt = txt.replace('{{'+k+'}}', v) 70 | fn.write_text(txt) 71 | 72 | # %% ../nbs/api/13_cli.ipynb 73 | def _update_repo_meta(cfg): 74 | "Enable gh pages and update the homepage and description in your GitHub repo." 75 | token=os.getenv('GITHUB_TOKEN') 76 | if token: 77 | from ghapi.core import GhApi 78 | api = GhApi(owner=cfg.user, repo=cfg.repo, token=token) 79 | try: api.repos.update(homepage=f'{cfg.doc_host}{cfg.doc_baseurl}', description=cfg.description) 80 | except HTTPError:print(f"Could not update the description & URL on the repo: {cfg.user}/{cfg.repo} using $GITHUB_TOKEN.\n" 81 | "Use a token with the correction permissions or perform these steps manually.") 82 | 83 | # %% ../nbs/api/13_cli.ipynb 84 | @call_parse 85 | @delegates(nbdev_create_config) 86 | def nbdev_new(**kwargs): 87 | "Create an nbdev project." 88 | from ghapi.core import GhApi 89 | nbdev_create_config.__wrapped__(**kwargs) 90 | cfg = get_config() 91 | _update_repo_meta(cfg) 92 | path = Path() 93 | 94 | _ORG_OR_USR,_REPOSITORY = 'answerdotai','nbdev-template' 95 | _TEMPLATE = f'{_ORG_OR_USR}/{_REPOSITORY}' 96 | template = kwargs.get('template', _TEMPLATE) 97 | try: org_or_usr, repo = template.split('/') 98 | except ValueError: org_or_usr, repo = _ORG_OR_USR, _REPOSITORY 99 | 100 | tag = kwargs.get('tag', None) 101 | if tag is None: 102 | with warnings.catch_warnings(): 103 | warnings.simplefilter('ignore', UserWarning) 104 | tag = GhApi(gh_host='https://api.github.com', authenticate=False).repos.get_latest_release(org_or_usr, repo).tag_name 105 | 106 | url = f"https://github.com/{org_or_usr}/{repo}/archive/{tag}.tar.gz" 107 | extract_tgz(url) 108 | tmpl_path = path/f'{repo}-{tag}' 109 | 110 | cfg.nbs_path.mkdir(exist_ok=True) 111 | nbexists = bool(first(cfg.nbs_path.glob('*.ipynb'))) 112 | _nbs_path_sufs = ('.ipynb','.css') 113 | for o in tmpl_path.ls(): 114 | p = cfg.nbs_path if o.suffix in _nbs_path_sufs else path 115 | if o.name == '_quarto.yml': continue 116 | if o.name == 'index.ipynb': _render_nb(o, cfg) 117 | if o.name == '00_core.ipynb' and not nbexists: move(o, p) 118 | elif not (path/o.name).exists(): move(o, p) 119 | rmtree(tmpl_path) 120 | 121 | refresh_quarto_yml() 122 | nbdev_export.__wrapped__() 123 | nbdev_readme.__wrapped__() 124 | nbdev_contributing.__wrapped__() 125 | 126 | # %% ../nbs/api/13_cli.ipynb 127 | mapping = { 128 | 'mit': 'mit', 129 | 'apache2': 'apache-2.0', 130 | 'gpl2': 'gpl-2.0', 131 | 'gpl3': 'gpl-3.0', 132 | 'bsd3': 'bsd-3-clause' 133 | } 134 | 135 | # %% ../nbs/api/13_cli.ipynb 136 | @call_parse 137 | def nbdev_update_license( 138 | to: str=None, # update license to 139 | ): 140 | "Allows you to update the license of your project." 141 | from ghapi.core import GhApi 142 | warnings.filterwarnings("ignore") 143 | avail_lic = GhApi().licenses.get_all_commonly_used().map(lambda x: x['key']) 144 | 145 | cfg = get_config() 146 | curr_lic = cfg['license'] 147 | 148 | mapped = mapping.get(to, None) 149 | 150 | if mapped not in avail_lic: raise ValueError(f"{to} is not an available license") 151 | body = GhApi().licenses.get(mapped)['body'] 152 | 153 | body = body.replace('[year], [fullname]', cfg['copyright']) 154 | body = body.replace('[year] [fullname]', cfg['copyright']) 155 | 156 | content = open("settings.ini", "r").read() 157 | content = re.sub(r"^(license\s*=\s*).*?$", r"\1 " + to, content, flags=re.MULTILINE) 158 | 159 | config = open("settings.ini", "w") 160 | config.write(content) 161 | 162 | lic = open('LICENSE', 'w') 163 | lic.write(body) 164 | print(f"License updated from {curr_lic} to {to}") 165 | 166 | # %% ../nbs/api/13_cli.ipynb 167 | @call_parse 168 | @delegates(nb_export, but=['procs', 'mod_maker']) 169 | def nb_export_cli(nbname, 170 | debug:store_true=False, # Debug flag 171 | **kwargs): 172 | "Export a single nbdev notebook to a python script." 173 | return nb_export(nbname=nbname, debug=debug, **kwargs) 174 | 175 | # %% ../nbs/api/13_cli.ipynb 176 | @call_parse 177 | def watch_export(nbs:str=None, # Nb directory to watch for changes 178 | lib:str=None, # Export directory to write py files to 179 | force:bool=False # Ignore nbdev config if in nbdev project 180 | ): 181 | '''Use `nb_export` on ipynb files in `nbs` directory on changes using nbdev config if available''' 182 | cfg = get_config() if is_nbdev() else None 183 | nbs = nbs or (cfg.nbs_path if cfg else '.') 184 | lib = lib or (cfg.lib_path if cfg else '.') 185 | if cfg and (nbs != cfg.nbs_path or lib != cfg.lib_path) and not force: 186 | raise ValueError("In nbdev project. Use --force to override config.") 187 | def _export(e,lib=lib): 188 | p = e.src_path 189 | if (not '.ipynb_checkpoints' in p and p.endswith('.ipynb') and not Path(p).name.startswith('.~')): 190 | if e.event_type == 'modified': run(f'nb_export --lib_path {lib} "{p}"') 191 | with fs_watchdog(_export, nbs): 192 | while True: time.sleep(1) 193 | 194 | # %% ../nbs/api/13_cli.ipynb 195 | @call_parse 196 | def chelp(): 197 | "Show help for all console scripts" 198 | from fastcore.xtras import console_help 199 | console_help('nbdev') 200 | -------------------------------------------------------------------------------- /nbdev/export.py: -------------------------------------------------------------------------------- 1 | """Exporting a notebook to a library""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/04_export.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['ExportModuleProc', 'black_format', 'scrub_magics', 'optional_procs', 'nb_export'] 7 | 8 | # %% ../nbs/api/04_export.ipynb 9 | from .config import * 10 | from .maker import * 11 | from .imports import * 12 | from .process import * 13 | 14 | from fastcore.script import * 15 | from fastcore.basics import * 16 | from fastcore.imports import * 17 | from fastcore.meta import * 18 | 19 | from collections import defaultdict 20 | 21 | # %% ../nbs/api/04_export.ipynb 22 | class ExportModuleProc: 23 | "A processor which exports code to a module" 24 | def begin(self): self.modules,self.in_all = defaultdict(L),defaultdict(L) 25 | def _default_exp_(self, cell, exp_to): self.default_exp = exp_to 26 | def _exporti_(self, cell, exp_to=None): self.modules[ifnone(exp_to, '#')].append(cell) 27 | def _export_(self, cell, exp_to=None): 28 | self._exporti_(cell, exp_to) 29 | self.in_all[ifnone(exp_to, '#')].append(cell) 30 | def __call__(self, cell): 31 | src = cell.source 32 | if not src: return 33 | if cell.cell_type=='markdown' and src.startswith('# '): self.modules['#'].append(cell) 34 | _exports_=_export_ 35 | 36 | # %% ../nbs/api/04_export.ipynb 37 | def black_format(cell, # Cell to format 38 | force=False): # Turn black formatting on regardless of settings.ini 39 | "Processor to format code with `black`" 40 | try: cfg = get_config() 41 | except FileNotFoundError: return 42 | if (not cfg.black_formatting and not force) or cell.cell_type != 'code': return 43 | try: import black 44 | except: raise ImportError("You must install black: `pip install black` if you wish to use black formatting with nbdev") 45 | else: 46 | _format_str = partial(black.format_str, mode = black.Mode()) 47 | try: cell.source = _format_str(cell.source).strip() 48 | except: pass 49 | 50 | # %% ../nbs/api/04_export.ipynb 51 | # includes the newline, because calling .strip() would affect all cells. 52 | _magics_pattern = re.compile(r'^\s*(%%|%).*\n?', re.MULTILINE) 53 | 54 | def scrub_magics(cell): # Cell to format 55 | "Processor to remove cell magics from exported code" 56 | try: cfg = get_config() 57 | except FileNotFoundError: return 58 | if cell.cell_type != 'code': return 59 | try: cell.source = _magics_pattern.sub('', cell.source) 60 | except: pass 61 | 62 | # %% ../nbs/api/04_export.ipynb 63 | import nbdev.export 64 | def optional_procs(): 65 | "An explicit list of processors that could be used by `nb_export`" 66 | return L([p for p in nbdev.export.__all__ 67 | if p not in ["nb_export", "nb_export_cli", "ExportModuleProc", "optional_procs"]]) 68 | 69 | # %% ../nbs/api/04_export.ipynb 70 | def nb_export(nbname:str, # Filename of notebook 71 | lib_path:str=None, # Path to destination library. If not in a nbdev project, defaults to current directory. 72 | procs=None, # Processors to use 73 | name:str=None, # Name of python script {name}.py to create. 74 | mod_maker=ModuleMaker, 75 | debug:bool=False, # Debug mode 76 | solo_nb:bool=False # Export single notebook outside of an nbdev project. 77 | ): 78 | "Create module(s) from notebook" 79 | if lib_path is None: lib_path = get_config().lib_path if is_nbdev() else '.' 80 | exp = ExportModuleProc() 81 | nb = NBProcessor(nbname, [exp]+L(procs), debug=debug) 82 | nb.process() 83 | for mod,cells in exp.modules.items(): 84 | if first(1 for o in cells if o.cell_type=='code'): 85 | all_cells = exp.in_all[mod] 86 | nm = ifnone(name, getattr(exp, 'default_exp', None) if mod=='#' else mod) 87 | if not nm: 88 | warn(f"Notebook '{nbname}' uses `#|export` without `#|default_exp` cell.\n" 89 | "Note nbdev2 no longer supports nbdev1 syntax. Run `nbdev_migrate` to upgrade.\n" 90 | "See https://nbdev.fast.ai/getting_started.html for more information.") 91 | return 92 | mm = mod_maker(dest=lib_path, name=nm, nb_path=nbname, is_new=bool(name) or mod=='#', solo_nb=solo_nb) 93 | mm.make(cells, all_cells, lib_path=lib_path) 94 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nbdev/frontmatter.py: -------------------------------------------------------------------------------- 1 | """A YAML and formatted-markdown frontmatter processor""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/09_frontmatter.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['FrontmatterProc'] 7 | 8 | # %% ../nbs/api/09_frontmatter.ipynb 9 | from .imports import * 10 | from .process import * 11 | from .doclinks import _nbpath2html 12 | 13 | from execnb.nbio import * 14 | from fastcore.imports import * 15 | import yaml 16 | 17 | # %% ../nbs/api/09_frontmatter.ipynb 18 | _RE_FM_BASE=r'''^---\s* 19 | (.*?\S+.*?) 20 | ---\s*''' 21 | 22 | _re_fm_nb = re.compile(_RE_FM_BASE+'$', flags=re.DOTALL) 23 | _re_fm_md = re.compile(_RE_FM_BASE, flags=re.DOTALL) 24 | 25 | def _fm2dict(s:str, nb=True): 26 | "Load YAML frontmatter into a `dict`" 27 | re_fm = _re_fm_nb if nb else _re_fm_md 28 | match = re_fm.search(s.strip()) 29 | return yaml.safe_load(match.group(1)) if match else {} 30 | 31 | def _md2dict(s:str): 32 | "Convert H1 formatted markdown cell to frontmatter dict" 33 | if '#' not in s: return {} 34 | m = re.search(r'^#\s+(\S.*?)\s*$', s, flags=re.MULTILINE) 35 | if not m: return {} 36 | res = {'title': m.group(1)} 37 | m = re.search(r'^>\s+(\S.*?)\s*$', s, flags=re.MULTILINE) 38 | if m: res['description'] = m.group(1) 39 | r = re.findall(r'^-\s+(\S.*:.*\S)\s*$', s, flags=re.MULTILINE) 40 | if r: 41 | try: res.update(yaml.safe_load('\n'.join(r))) 42 | except Exception as e: warn(f'Failed to create YAML dict for:\n{r}\n\n{e}\n') 43 | return res 44 | 45 | # %% ../nbs/api/09_frontmatter.ipynb 46 | def _dict2fm(d): return f'---\n{yaml.dump(d)}\n---\n\n' 47 | def _insertfm(nb, fm): nb.cells.insert(0, mk_cell(_dict2fm(fm), 'raw')) 48 | 49 | class FrontmatterProc(Processor): 50 | "A YAML and formatted-markdown frontmatter processor" 51 | def begin(self): self.fm = getattr(self.nb, 'frontmatter_', {}) 52 | 53 | def _update(self, f, cell): 54 | s = cell.get('source') 55 | if not s: return 56 | d = f(s) 57 | if not d: return 58 | self.fm.update(d) 59 | cell.source = None 60 | 61 | def cell(self, cell): 62 | if cell.cell_type=='raw': self._update(_fm2dict, cell) 63 | elif cell.cell_type=='markdown' and 'title' not in self.fm: self._update(_md2dict, cell) 64 | 65 | def end(self): 66 | self.nb.frontmatter_ = self.fm 67 | if not self.fm: return 68 | if not hasattr(self.nb, 'path_'): 69 | raise AttributeError('Notebook missing `path_` attribute.\n\nPlease remove any nbdev-related notebook filters ' 70 | 'from your _quarto.yml file (e.g. `ipynb-filter: [nbdev_filter]`), since they are no ' 71 | 'longer supported as of nbdev v2.3. See the v2.3 launch post for more information: ' 72 | 'https://forums.fast.ai/t/upcoming-changes-in-v2-3-edit-now-released/98905.') 73 | self.fm.update({'output-file': _nbpath2html(Path(self.nb.path_)).name}) 74 | _insertfm(self.nb, self.fm) 75 | -------------------------------------------------------------------------------- /nbdev/imports.py: -------------------------------------------------------------------------------- 1 | from fastcore.imports import * 2 | from fastcore.foundation import * 3 | from fastcore.utils import * 4 | 5 | -------------------------------------------------------------------------------- /nbdev/merge.py: -------------------------------------------------------------------------------- 1 | """Fix merge conflicts in jupyter notebooks""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/07_merge.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['conf_re', 'unpatch', 'nbdev_fix', 'nbdev_merge'] 7 | 8 | # %% ../nbs/api/07_merge.ipynb 9 | from .imports import * 10 | from .config import * 11 | from .export import * 12 | from .sync import * 13 | 14 | from execnb.nbio import * 15 | from fastcore.script import * 16 | from fastcore import shutil 17 | 18 | import subprocess 19 | from difflib import SequenceMatcher 20 | 21 | # %% ../nbs/api/07_merge.ipynb 22 | _BEG,_MID,_END = '<'*7,'='*7,'>'*7 23 | conf_re = re.compile(rf'^{_BEG}\s+(\S+)\n(.*?)^{_MID}\n(.*?)^{_END}\s+([\S ]+)\n', re.MULTILINE|re.DOTALL) 24 | 25 | def _unpatch_f(before, cb1, cb2, c, r): 26 | if cb1 is not None and cb1 != cb2: raise Exception(f'Branch mismatch: {cb1}/{cb2}') 27 | r.append(before) 28 | r.append(c) 29 | return cb2 30 | 31 | # %% ../nbs/api/07_merge.ipynb 32 | def unpatch(s:str): 33 | "Takes a string with conflict markers and returns the two original files, and their branch names" 34 | *main,last = conf_re.split(s) 35 | r1,r2,c1b,c2b = [],[],None,None 36 | for before,c1_branch,c1,c2,c2_branch in chunked(main, 5): 37 | c1b = _unpatch_f(before, c1b, c1_branch, c1, r1) 38 | c2b = _unpatch_f(before, c2b, c2_branch, c2, r2) 39 | return ''.join(r1+[last]), ''.join(r2+[last]), c1b, c2b 40 | 41 | # %% ../nbs/api/07_merge.ipynb 42 | def _make_md(code): return [dict(source=f'`{code}`', cell_type="markdown", metadata={})] 43 | def _make_conflict(a,b, branch1, branch2): 44 | return _make_md(f'{_BEG} {branch1}') + a+_make_md(_MID)+b + _make_md(f'{_END} {branch2}') 45 | 46 | def _merge_cells(a, b, brancha, branchb, theirs): 47 | matches = SequenceMatcher(None, a, b).get_matching_blocks() 48 | res,prev_sa,prev_sb,conflict = [],0,0,False 49 | for sa,sb,sz in matches: 50 | ca,cb = a[prev_sa:sa],b[prev_sb:sb] 51 | if ca or cb: 52 | res += _make_conflict(ca, cb, brancha, branchb) 53 | conflict = True 54 | if sz: res += b[sb:sb+sz] if theirs else a[sa:sa+sz] 55 | prev_sa,prev_sb = sa+sz,sb+sz 56 | return res,conflict 57 | 58 | # %% ../nbs/api/07_merge.ipynb 59 | @call_parse 60 | def nbdev_fix(nbname:str, # Notebook filename to fix 61 | outname:str=None, # Filename of output notebook (defaults to `nbname`) 62 | nobackup:bool_arg=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided 63 | theirs:bool=False, # Use their outputs and metadata instead of ours 64 | noprint:bool=False): # Do not print info about whether conflicts are found 65 | "Create working notebook from conflicted notebook `nbname`" 66 | nbname = Path(nbname) 67 | if not nobackup and not outname: shutil.copy(nbname, nbname.with_suffix('.ipynb.bak')) 68 | nbtxt = nbname.read_text(encoding='utf-8') 69 | a,b,branch1,branch2 = unpatch(nbtxt) 70 | ac,bc = dict2nb(loads(a)),dict2nb(loads(b)) 71 | dest = bc if theirs else ac 72 | cells,conflict = _merge_cells(ac.cells, bc.cells, branch1, branch2, theirs=theirs) 73 | dest.cells = cells 74 | write_nb(dest, ifnone(outname, nbname)) 75 | if not noprint: 76 | if conflict: print("One or more conflict remains in the notebook, please inspect manually.") 77 | else: print("Successfully merged conflicts!") 78 | return conflict 79 | 80 | # %% ../nbs/api/07_merge.ipynb 81 | def _git_branch_merge(): 82 | try: return only(v for k,v in os.environ.items() if k.startswith('GITHEAD')) 83 | except ValueError: return 84 | 85 | # %% ../nbs/api/07_merge.ipynb 86 | def _git_rebase_head(): 87 | for d in ('apply','merge'): 88 | d = Path(f'.git/rebase-{d}') 89 | if d.is_dir(): 90 | cmt = (d/'orig-head').read_text(encoding='utf-8') 91 | msg = run(f'git show-branch --no-name {cmt}') 92 | return f'{cmt[:7]} ({msg})' 93 | 94 | # %% ../nbs/api/07_merge.ipynb 95 | def _git_merge_file(base, ours, theirs): 96 | "`git merge-file` with expected labels depending on if a `merge` or `rebase` is in-progress" 97 | l_theirs = _git_rebase_head() or _git_branch_merge() or 'THEIRS' 98 | cmd = f"git merge-file -L HEAD -L BASE -L '{l_theirs}' {ours} {base} {theirs}" 99 | return subprocess.run(cmd, shell=True, capture_output=True, text=True) 100 | 101 | # %% ../nbs/api/07_merge.ipynb 102 | @call_parse 103 | def nbdev_merge(base:str, ours:str, theirs:str, path:str): 104 | "Git merge driver for notebooks" 105 | if not _git_merge_file(base, ours, theirs).returncode: return 106 | theirs = str2bool(os.environ.get('THEIRS', False)) 107 | return nbdev_fix.__wrapped__(ours, theirs=theirs) 108 | -------------------------------------------------------------------------------- /nbdev/migrate.py: -------------------------------------------------------------------------------- 1 | """Utilities for migrating to nbdev""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/16_migrate.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['MigrateProc', 'fp_md_fm', 'migrate_nb', 'migrate_md', 'nbdev_migrate'] 7 | 8 | # %% ../nbs/api/16_migrate.ipynb 9 | from .process import * 10 | from .frontmatter import * 11 | from .frontmatter import _fm2dict, _re_fm_md, _dict2fm, _insertfm 12 | from .processors import * 13 | from .config import get_config, read_nb 14 | from .sync import write_nb 15 | from .showdoc import show_doc 16 | from fastcore.all import * 17 | import shutil 18 | 19 | # %% ../nbs/api/16_migrate.ipynb 20 | def _cat_slug(fmdict): 21 | "Get the partial slug from the category front matter." 22 | slug = '/'.join(fmdict.get('categories', '')) 23 | return '/' + slug if slug else '' 24 | 25 | # %% ../nbs/api/16_migrate.ipynb 26 | def _file_slug(fname): 27 | "Get the partial slug from the filename." 28 | p = Path(fname) 29 | dt = '/'+p.name[:10].replace('-', '/')+'/' 30 | return dt + p.stem[11:] 31 | 32 | # %% ../nbs/api/16_migrate.ipynb 33 | def _replace_fm(d:dict, # dictionary you wish to conditionally change 34 | k:str, # key to check 35 | val:str,# value to check if d[k] == v 36 | repl_dict:dict #dictionary that will be used as a replacement 37 | ): 38 | "replace key `k` in dict `d` if d[k] == val with `repl_dict`" 39 | if str(d.get(k, '')).lower().strip() == str(val.lower()).strip(): 40 | d.pop(k) 41 | d = merge(d, repl_dict) 42 | return d 43 | 44 | def _fp_fm(d): 45 | "create aliases for fastpages front matter to match Quarto front matter." 46 | d = _replace_fm(d, 'search_exclude', 'true', {'search':'false'}) 47 | d = _replace_fm(d, 'hide', 'true', {'draft': 'true'}) 48 | return d 49 | 50 | # %% ../nbs/api/16_migrate.ipynb 51 | def _fp_image(d): 52 | "Correct path of fastpages images to reference the local directory." 53 | prefix = 'images/copied_from_nb/' 54 | if d.get('image', '').startswith(prefix): d['image'] = d['image'].replace(prefix, '') 55 | return d 56 | 57 | # %% ../nbs/api/16_migrate.ipynb 58 | def _rm_quote(s): 59 | title = re.search('''"(.*?)"''', s) 60 | return title.group(1) if title else s 61 | 62 | def _is_jekyll_post(path): return bool(re.search(r'^\d{4}-\d{2}-\d{2}-', Path(path).name)) 63 | 64 | def _fp_convert(fm:dict, path:Path): 65 | "Make fastpages frontmatter Quarto complaint and add redirects." 66 | fs = _file_slug(path) 67 | if _is_jekyll_post(path): 68 | fm = compose(_fp_fm, _fp_image)(fm) 69 | if 'permalink' in fm: fm['aliases'] = [f"{fm['permalink'].strip()}"] 70 | else: fm['aliases'] = [f'{_cat_slug(fm) + fs}'] 71 | if not fm.get('date'): 72 | _,y,m,d,_ = fs.split('/') 73 | fm['date'] = f'{y}-{m}-{d}' 74 | 75 | if fm.get('summary') and not fm.get('description'): fm['description'] = fm['summary'] 76 | if fm.get('tags') and not fm.get('categories'): 77 | if isinstance(fm['tags'], str): fm['categories'] = fm['tags'].split() 78 | elif isinstance(fm['tags'], list): fm['categories'] = fm['tags'] 79 | for k in ['title', 'description']: 80 | if k in fm: fm[k] = _rm_quote(fm[k]) 81 | 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 82 | return fm 83 | 84 | # %% ../nbs/api/16_migrate.ipynb 85 | class MigrateProc(Processor): 86 | "Migrate fastpages front matter in notebooks to a raw cell." 87 | def begin(self): 88 | self.nb.frontmatter_ = _fp_convert(self.nb.frontmatter_, self.nb.path_) 89 | if getattr(first(self.nb.cells), 'cell_type', None) == 'raw': del self.nb.cells[0] 90 | _insertfm(self.nb, self.nb.frontmatter_) 91 | 92 | # %% ../nbs/api/16_migrate.ipynb 93 | def fp_md_fm(path): 94 | "Make fastpages front matter in markdown files quarto compliant." 95 | p = Path(path) 96 | md = p.read_text() 97 | fm = _fm2dict(md, nb=False) 98 | if fm: 99 | fm = _fp_convert(fm, path) 100 | return _re_fm_md.sub(_dict2fm(fm), md) 101 | else: return md 102 | 103 | # %% ../nbs/api/16_migrate.ipynb 104 | _alias = merge({k:'code-fold: true' for k in ['collapse', 'collapse_input', 'collapse_hide']}, 105 | {'collapse_show':'code-fold: show', 'hide_input': 'echo: false', 'hide': 'include: false', 'hide_output': 'output: false'}) 106 | def _subv1(s): return _alias.get(s, s) 107 | 108 | # %% ../nbs/api/16_migrate.ipynb 109 | def _re_v1(): 110 | d = ['default_exp', 'export', 'exports', 'exporti', 'hide', 'hide_input', 'collapse_show', 'collapse', 111 | 'collapse_hide', 'collapse_input', 'hide_output', 'default_cls_lvl'] 112 | d += L(get_config().tst_flags).filter() 113 | d += [s.replace('_', '-') for s in d] # allow for hyphenated version of old directives 114 | _tmp = '|'.join(list(set(d))) 115 | return re.compile(fr"^[ \f\v\t]*?(#)\s*({_tmp})(?!\S)", re.MULTILINE) 116 | 117 | def _repl_directives(code_str): 118 | def _fmt(x): return f"#| {_subv1(x[2].replace('-', '_').strip())}" 119 | return _re_v1().sub(_fmt, code_str) 120 | 121 | # %% ../nbs/api/16_migrate.ipynb 122 | def _repl_v1dir(cell): 123 | "Replace nbdev v1 with v2 directives." 124 | if cell.get('source') and cell.get('cell_type') == 'code': 125 | ss = cell['source'].splitlines() 126 | first_code = first_code_ln(ss, re_pattern=_re_v1()) 127 | if not first_code: first_code = len(ss) 128 | if not ss: pass 129 | else: cell['source'] = '\n'.join([_repl_directives(c) for c in ss[:first_code]] + ss[first_code:]) 130 | 131 | # %% ../nbs/api/16_migrate.ipynb 132 | _re_callout = re.compile(r'^>\s(Warning|Note|Important|Tip):(.*)', flags=re.MULTILINE) 133 | def _co(x): return ":::{.callout-"+x[1].lower()+"}\n\n" + f"{x[2].strip()}\n\n" + ":::" 134 | def _convert_callout(s): 135 | "Convert nbdev v1 to v2 callouts." 136 | return _re_callout.sub(_co, s) 137 | 138 | # %% ../nbs/api/16_migrate.ipynb 139 | _re_video = re.compile(r'^>\syoutube:(.*)', flags=re.MULTILINE) 140 | def _v(x): return "{{< " + f"video {x[1].strip()}" + " >}}" 141 | def _convert_video(s): 142 | "Replace nbdev v1 with v2 video embeds." 143 | return _re_video.sub(_v, s) 144 | 145 | # %% ../nbs/api/16_migrate.ipynb 146 | _shortcuts = compose(_convert_video, _convert_callout) 147 | 148 | def _repl_v1shortcuts(cell): 149 | "Replace nbdev v1 with v2 callouts." 150 | if cell.get('source') and cell.get('cell_type') == 'markdown': 151 | cell['source'] = _shortcuts(cell['source']) 152 | 153 | # %% ../nbs/api/16_migrate.ipynb 154 | def migrate_nb(path, overwrite=True): 155 | "Migrate Notebooks from nbdev v1 and fastpages." 156 | nbp = NBProcessor(path, procs=[FrontmatterProc, MigrateProc, _repl_v1shortcuts, _repl_v1dir], rm_directives=False) 157 | nbp.process() 158 | if overwrite: write_nb(nbp.nb, path) 159 | return nbp.nb 160 | 161 | # %% ../nbs/api/16_migrate.ipynb 162 | def migrate_md(path, overwrite=True): 163 | "Migrate Markdown Files from fastpages." 164 | txt = fp_md_fm(path) 165 | if overwrite: path.write_text(txt) 166 | return txt 167 | 168 | # %% ../nbs/api/16_migrate.ipynb 169 | @call_parse 170 | def nbdev_migrate( 171 | path:str=None, # A path or glob containing notebooks and markdown files to migrate 172 | no_skip:bool=False, # Do not skip directories beginning with an underscore 173 | ): 174 | "Convert all markdown and notebook files in `path` from v1 to v2" 175 | _skip_re = None if no_skip else '^[_.]' 176 | if path is None: path = get_config().nbs_path 177 | for f in globtastic(path, file_re='(.ipynb$)|(.md$)', skip_folder_re=_skip_re, func=Path): 178 | try: 179 | if f.name.endswith('.ipynb'): migrate_nb(f) 180 | if f.name.endswith('.md'): migrate_md(f) 181 | except Exception as e: raise Exception(f'Error in migrating file: {f}') from e 182 | -------------------------------------------------------------------------------- /nbdev/process.py: -------------------------------------------------------------------------------- 1 | """A notebook processor""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/03_process.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['langs', 'nb_lang', 'first_code_ln', 'extract_directives', 'opt_set', 'instantiate', 'NBProcessor', 'Processor'] 7 | 8 | # %% ../nbs/api/03_process.ipynb 9 | from .config import * 10 | from .maker import * 11 | from .imports import * 12 | 13 | from execnb.nbio import * 14 | from fastcore.script import * 15 | from fastcore.imports import * 16 | 17 | from collections import defaultdict 18 | 19 | # %% ../nbs/api/03_process.ipynb 20 | # from https://github.com/quarto-dev/quarto-cli/blob/main/src/resources/jupyter/notebook.py 21 | langs = defaultdict( 22 | lambda: '#', r = "#", python = "#", julia = "#", scala = "//", matlab = "%", csharp = "//", fsharp = "//", 23 | c = ["/*","*/"], css = ["/*","*/"], sas = ["*",";"], powershell = "#", bash = "#", sql = "--", mysql = "--", psql = "--", 24 | lua = "--", cpp = "//", cc = "//", stan = "#", octave = "#", fortran = "!", fortran95 = "!", awk = "#", gawk = "#", stata = "*", 25 | java = "//", groovy = "//", sed = "#", perl = "#", ruby = "#", tikz = "%", javascript = "//", js = "//", d3 = "//", node = "//", 26 | sass = "//", coffee = "#", go = "//", asy = "//", haskell = "--", dot = "//", apl = "⍝") 27 | 28 | # %% ../nbs/api/03_process.ipynb 29 | def nb_lang(nb): return nested_attr(nb, 'metadata.kernelspec.language', 'python') 30 | 31 | # %% ../nbs/api/03_process.ipynb 32 | def _dir_pre(lang=None): return fr"\s*{langs[lang]}\s*\|" 33 | def _quarto_re(lang=None): return re.compile(_dir_pre(lang) + r'\s*[\w|-]+\s*:') 34 | 35 | # %% ../nbs/api/03_process.ipynb 36 | def _directive(s, lang='python'): 37 | s = re.sub('^'+_dir_pre(lang), f"{langs[lang]}|", s) 38 | if s.strip().endswith(':'): s = s.replace(':', '') # You can append colon at the end to be Quarto compliant. Ex: #|hide: 39 | if ':' in s: s = s.replace(':', ': ') 40 | s = (s.strip()[2:]).strip().split() 41 | if not s: return None 42 | direc,*args = s 43 | return direc,args 44 | 45 | # %% ../nbs/api/03_process.ipynb 46 | def _norm_quarto(s, lang='python'): 47 | "normalize quarto directives so they have a space after the colon" 48 | m = _quarto_re(lang).match(s) 49 | return m.group(0) + ' ' + _quarto_re(lang).sub('', s).lstrip() if m else s 50 | 51 | # %% ../nbs/api/03_process.ipynb 52 | _cell_mgc = re.compile(r"^\s*%%\w+") 53 | 54 | def first_code_ln(code_list, re_pattern=None, lang='python'): 55 | "get first line number where code occurs, where `code_list` is a list of code" 56 | if re_pattern is None: re_pattern = _dir_pre(lang) 57 | 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)) 58 | 59 | # %% ../nbs/api/03_process.ipynb 60 | def _partition_cell(cell, lang): 61 | if not cell.source: return [],[] 62 | lines = cell.source.splitlines(True) 63 | first_code = first_code_ln(lines, lang=lang) 64 | return lines[:first_code],lines[first_code:] 65 | 66 | # %% ../nbs/api/03_process.ipynb 67 | def extract_directives(cell, remove=True, lang='python'): 68 | "Take leading comment directives from lines of code in `ss`, remove `#|`, and split" 69 | dirs,code = _partition_cell(cell, lang) 70 | if not dirs: return {} 71 | if remove: 72 | # Leave Quarto directives and cell magic in place for later processing 73 | cell['source'] = ''.join([_norm_quarto(o, lang) for o in dirs if _quarto_re(lang).match(o) or _cell_mgc.match(o)] + code) 74 | return dict(L(_directive(s, lang) for s in dirs).filter()) 75 | 76 | # %% ../nbs/api/03_process.ipynb 77 | def opt_set(var, newval): 78 | "newval if newval else var" 79 | return newval if newval else var 80 | 81 | # %% ../nbs/api/03_process.ipynb 82 | def instantiate(x, **kwargs): 83 | "Instantiate `x` if it's a type" 84 | return x(**kwargs) if isinstance(x,type) else x 85 | 86 | def _mk_procs(procs, nb): return L(procs).map(instantiate, nb=nb) 87 | 88 | # %% ../nbs/api/03_process.ipynb 89 | def _is_direc(f): return getattr(f, '__name__', '-')[-1]=='_' 90 | 91 | # %% ../nbs/api/03_process.ipynb 92 | class NBProcessor: 93 | "Process cells and nbdev comments in a notebook" 94 | def __init__(self, path=None, procs=None, nb=None, debug=False, rm_directives=True, process=False): 95 | self.nb = read_nb(path) if nb is None else nb 96 | self.lang = nb_lang(self.nb) 97 | for cell in self.nb.cells: cell.directives_ = extract_directives(cell, remove=rm_directives, lang=self.lang) 98 | self.procs = _mk_procs(procs, nb=self.nb) 99 | self.debug,self.rm_directives = debug,rm_directives 100 | if process: self.process() 101 | 102 | def _process_cell(self, proc, cell): 103 | if not hasattr(cell,'source'): return 104 | if cell.cell_type=='code' and cell.directives_: 105 | # Option 1: `proc` is directive name with `_` suffix 106 | f = getattr(proc, '__name__', '-').rstrip('_') 107 | if f in cell.directives_: self._process_comment(proc, cell, f) 108 | 109 | # Option 2: `proc` contains a method named `_{directive}_` 110 | for cmd in cell.directives_: 111 | f = getattr(proc, f'_{cmd}_', None) 112 | if f: self._process_comment(f, cell, cmd) 113 | if callable(proc) and not _is_direc(proc): cell = opt_set(cell, proc(cell)) 114 | 115 | def _process_comment(self, proc, cell, cmd): 116 | args = cell.directives_[cmd] 117 | if self.debug: print(cmd, args, proc) 118 | return proc(cell, *args) 119 | 120 | def _proc(self, proc): 121 | if hasattr(proc,'begin'): proc.begin() 122 | for cell in self.nb.cells: self._process_cell(proc, cell) 123 | if hasattr(proc,'end'): proc.end() 124 | self.nb.cells = [c for c in self.nb.cells if c and getattr(c,'source',None) is not None] 125 | for i,cell in enumerate(self.nb.cells): cell.idx_ = i 126 | 127 | def process(self): 128 | "Process all cells with all processors" 129 | for proc in self.procs: self._proc(proc) 130 | 131 | # %% ../nbs/api/03_process.ipynb 132 | class Processor: 133 | "Base class for processors" 134 | def __init__(self, nb): self.nb = nb 135 | def cell(self, cell): pass 136 | def __call__(self, cell): return self.cell(cell) 137 | -------------------------------------------------------------------------------- /nbdev/qmd.py: -------------------------------------------------------------------------------- 1 | """Basic qmd generation helpers (experimental)""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/15_qmd.ipynb. 4 | 5 | # %% ../nbs/api/15_qmd.ipynb 2 6 | from __future__ import annotations 7 | import sys,os,inspect 8 | 9 | from fastcore.utils import * 10 | from fastcore.meta import delegates 11 | 12 | # %% auto 0 13 | __all__ = ['meta', 'div', 'img', 'btn', 'tbl_row', 'tbl_sep'] 14 | 15 | # %% ../nbs/api/15_qmd.ipynb 16 | def meta(md, # Markdown to add meta to 17 | classes=None, # List of CSS classes to add 18 | style=None, # Dict of CSS styles to add 19 | **kwargs): # Additional attributes to add to meta 20 | "A metadata section for qmd div in `{}`" 21 | if style: kwargs['style'] = "; ".join(f'{k}: {v}' for k,v in style.items()) 22 | props = ' '.join(f'{k}="{v}"' for k,v in kwargs.items()) 23 | classes = ' '.join('.'+c for c in L(classes)) 24 | meta = [] 25 | if classes: meta.append(classes) 26 | if props: meta.append(props) 27 | meta = ' '.join(meta) 28 | return md + ("{" + meta + "}" if meta else "") 29 | 30 | # %% ../nbs/api/15_qmd.ipynb 31 | def div(txt, # Markdown to add meta to 32 | classes=None, # List of CSS classes to add 33 | style=None, # Dict of CSS styles to add 34 | **kwargs): 35 | "A qmd div with optional metadata section" 36 | return meta("::: ", classes=classes, style=style, **kwargs) + f"\n\n{txt}\n\n:::\n\n" 37 | 38 | # %% ../nbs/api/15_qmd.ipynb 39 | def img(fname, # Image to link to 40 | classes=None, # List of CSS classes to add 41 | style=None, # Dict of CSS styles to add 42 | height=None, # Height attribute 43 | relative=None, # Tuple of (position,px) 44 | link=False, # Hyperlink to this image 45 | **kwargs): 46 | "A qmd image" 47 | kwargs,style = kwargs or {}, style or {} 48 | if height: kwargs["height"]= f"{height}px" 49 | if relative: 50 | pos,px = relative 51 | style["position"] = "relative" 52 | style[pos] = f"{px}px" 53 | res = meta(f'![]({fname})', classes=classes, style=style, **kwargs) 54 | return f'[{res}]({fname})' if link else res 55 | 56 | # %% ../nbs/api/15_qmd.ipynb 57 | def btn(txt, # Button text 58 | link, # Button link URL 59 | classes=None, # List of CSS classes to add 60 | style=None, # Dict of CSS styles to add 61 | **kwargs): 62 | "A qmd button" 63 | return meta(f'[{txt}]({link})', classes=classes, style=style, role="button") 64 | 65 | # %% ../nbs/api/15_qmd.ipynb 66 | def tbl_row(cols:list, # Auto-stringified columns to show in the row 67 | ): 68 | "Create a markdown table row from `cols`" 69 | return '|' + '|'.join(str(c or '') for c in cols) + '|' 70 | 71 | # %% ../nbs/api/15_qmd.ipynb 72 | def tbl_sep(sizes:int|list=3 # List of column sizes, or single `int` if all sizes the same 73 | ): 74 | "Create a markdown table separator with relative column size `sizes`" 75 | if isinstance(sizes,int): sizes = [3]*sizes 76 | return tbl_row('-'*s for s in sizes) 77 | 78 | # %% ../nbs/api/15_qmd.ipynb 79 | def _install_nbdev(): 80 | return div('''#### pip 81 | 82 | ```sh 83 | pip install -U nbdev 84 | ``` 85 | 86 | #### conda 87 | 88 | ```sh 89 | conda install -c fastai nbdev 90 | ``` 91 | ''', ['panel-tabset']) 92 | -------------------------------------------------------------------------------- /nbdev/serve.py: -------------------------------------------------------------------------------- 1 | """A parallel ipynb processor (experimental)""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/17_serve.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['proc_nbs'] 7 | 8 | # %% ../nbs/api/17_serve.ipynb 9 | import ast,subprocess,threading,sys 10 | from shutil import rmtree,copy2 11 | 12 | from fastcore.utils import * 13 | from fastcore.parallel import parallel 14 | from fastcore.script import call_parse 15 | from fastcore.meta import delegates 16 | 17 | from .config import get_config 18 | from .doclinks import nbglob_cli,nbglob 19 | from .processors import FilterDefaults 20 | import nbdev.serve_drv 21 | 22 | # %% ../nbs/api/17_serve.ipynb 23 | def _is_qpy(path:Path): 24 | "Is `path` a py script starting with frontmatter?" 25 | path = Path(path) 26 | if not path.suffix=='.py': return 27 | p = ast.parse(path.read_text(encoding='utf-8')) 28 | # try: p = ast.parse(path.read_text(encoding='utf-8')) 29 | # except: return 30 | if not p.body: return 31 | a = p.body[0] 32 | if isinstance(a, ast.Expr) and isinstance(a.value, ast.Constant): 33 | v = a.value.value.strip() 34 | vl = v.splitlines() 35 | if vl[0]=='---' and vl[-1]=='---': return '\n'.join(vl[1:-1]) 36 | 37 | # %% ../nbs/api/17_serve.ipynb 38 | def _proc_file(s, cache, path, mtime=None): 39 | skips = ('_proc', '_docs', '_site', 'settings.ini') 40 | if not s.is_file() or any(o[0]=='.' or o in skips for o in s.parts): return 41 | d = cache/s.relative_to(path) 42 | if s.suffix=='.py': d = d.with_suffix('') 43 | if d.exists(): 44 | dtime = d.stat().st_mtime 45 | if mtime: dtime = max(dtime, mtime) 46 | if s.stat().st_mtime<=dtime: return 47 | 48 | d.parent.mkdir(parents=True, exist_ok=True) 49 | if s.suffix=='.ipynb': return s,d,FilterDefaults 50 | md = _is_qpy(s) 51 | if md is not None: return s,d,md.strip() 52 | else: copy2(s,d) 53 | 54 | # %% ../nbs/api/17_serve.ipynb 55 | @delegates(nbglob_cli) 56 | def proc_nbs( 57 | path:str='', # Path to notebooks 58 | n_workers:int=defaults.cpus, # Number of workers 59 | force:bool=False, # Ignore cache and build all 60 | file_glob:str='', # Only include files matching glob 61 | file_re:str='', # Only include files matching glob 62 | **kwargs): 63 | "Process notebooks in `path` for docs rendering" 64 | cfg = get_config() 65 | cache = cfg.config_path/'_proc' 66 | path = Path(path or cfg.nbs_path) 67 | files = nbglob(path, func=Path, file_glob='', file_re='', **kwargs) 68 | if (path/'_quarto.yml').exists(): files.append(path/'_quarto.yml') 69 | if (path/'_brand.yml').exists(): files.append(path/'_brand.yml') 70 | if (path/'_extensions').exists(): files.extend(nbglob(path/'_extensions', func=Path, file_glob='', file_re='', skip_file_re='^[.]')) 71 | 72 | # If settings.ini or filter script newer than cache folder modified, delete cache 73 | chk_mtime = max(cfg.config_file.stat().st_mtime, Path(__file__).stat().st_mtime) 74 | cache.mkdir(parents=True, exist_ok=True) 75 | cache_mtime = cache.stat().st_mtime 76 | if force or (cache.exists() and cache_mtime bool: 64 | "Returns False if `indicator_fname` is a sibling to `fname` else True" 65 | if p.exists(): return not bool(p.parent.ls().attrgot('name').filter(lambda x: x == ignore_fname)) 66 | else: True 67 | 68 | # %% ../nbs/api/12_test.ipynb 69 | @call_parse 70 | @delegates(nbglob_cli) 71 | def nbdev_test( 72 | path:str=None, # A notebook name or glob to test 73 | flags:str='', # Space separated list of test flags to run that are normally ignored 74 | n_workers:int=None, # Number of workers 75 | timing:bool=False, # Time each notebook to see which are slow 76 | do_print:bool=False, # Print start and end of each notebook 77 | pause:float=0.01, # Pause time (in seconds) between notebooks to avoid race conditions 78 | ignore_fname:str='.notest', # Filename that will result in siblings being ignored 79 | **kwargs): 80 | "Test in parallel notebooks matching `path`, passing along `flags`" 81 | skip_flags = get_config().tst_flags.split() 82 | force_flags = flags.split() 83 | files = nbglob(path, as_path=True, **kwargs) 84 | files = [f.absolute() for f in sorted(files) if _keep_file(f, ignore_fname)] 85 | if len(files)==0: return print('No files were eligible for testing') 86 | 87 | if n_workers is None: n_workers = 0 if len(files)==1 else min(num_cpus(), 8) 88 | if IN_NOTEBOOK: kw = {'method':'spawn'} if os.name=='nt' else {'method':'forkserver'} 89 | else: kw = {} 90 | wd_pth = get_config().nbs_path 91 | with working_directory(wd_pth if (wd_pth and wd_pth.exists()) else os.getcwd()): 92 | results = parallel(test_nb, files, skip_flags=skip_flags, force_flags=force_flags, n_workers=n_workers, 93 | basepath=get_config().config_path, pause=pause, do_print=do_print, **kw) 94 | passed,times = zip(*results) 95 | if all(passed): print("Success.") 96 | else: 97 | _fence = '='*50 98 | failed = '\n\t'.join(f.name for p,f in zip(passed,files) if not p) 99 | sys.stderr.write(f"\nnbdev Tests Failed On The Following Notebooks:\n{_fence}\n\t{failed}\n") 100 | sys.exit(1) 101 | if timing: 102 | for i,t in sorted(enumerate(times), key=lambda o:o[1], reverse=True): print(f"{files[i].name}: {int(t)} secs") 103 | -------------------------------------------------------------------------------- /nbs/.gitignore: -------------------------------------------------------------------------------- 1 | site_libs/ 2 | docs/ 3 | /.quarto/ 4 | -------------------------------------------------------------------------------- /nbs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/.nojekyll -------------------------------------------------------------------------------- /nbs/CNAME: -------------------------------------------------------------------------------- 1 | nbdev.fast.ai 2 | -------------------------------------------------------------------------------- /nbs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | pre-render: 4 | - pysym2md --output_file apilist.txt nbdev 5 | post-render: 6 | - llms_txt2ctx llms.txt --optional true --save_nbdev_fname llms-ctx-full.txt 7 | - llms_txt2ctx llms.txt --save_nbdev_fname llms-ctx.txt 8 | resources: 9 | - "*.txt" 10 | preview: 11 | port: 3000 12 | browser: false 13 | 14 | format: 15 | html: 16 | theme: cosmo 17 | css: styles.css 18 | toc: true 19 | toc-depth: 4 20 | keep-md: true 21 | commonmark: default 22 | 23 | website: 24 | twitter-card: true 25 | open-graph: true 26 | repo-actions: [issue] 27 | sidebar: 28 | style: floating 29 | contents: 30 | - auto: "/*.ipynb" 31 | - section: Tutorials 32 | contents: tutorials/* 33 | - section: Explanations 34 | contents: explanations/* 35 | - section: API 36 | contents: api/* 37 | favicon: favicon.png 38 | navbar: 39 | background: primary 40 | search: true 41 | collapse-below: lg 42 | left: 43 | - text: "Get Started" 44 | href: getting_started.ipynb 45 | - text: "Tutorial" 46 | href: tutorials/tutorial.ipynb 47 | - text: "Blog" 48 | href: blog/index.qmd 49 | - text: "Help" 50 | menu: 51 | - text: "Report an Issue" 52 | icon: bug 53 | href: https://github.com/fastai/nbdev/issues 54 | - text: "Fast.ai Forum" 55 | icon: chat-right-text 56 | href: https://forums.fast.ai/ 57 | - text: "FAQ" 58 | icon: question-circle 59 | href: getting_started.ipynb#faq 60 | right: 61 | - icon: github 62 | href: "https://github.com/fastai/nbdev" 63 | - icon: twitter 64 | href: https://twitter.com/fastdotai 65 | aria-label: Fast.ai Twitter 66 | 67 | metadata-files: [nbdev.yml] 68 | -------------------------------------------------------------------------------- /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 | "import importlib" 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_spec = importlib.util.spec_from_file_location(\"_modidx\", get_config().lib_path / \"_modidx.py\")\n", 102 | " midx = importlib.util.module_from_spec(midx_spec)\n", 103 | " midx_spec.loader.exec_module(midx)\n", 104 | "\n", 105 | " return L(files for mod in midx.d['syms'].values() for _,files in mod.values()).unique()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "#|export\n", 115 | "_re_import = re.compile(r\"from\\s+\\S+\\s+import\\s+\\S\")" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "#|hide\n", 125 | "assert _re_import.match('from foo import bar')\n", 126 | "assert not _re_import.match('#from foo import bar')" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "#|export\n", 136 | "def _to_absolute(code, py_path, lib_dir):\n", 137 | " if not _re_import.search(code): return code\n", 138 | " res = update_import(code, ast.parse(code).body, str(py_path.relative_to(lib_dir).parent), absolute_import)\n", 139 | " return ''.join(res) if res else code" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "#|export\n", 149 | "def _update_nb(nb_path, cells, lib_dir):\n", 150 | " \"Update notebook `nb_path` with contents from `cells`\"\n", 151 | " nbp = NBProcessor(nb_path, ExportModuleProc(), rm_directives=False)\n", 152 | " nbp.process()\n", 153 | " for cell in cells:\n", 154 | " assert cell.nb_path == nb_path\n", 155 | " nbcell = nbp.nb.cells[cell.idx]\n", 156 | " dirs,_ = _partition_cell(nbcell, 'python')\n", 157 | " nbcell.source = ''.join(dirs) + _to_absolute(cell.code, cell.py_path, lib_dir)\n", 158 | " write_nb(nbp.nb, nb_path)" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "#|export\n", 168 | "def _update_mod(py_path, lib_dir):\n", 169 | " \"Propagate changes from cells in module `py_path` to corresponding notebooks\"\n", 170 | " py_cells = L(_iter_py_cells(py_path)).filter(lambda o: o.nb != 'auto')\n", 171 | " for nb_path,cells in groupby(py_cells, 'nb_path').items(): _update_nb(nb_path, cells, lib_dir)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "#|hide\n", 181 | "assert min(_mod_files().map(lambda x: x.endswith('.py'))) is True" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "#|export\n", 191 | "@call_parse\n", 192 | "def nbdev_update(fname:str=None): # A Python file name to update\n", 193 | " \"Propagate change in modules matching `fname` to notebooks that created them\"\n", 194 | " 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", 195 | " if os.environ.get('IN_TEST',0): return\n", 196 | " cfg = get_config()\n", 197 | " if not cfg.cell_number: raise ValueError(\"`nbdev_update` does not support without cell_number in .py files. Please check your settings.ini\")\n", 198 | " fname = Path(fname or cfg.lib_path)\n", 199 | " lib_dir = cfg.lib_path.parent\n", 200 | " files = globtastic(fname, file_glob='*.py', skip_folder_re='^[_.]').filter(lambda x: str(Path(x).absolute().relative_to(lib_dir) in _mod_files()))\n", 201 | " files.map(_update_mod, lib_dir=lib_dir)" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "#|hide\n", 211 | "# nbdev_update(\"../nbdev/sync.py\")" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "## Export -" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "#|hide\n", 228 | "#|eval: false\n", 229 | "from nbdev.doclinks import nbdev_export\n", 230 | "nbdev_export()" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [] 239 | } 240 | ], 241 | "metadata": { 242 | "kernelspec": { 243 | "display_name": "python3", 244 | "language": "python", 245 | "name": "python3" 246 | } 247 | }, 248 | "nbformat": 4, 249 | "nbformat_minor": 4 250 | } 251 | -------------------------------------------------------------------------------- /nbs/api/15_qmd.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f0f931c2", 6 | "metadata": {}, 7 | "source": [ 8 | "# qmd\n", 9 | "\n", 10 | "> 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'![]({fname})', 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": "python3", 215 | "language": "python", 216 | "name": "python3" 217 | } 218 | }, 219 | "nbformat": 4, 220 | "nbformat_minor": 5 221 | } 222 | -------------------------------------------------------------------------------- /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(encoding='utf-8'))\n", 70 | "# try: p = ast.parse(path.read_text(encoding='utf-8'))\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', 'settings.ini')\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/'_brand.yml').exists(): files.append(path/'_brand.yml')\n", 144 | " if (path/'_extensions').exists(): files.extend(nbglob(path/'_extensions', func=Path, file_glob='', file_re='', skip_file_re='^[.]'))\n", 145 | "\n", 146 | " # If settings.ini or filter script newer than cache folder modified, delete cache\n", 147 | " chk_mtime = max(cfg.config_file.stat().st_mtime, Path(__file__).stat().st_mtime)\n", 148 | " cache.mkdir(parents=True, exist_ok=True)\n", 149 | " cache_mtime = cache.stat().st_mtime\n", 150 | " if force or (cache.exists() and cache_mtime 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 | "repo = nbdev\r\n", 45 | "description = Create delightful software with Jupyter Notebooks\r\n", 46 | "copyright = 2020 onwards, Jeremy Howard\r\n", 47 | "keywords = nbdev fastai jupyter notebook export\r\n", 48 | "user = fastai\r\n", 49 | "author = Jeremy Howard and Hamel Husain\r\n", 50 | "author_email = j@fast.ai\r\n", 51 | "branch = master\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 | "| repo | str | None | Repo name |\n", 79 | "| branch | str | None | Repo default branch |\n", 80 | "| user | str | None | Repo username |\n", 81 | "| author | str | None | Package author's name |\n", 82 | "| author_email | str | None | Package author's email address |\n", 83 | "| description | str | None | Short summary of the package |\n", 84 | "| path | str | . | Path to create config file |\n", 85 | "| cfg_name | str | settings.ini | Name of config file to create |\n", 86 | "| lib_name | str | %(repo)s | Package name |\n", 87 | "| git_url | str | https://github.com/%(user)s/%(repo)s | Repo URL |\n", 88 | "| custom_sidebar | bool_arg | False | Use a custom sidebar.yml? |\n", 89 | "| nbs_path | Path | nbs | Path to notebooks |\n", 90 | "| lib_path | Path | None | Path to package root (default: `repo` with `-` replaced by `_`) |\n", 91 | "| doc_path | Path | _docs | Path to rendered docs |\n", 92 | "| tst_flags | str | notest | Test flags |\n", 93 | "| version | str | 0.0.1 | Version of this release |\n", 94 | "| doc_host | str | https://%(user)s.github.io | Hostname for docs |\n", 95 | "| doc_baseurl | str | /%(repo)s | Base URL for docs |\n", 96 | "| keywords | str | nbdev jupyter notebook python | Package keywords |\n", 97 | "| license | str | apache2 | License for the package |\n", 98 | "| copyright | str | None | Copyright for the package, defaults to '`current_year` onwards, `author`' |\n", 99 | "| status | str | 3 | Development status PyPI classifier |\n", 100 | "| min_python | str | 3.7 | Minimum Python version PyPI classifier |\n", 101 | "| audience | str | Developers | Intended audience PyPI classifier |\n", 102 | "| language | str | English | Language PyPI classifier |\n", 103 | "| recursive | bool_arg | True | Include subfolders in notebook globs? |\n", 104 | "| black_formatting | bool_arg | False | Format libraries with black? |\n", 105 | "| readme_nb | str | index.ipynb | Notebook to export as repo readme |\n", 106 | "| title | str | %(lib_name)s | Quarto website title |\n", 107 | "| allowed_metadata_keys | str | | Preserve the list of keys in the main notebook metadata |\n", 108 | "| allowed_cell_metadata_keys | str | | Preserve the list of keys in cell level metadata |\n", 109 | "| jupyter_hooks | bool_arg | False | Run Jupyter hooks? |\n", 110 | "| clean_ids | bool_arg | True | Remove ids from plaintext reprs? |\n", 111 | "| clear_all | bool_arg | False | Remove all cell metadata and cell outputs? |\n", 112 | "| cell_number | bool_arg | True | Add cell number to the exported file |\n", 113 | "| put_version_in_init | bool_arg | True | Add the version to the main __init__.py in nbdev_export |\n", 114 | "| skip_procs | str | | A list of processors that you want to skip |" 115 | ], 116 | "text/plain": [ 117 | "" 118 | ] 119 | }, 120 | "execution_count": null, 121 | "metadata": {}, 122 | "output_type": "execute_result" 123 | } 124 | ], 125 | "source": [ 126 | "#| exec_doc\n", 127 | "#| echo: false\n", 128 | "DocmentTbl(nbdev_create_config)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "You can customise nbdev for all repositories for your user with a `~/.config/nbdev/settings.ini` file." 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "In order for Git actions to run smoothly, add `requirements` and `dev_requirements` with required packages in `settings.ini`.\n", 143 | "\n", 144 | "see [here](https://github.com/fastai/nbdev/blob/master/settings.ini) as a reference." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [] 153 | } 154 | ], 155 | "metadata": { 156 | "kernelspec": { 157 | "display_name": "python3", 158 | "language": "python", 159 | "name": "python3" 160 | } 161 | }, 162 | "nbformat": 4, 163 | "nbformat_minor": 4 164 | } 165 | -------------------------------------------------------------------------------- /nbs/explanations/docs.mermaid: -------------------------------------------------------------------------------- 1 | flowchart TB 2 | %%styles 3 | style JN fill:#FFA500 4 | style FP fill:#cfe5ed 5 | style SF fill:#dff1dd,stroke-dasharray: 5 5; 6 | style QMD fill:#7286bb,color:#fff; 7 | classDef files fill:#ede8ce ; 8 | classDef code fill:#5695c7,color:#fff; 9 | classDef container fill:#f9f9f6; 10 | 11 | %% list of nodes 12 | FP(Processing Pipeline 13 | transforms notebook based\non directives and front-matter) 14 | E(execnb) 15 | SD("show_doc") 16 | SS(Static Site 17 | HTML, CSS and Javascript) 18 | CF("Intermediate Output is stored in the _procs/ directory 19 | 20 | (This is a full copy of your Quarto project)") 21 | class SD,E code; 22 | 23 | subgraph SF["Source Files"] 24 | JN([Jupyter 25 | Notebook]) 26 | QMD(["Quarto 27 | Markdown 28 | (.qmd)"]) 29 | end 30 | 31 | 32 | %% connections to things inside Notebook Processor (NBP) 33 | JN -- json --> FP 34 | E -. "cell execution" .- SD 35 | 36 | subgraph NBP [" Notebook Processor 37 | "] 38 | SD -.- |"render API docs"|FP 39 | end 40 | 41 | FP -- modified json with only 42 | Quarto directives remaining --> CF 43 | 44 | subgraph Quarto ["Quarto
"] 45 | direction LR 46 | F[[_quarto.yml]] .-> G[[custom.yml]] & H[[sidebar.yml]] 47 | class F,G,H files; 48 | end 49 | 50 | QMD --"rendered 51 | directly by Quarto 52 | (no pre-processing required)"--> CF 53 | CF --> Quarto 54 | Quarto --> SS 55 | 56 | class NBP,CF,Quarto container; 57 | -------------------------------------------------------------------------------- /nbs/explanations/images/github-actions-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/explanations/images/github-actions-pages.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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": "python3", 45 | "language": "python", 46 | "name": "python3" 47 | } 48 | }, 49 | "nbformat": 4, 50 | "nbformat_minor": 5 51 | } 52 | -------------------------------------------------------------------------------- /nbs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/favicon.png -------------------------------------------------------------------------------- /nbs/images/amd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /nbs/images/bom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/bom.png -------------------------------------------------------------------------------- /nbs/images/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/card.png -------------------------------------------------------------------------------- /nbs/images/chris-lattner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/chris-lattner.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nbs/images/david-berg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/david-berg.jpeg -------------------------------------------------------------------------------- /nbs/images/divio-overview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/divio-overview.webp -------------------------------------------------------------------------------- /nbs/images/docs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /nbs/images/erik-gaasedelen.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/erik-gaasedelen.jpeg -------------------------------------------------------------------------------- /nbs/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/favicon.png -------------------------------------------------------------------------------- /nbs/images/fernando-pérez.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/fernando-pérez.jpeg -------------------------------------------------------------------------------- /nbs/images/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /nbs/images/hugo-bowne-anderson.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/hugo-bowne-anderson.jpeg -------------------------------------------------------------------------------- /nbs/images/jupyter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /nbs/images/lyft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo_standard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /nbs/images/netflix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /nbs/images/novetta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /nbs/images/outerbounds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /nbs/images/overstory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/overstory.png -------------------------------------------------------------------------------- /nbs/images/packaging.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /nbs/images/roxanna-pourzand.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/images/roxanna-pourzand.jpeg -------------------------------------------------------------------------------- /nbs/images/testing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /nbs/images/transform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /nbs/images/vscode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | h1, h2, h3 { 14 | font-weight: normal; 15 | } 16 | 17 | body { 18 | background: var(--background); 19 | color: var(--foreground); 20 | text-align: center; 21 | } 22 | 23 | #title-block-header { 24 | display: none; 25 | } 26 | 27 | .navbar { 28 | background: var(--background); 29 | text-align: left; 30 | font-size: 16px; 31 | } 32 | 33 | .title, .navbar-title { 34 | font-weight: bold; 35 | padding: 4px 8px; 36 | border-radius: 8px; 37 | margin-right: 4px; 38 | } 39 | 40 | .navbar-title { 41 | background: linear-gradient(240.01deg, #4B555B 15.29%, rgba(67, 78, 84, 0) 138.75%); 42 | } 43 | 44 | .title { 45 | background: linear-gradient(240.01deg, #78858C 15.29%, rgba(105, 117, 123, 0) 138.75%); 46 | } 47 | 48 | .content-block { 49 | margin-left: 30px; 50 | margin-right: 30px; 51 | overflow-x: hidden; 52 | } 53 | 54 | @media(min-width: 900px) { 55 | .content-block { 56 | margin-left: 50px; 57 | margin-right: 50px; 58 | } 59 | } 60 | 61 | @media(min-width: 1200px) { 62 | .content-block { 63 | max-width: var(--content-width); 64 | margin-left: auto; 65 | margin-right: auto; 66 | } 67 | } 68 | 69 | .hero-banner { 70 | padding-top: 20px; 71 | padding-bottom: 10px; 72 | margin-bottom: 30px; 73 | display: flex; 74 | flex-direction: column; 75 | align-items: center; 76 | } 77 | 78 | .hero-banner h1 { 79 | font-size: 56px; 80 | font-weight: bold; 81 | margin-top: 10px; 82 | margin-bottom: 40px; 83 | } 84 | 85 | .hero-banner h2 { 86 | border: none; 87 | font-size: 40px; 88 | font-weight: bold; 89 | margin-top: 10px; 90 | margin-bottom: 40px; 91 | } 92 | 93 | .hero-banner h3 { 94 | color: var(--foreground); 95 | font-size: 20px; 96 | max-width: 90%; 97 | margin-left: auto; 98 | margin-right: auto; 99 | margin-top: 0; 100 | margin-bottom: 40px; 101 | } 102 | 103 | @media(min-width: 900px) { 104 | .hero-banner h3 { 105 | max-width: 60%; 106 | } 107 | } 108 | 109 | @media(min-width: 1200px) { 110 | .hero-banner h3 { 111 | max-width: 75%; 112 | } 113 | } 114 | 115 | .mid-content { 116 | width: 100%; 117 | padding: 0; 118 | padding-top: 80px !important; 119 | padding-bottom: 80px; 120 | background: linear-gradient(235.87deg, #656A75 12.29%, #33373F 107.53%); 121 | overflow-x: hidden; 122 | } 123 | 124 | .testimonial { 125 | background: rgba(51, 55, 63, 0.4); 126 | border-radius: 12px; 127 | padding: 30px; 128 | text-align: left; 129 | } 130 | 131 | .testimonial p { 132 | margin: 0; 133 | padding: 0; 134 | } 135 | 136 | .testimonial img { 137 | margin: 0; 138 | padding: 0; 139 | display: inline; 140 | float: left; 141 | width: 50px; 142 | margin-right: 20px; 143 | border-radius: 8px; 144 | } 145 | 146 | .testimonial h1 { 147 | font-size: 16px; 148 | font-weight: bold; 149 | padding: 0; 150 | margin: 0; 151 | margin-bottom: 4px; 152 | } 153 | 154 | .testimonial h2 { 155 | border: none; 156 | font-size: 16px; 157 | padding: 0; 158 | margin: 0; 159 | color: #A2A8B4; 160 | } 161 | 162 | .testimonial h3 { 163 | margin: 0; 164 | margin-top: 30px; 165 | font-size: 16px; 166 | } 167 | 168 | .feature { 169 | font-size: 16px; 170 | height: 210px; 171 | background: var(--background-2); 172 | border-radius: 12px; 173 | padding: 30px; 174 | text-align: left; 175 | display: flex; 176 | flex-direction: column; 177 | } 178 | 179 | .feature img { 180 | margin-bottom: 20px; 181 | width: 48px; 182 | } 183 | 184 | .footer { 185 | color: #A2A8B4; 186 | font-size: 16px; 187 | margin-top: 40px; 188 | padding-top: 30px !important; 189 | border-top: 1px solid var(--foreground-2); 190 | } 191 | 192 | .btn-action-primary { 193 | color: #F9FAFB; 194 | background: #009AF1; 195 | font-weight: 700; 196 | line-height: 20px; /* identical to box height */ 197 | } 198 | 199 | .btn-action-primary:hover, .btn-action-primary:active, .btn-action-primary:focus { 200 | background: #35acf0; 201 | } 202 | 203 | .btn-action { 204 | min-width: 165px; 205 | border-radius: 8px; 206 | padding: 20px 48px; 207 | border: none; 208 | } 209 | 210 | /* Below are needed to make .ipynb equivalent to .qmd */ 211 | 212 | .figure { 213 | display: inline; 214 | } 215 | 216 | .quarto-figure { 217 | margin: 0; 218 | } 219 | -------------------------------------------------------------------------------- /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/llms.txt: -------------------------------------------------------------------------------- 1 | # nbdev 2 | 3 | nbdev makes exploration an integral part of your workflow, all while promoting software engineering best practices. It allows you to write, test, document, and distribute software packages and technical articles — all in one place, your notebook. 4 | 5 | Here are some of nbdev's features: 6 | 7 | - **Supports literate programming**: write prose, code, and tests in notebooks — no context-switching 8 | - **Git-friendly notebooks**: human-readable merge conflicts; no unwanted metadata 9 | - **Two-way sync between notebooks and plaintext source code**: allows you to use your IDE for code navigation or quick edits 10 | - **Beautiful technical documentation**: documentation is automatically generated using Quarto and hosted on GitHub Pages; docs support LaTeX, are searchable, and are automatically hyperlinked (including out-of-the-box support for many packages via nbdev-index) 11 | - **Seamless testing**: write tests as ordinary notebook cells, run them in parallel with a single command; out-of-the-box continuous integration with GitHub Actions to ensure tests pass before every merge 12 | - **Simplified package release**: publish packages to PyPI and conda; Python best practices are automatically followed (e.g. only exported objects are included in `__all__`) 13 | 14 | ## Tutorials 15 | 16 | - [Getting started](https://nbdev.fast.ai/getting_started.html.md) 17 | - [Tutorial](https://nbdev.fast.ai/tutorials/tutorial.html.md) 18 | - [Best practices](https://nbdev.fast.ai/tutorials/best_practices.html.md) 19 | 20 | ## API 21 | 22 | - [API List](https://nbdev.fast.ai/apilist.txt): A succint list of all functions and methods in nbdev. 23 | 24 | ## Optional 25 | 26 | - [Git-Friendly Jupyter](https://nbdev.fast.ai/tutorials/git_friendly_jupyter.html.md): How to use nbdev hooks for git-friendly Jupyter notebooks 27 | - [Blogging](https://nbdev.fast.ai/tutorials/blogging.html.md: Creating a blog with notebooks) 28 | - [Pre-Commit Hooks](https://nbdev.fast.ai/tutorials/pre_commit.html.md): How to use nbdev’s git pre-commit hooks 29 | - [Documentation Only Sites](https://nbdev.fast.ai/tutorials/docs_only.html.md): How to create nbdev powered docs without a library! 30 | - [Modular nbdev](https://nbdev.fast.ai/tutorials/modular_nbdev.html.md): How to use nbdev’s various tools separately 31 | - [Writing nbdev plugins](https://nbdev.fast.ai/tutorials/extensions.html.md): How to customize nbdev processors to do what you want 32 | - [nbdev1 Migration](https://nbdev.fast.ai/tutorials/migrating.html.md): How to change your nbdev1 repo to work with nbdev2 33 | - [nbdev.config](https://nbdev.fast.ai/api/config.html.md): Configuring nbdev and bootstrapping notebook export 34 | - [nbdev.maker](https://nbdev.fast.ai/api/maker.html.md): Create one or more modules from selected notebook cells 35 | - [nbdev.process](https://nbdev.fast.ai/api/process.html.md): A notebook processor 36 | - [nbdev.export](https://nbdev.fast.ai/api/export.html.md): Exporting a notebook to a library 37 | - [nbdev.doclinks](https://nbdev.fast.ai/api/doclinks.html.md): Generating a documentation index from a module 38 | - [nbdev.sync](https://nbdev.fast.ai/api/sync.html.md): Propagate small changes in the library back to notebooks 39 | - [nbdev.merge](https://nbdev.fast.ai/api/merge.html.md): Fix merge conflicts in jupyter notebooks 40 | - [nbdev.showdoc](https://nbdev.fast.ai/api/showdoc.html.md): Display symbol documentation in notebook and website 41 | - [nbdev.frontmatter](https://nbdev.fast.ai/api/frontmatter.html.md): A YAML and formatted-markdown frontmatter processor 42 | - [nbdev.processor](https://nbdev.fast.ai/api/processors.html.md): Some processors for NBProcessor 43 | - [nbdev.clean](https://nbdev.fast.ai/api/clean.html.md): Strip superfluous metadata from notebooks 44 | - [nbdev.test](https://nbdev.fast.ai/api/test.html.md): Run unit tests on notebooks in parallel 45 | - [nbdev.cli](https://nbdev.fast.ai/api/cli.html.md): CLI commands 46 | - [nbdev.quarto](https://nbdev.fast.ai/api/quarto.html.md): Install and interact with Quarto from nbdev 47 | - [nbdev.qmd](https://nbdev.fast.ai/api/qmd.html.md): Basic qmd generation helpers (experimental) 48 | - [nbdev.migrate](https://nbdev.fast.ai/api/migrate.html.md): Utilities for migrating to nbdev 49 | - [nbdev.serve](https://nbdev.fast.ai/api/serve.html.md): A parallel ipynb processor (experimental) 50 | - [nbdev.release](https://nbdev.fast.ai/api/release.html.md): Auto-generated tagged releases and release notes from GitHub issues -------------------------------------------------------------------------------- /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: main 9 | repo-url: "https://github.com/AnswerDotAI/nbdev" 10 | -------------------------------------------------------------------------------- /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/tutorials/.nodoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/.nodoc -------------------------------------------------------------------------------- /nbs/tutorials/.notest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/.notest -------------------------------------------------------------------------------- /nbs/tutorials/docs_only.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0cbcd12b-a30a-4aec-bc64-6ef6e89fc847", 6 | "metadata": {}, 7 | "source": [ 8 | "# Documentation Only Sites\n", 9 | "\n", 10 | "> 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": "python3", 78 | "language": "python", 79 | "name": "python3" 80 | } 81 | }, 82 | "nbformat": 4, 83 | "nbformat_minor": 5 84 | } 85 | -------------------------------------------------------------------------------- /nbs/tutorials/images/github-actions-initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/github-actions-initial.png -------------------------------------------------------------------------------- /nbs/tutorials/images/github-actions-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/github-actions-pages.png -------------------------------------------------------------------------------- /nbs/tutorials/images/github-create-new-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/github-create-new-repo.png -------------------------------------------------------------------------------- /nbs/tutorials/images/github-enable-pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/github-enable-pages.png -------------------------------------------------------------------------------- /nbs/tutorials/images/github-repo-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/github-repo-empty.png -------------------------------------------------------------------------------- /nbs/tutorials/images/jupyter-blank-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/jupyter-blank-terminal.png -------------------------------------------------------------------------------- /nbs/tutorials/images/jupyter-friendly-conflict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/jupyter-friendly-conflict.png -------------------------------------------------------------------------------- /nbs/tutorials/images/jupyter-unreadable-notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/jupyter-unreadable-notebook.png -------------------------------------------------------------------------------- /nbs/tutorials/images/jupyter-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/jupyter-welcome.png -------------------------------------------------------------------------------- /nbs/tutorials/images/marie-curie-notebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/marie-curie-notebook.jpg -------------------------------------------------------------------------------- /nbs/tutorials/images/nbdev-hello-world-site-initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/nbs/tutorials/images/nbdev-hello-world-site-initial.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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": "python3", 213 | "language": "python", 214 | "name": "python3" 215 | } 216 | }, 217 | "nbformat": 4, 218 | "nbformat_minor": 5 219 | } 220 | -------------------------------------------------------------------------------- /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 | ![Jupyter](/images/jupyter.svg) 118 | 119 | ![Vscode](/images/vscode.svg) 120 | 121 | ![Git](/images/git.svg) 122 | ::: 123 | ``` 124 | 125 | ::: {layout-ncol=3} 126 | ![Jupyter](/images/jupyter.svg){width=100px fig-align="left"} 127 | 128 | ![Vscode](/images/vscode.svg){width=100px fig-align="left"} 129 | 130 | ![Git](/images/git.svg){width=100px fig-align="left"} 131 | ::: 132 | 133 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=64.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name="nbdev" 7 | requires-python=">=3.9" 8 | dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"] 9 | 10 | [tool.uv] 11 | cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }] 12 | -------------------------------------------------------------------------------- /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 = AnswerDotAI 8 | author = Jeremy Howard and Hamel Husain 9 | author_email = j@fast.ai 10 | branch = main 11 | min_python = 3.9 12 | version = 2.4.3 13 | audience = Developers 14 | language = English 15 | custom_sidebar = True 16 | license = apache2 17 | status = 5 18 | requirements = fastcore>=1.8.0 execnb>=0.1.12 astunparse ghapi>=1.0.3 watchdog asttokens setuptools 19 | pip_requirements = PyYAML 20 | conda_requirements = pyyaml 21 | conda_user = fastai 22 | dev_requirements = ipywidgets nbdev-numpy nbdev-stdlib pandas matplotlib black svg.py nbclassic pysymbol_llm llms-txt sphinx plum-dispatch 23 | console_scripts = nbdev_create_config=nbdev.config:nbdev_create_config 24 | nbdev_update=nbdev.sync:nbdev_update 25 | nbdev_update_license=nbdev.cli:nbdev_update_license 26 | nbdev_export=nbdev.doclinks:nbdev_export 27 | nbdev_fix=nbdev.merge:nbdev_fix 28 | nbdev_merge=nbdev.merge:nbdev_merge 29 | nbdev_trust=nbdev.clean:nbdev_trust 30 | nbdev_clean=nbdev.clean:nbdev_clean 31 | nbdev_install_hooks=nbdev.clean:nbdev_install_hooks 32 | nbdev_filter=nbdev.cli:nbdev_filter 33 | nbdev_sidebar=nbdev.quarto:nbdev_sidebar 34 | nbdev_test=nbdev.test:nbdev_test 35 | nbdev_new=nbdev.cli:nbdev_new 36 | nbdev_migrate=nbdev.migrate:nbdev_migrate 37 | nbdev_install_quarto=nbdev.quarto:install_quarto 38 | nbdev_install=nbdev.quarto:install 39 | nbdev_docs=nbdev.quarto:nbdev_docs 40 | nbdev_preview=nbdev.quarto:nbdev_preview 41 | nbdev_prepare=nbdev.quarto:prepare 42 | nbdev_readme=nbdev.quarto:nbdev_readme 43 | nbdev_contributing=nbdev.quarto:nbdev_contributing 44 | nbdev_release_gh=nbdev.release:release_gh 45 | nbdev_release_git=nbdev.release:release_git 46 | nbdev_changelog=nbdev.release:changelog 47 | nbdev_pypi=nbdev.release:release_pypi 48 | nbdev_conda=nbdev.release:release_conda 49 | nbdev_release_both=nbdev.release:release_both 50 | nbdev_bump_version=nbdev.release:nbdev_bump_version 51 | nbdev_requirements=nbdev.release:write_requirements 52 | nbdev_proc_nbs=nbdev.quarto:nbdev_proc_nbs 53 | nbdev_help=nbdev.cli:chelp 54 | nb_export=nbdev.cli:nb_export_cli 55 | watch_export=nbdev.cli:watch_export 56 | tst_flags = notest 57 | nbs_path = nbs 58 | doc_path = _docs 59 | recursive = True 60 | doc_host = https://nbdev.fast.ai 61 | doc_baseurl = / 62 | git_url = https://github.com/AnswerDotAI/nbdev 63 | lib_path = nbdev 64 | title = nbdev 65 | black_formatting = False 66 | readme_nb = getting_started.ipynb 67 | allowed_metadata_keys = 68 | allowed_cell_metadata_keys = 69 | jupyter_hooks = True 70 | clean_ids = False 71 | clear_all = False 72 | cell_number = False 73 | put_version_in_init = True 74 | skip_procs = 75 | update_pyproject = True 76 | 77 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | from configparser import ConfigParser 3 | 4 | import setuptools 5 | from pkg_resources import parse_version 6 | 7 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 8 | 9 | # note: all settings are in settings.ini; edit there, not here 10 | config = ConfigParser(delimiters=['=']) 11 | config.read('settings.ini', encoding="utf-8") 12 | cfg = config['DEFAULT'] 13 | 14 | cfg_keys = 'version description keywords author author_email'.split() 15 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 16 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 17 | setup_cfg = {o:cfg[o] for o in cfg_keys} 18 | 19 | licenses = { 20 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 21 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 22 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 23 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 24 | 'agpl3': ('GNU Affero General Public License v3', 'OSI Approved :: GNU Affero General Public License (AGPLv3)'), 25 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 26 | } 27 | statuses = [ '0 - Pre-Planning', '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 28 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 29 | py_versions = '3.7 3.8 3.9 3.10 3.12 3.13'.split() 30 | 31 | requirements = ['packaging'] 32 | requirements += shlex.split(cfg.get('requirements', '')) 33 | if cfg.get('pip_requirements'): requirements += shlex.split(cfg.get('pip_requirements', '')) 34 | min_python = cfg['min_python'] 35 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 36 | dev_requirements = (cfg.get('dev_requirements') or '').split() 37 | project_urls = {} 38 | if cfg.get('doc_host'): project_urls["Documentation"] = cfg['doc_host'] + cfg.get('doc_baseurl', '') 39 | 40 | setuptools.setup( 41 | name = cfg['lib_name'], 42 | license = lic[0], 43 | classifiers = [ 44 | 'Development Status :: ' + statuses[int(cfg['status'])], 45 | 'Intended Audience :: ' + cfg['audience'].title(), 46 | 'Natural Language :: ' + cfg['language'].title(), 47 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 48 | url = cfg['git_url'], 49 | packages = setuptools.find_packages(), 50 | include_package_data = True, 51 | install_requires = requirements, 52 | extras_require={ 'dev': dev_requirements }, 53 | dependency_links = cfg.get('dep_links','').split(), 54 | python_requires = '>=' + cfg['min_python'], 55 | long_description = open('README.md', encoding="utf8").read(), 56 | long_description_content_type = 'text/markdown', 57 | zip_safe = False, 58 | entry_points = { 59 | 'console_scripts': cfg.get('console_scripts','').split(), 60 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 61 | }, 62 | project_urls = project_urls, 63 | **setup_cfg) 64 | 65 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | _docs_test_files/ 2 | -------------------------------------------------------------------------------- /tests/.nodoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/tests/.nodoc -------------------------------------------------------------------------------- /tests/.notest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/tests/.notest -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ![]({{ site.baseurl }}/images/logo.png "fast.ai's logo") 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/export_procs.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 | "cell_type": "markdown", 18 | "id": "40855489-6543-4f63-81c4-1127f4e09c31", 19 | "metadata": {}, 20 | "source": [ 21 | "to test `scrub_magics`:" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "22aa79e5-80ba-4b11-a655-ad8d830fa2ef", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "%%spark\n", 32 | "#|export bar\n", 33 | "\"hello nbdev\"" 34 | ] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3 (ipykernel)", 40 | "language": "python", 41 | "name": "python3" 42 | } 43 | }, 44 | "nbformat": 4, 45 | "nbformat_minor": 5 46 | } 47 | -------------------------------------------------------------------------------- /tests/image.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAE0lEQVR4nGNkaGDACpiwCw9WCQBqCACQJ5at+QAAAABJRU5ErkJggg==\n", 11 | "text/plain": [ 12 | "" 13 | ] 14 | }, 15 | "execution_count": null, 16 | "metadata": {}, 17 | "output_type": "execute_result" 18 | } 19 | ], 20 | "source": [ 21 | "from PIL import Image\n", 22 | "Image.new(mode='RGB', size=(8, 8), color=\"green\")" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "torch", 29 | "language": "python", 30 | "name": "python3" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 2 35 | } 36 | -------------------------------------------------------------------------------- /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/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/notest/.notest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnswerDotAI/nbdev/ea0800cb1f4a7c5a847e840cf18bfecba95fd707/tests/notest/.notest -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------