I wanted to launch a bunch of Python notebooks without having to carefully manage virtualenvs and kernels, so I made a [single file, hermetic notebook](https://gist.github.com/zsol/d624b49a505e1041f6948df37d74398e). You use it by downloading the file and issuing ``` uv run nb.py ``` (assuming you have [uv](https://docs.astral.sh/uv/) installed) It will pop up a jupyter notebook in your browser, and away we go! ![[Hermetic Notebook Screenshot.png]] ## Things you can do Apart from the usual Jupyter things, you can: ### Add more dependencies ``` uv add --script nb.py numpy scipy matplotlib ``` Or edit the `dependencies` block in the first cell, and rerun `uv run nb.py`. ### Check it into a repo This notebook is a plain Python file, so unless you use top-level `await` expressions, it will work just fine (`%` magics are automatically commented out). ### Make it executable On Linux/Mac, you could add a shebang like this: ``` #!/usr/bin/env -S uv run ``` to the top of the file, and after making it executable with `chmod +x nb.py`, you can directly run it: `./nb.py` ### Lint, Format, Type check These tools are pre-installed in the notebook for you - formatting works using `black` and `isort`, type checking via `mypy`, and linting via `ruff`. ## How does it work At the top of the file, there are a few things: * [Inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata) captures dependencies, which `uv run` [reads and installs](https://docs.astral.sh/uv/guides/scripts/#running-a-script-with-dependencies) them into an ephemeral virtualenv (including the Python version if it's missing) * [Jupytext](https://jupytext.readthedocs.io/en/latest/) headers allow the `.py` file to be read as a notebook * the small bit of Python code at the top initializes Jupyter, isolates its configuration from `$HOME/.jupyter` and makes it open `.py` files using Jupytext by default, and opens itself when launching Jupyter * `# %% jupyter={"source_hidden": true}` makes Jupyter hide (collapse) this first cell by default Everything after the last `# %%` marker is the second cell of the notebook. Apart from this, the dependencies include `black`, `isort`, and `jupyterlab-code-formatter` to power autoformatting of cells, `python-lsp-server` to provide IDE services using Jedi, and `pylsp-mypy` to make type checking happen.