Skip to main content

juv logo
juv

A toolkit for reproducible Jupyter notebooks, powered by uv.

  • 🗂️ Create, manage, and run Jupyter notebooks with their dependencies
  • 📌 Pin dependencies with PEP 723 - inline script metadata
  • 🚀 Launch ephemeral sessions for multiple front ends (e.g., JupyterLab, Notebook, NbClassic)
  • ⚡ Powered by uv for fast dependency management

Installation

juv is published to the Python Package Index (PyPI) and can be installed globally with uv or pipx (recommended):

uv tool install juv
# or pipx install juv

You can also use the uvx command to invoke it without installing:

uvx juv

Usage

juv should feel familar for uv users. The goal is to extend its dependencies management to Jupyter notebooks.

# Create a notebook
juv init notebook.ipynb
juv init --python=3.9 notebook.ipynb # specify a minimum Python version

# Add dependencies to the notebook
juv add notebook.ipynb pandas numpy
juv add notebook.ipynb --requirements=requirements.txt

# Pin a timestamp to constrain dependency resolution to a specific date
juv stamp notebook.ipynb # now

# Launch the notebook
juv run notebook.ipynb
juv run --with=polars notebook.ipynb # additional dependencies for this session (not saved)
juv run --jupyter=notebook@6.4.0 notebook.ipynb # pick a specific Jupyter frontend
juv run --jupyter=nbclassic notebook.ipynb -- --no-browser # pass additional arguments to Jupyter

# JUV_JUPYTER env var to set preferred Jupyter frontend (default: lab)
export JUV_JUPYTER=nbclassic
juv run notebook.ipynb

# Lock the dependencies of a notebook
# The lockfile is respected (and updated) when using `juv run`/`juv add`/`juv remove`
juv lock Untitled.ipynb
# Print the lockfile
cat Untitled.ipynb | jq -r '.metadata["uv.lock"]'

# See dependency tree of notebook
juv tree Untitled.ipynb

# Export a lockfile in a pip-compatable format
juv export Untitled.ipynb

If a script is provided to run, it will be converted to a notebook before launching the Jupyter session.

uvx juv run script.py
# Converted script to notebook `script.ipynb`
# Launching Jupyter session...

Exporting virtual environments

juv manages notebooks with dependencies and runs them in a Jupyter UI using ephemeral virtual environments. To make these environments available to other tools, use juv venv to export a virtual environment with a kernel.

juv venv --from=Untitled.ipynb
# Using CPython 3.13.0
# Creating virtual environment at: .venv
# Activate with: source .venv/bin/activate

Most editors (e.g., VS Code) allow selecting this environment for running notebooks and enabling features like autocomplete and type checking. To omit adding ipykernel to the exported enviroment, you can add --no-kernel flag:

juv venv --from=Untitled.ipynb --no-kernel

[!NOTE] We do not recommend modifying this environment directly (e.g., with pip or uv, see below). Instead, recreate it by running juv venv again whenever you update dependencies to keep it up to date.

Other Jupyter front ends (e.g., VS Code)

juv has a VS Code extension that provides a more integrated experience. Notebooks created with the juv CLI can be run with the extension and vice versa.

Motivation

Rethinking the "getting started" guide for notebooks

Jupyter notebooks are the de facto standard for data science, yet they suffer from a reproducibility crisis.

This issue does not stem from a fundamental lack of care for reproducibility. Rather, our tools limit us from easily falling into the pit of success with notebooks - in particular, managing dependencies.

Notebooks are much like one-off Python scripts and therefore do not benefit from the same dependency management as packages. Being a "good steward" of notebooks requires discipline (due to the manual nature of virtual environments) and knowledge of Python packaging - a somewhat unreasonable expectation for domain experts who are focused on solving problems, not software engineering.

You will often find a "getting started" guide in the wild like this:

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt # or just pip install pandas numpy, etc
jupyter lab

Four lines of code, where a few things can go wrong. What version of Python? What package version(s)? What if we forget to activate the environment?

The gold standard for "getting started" is a single command (i.e, no guide).

<magic tool> run notebook.ipynb

However, this ideal has remained elusive for Jupyter notebooks. Why?

  • Virtual environments are a leaky abstraction deeply ingrained in the Python psyche: create, activate, install, run. Their historical "cost" has forced us to treat them as entities that must be managed explicitly. In fact, an entire ecosystem of tooling and best practices are oriented around long-lived environments, rather than something more ephemeral. End users separately create and then mutate virtual environments with low-level tools like pip. The manual nature and overhead of these steps encourages sharing environments across projects - a nightmare for reproducibility.

  • Only Python packages could historically specify their dependencies. Data science code often lives in notebooks rather than packages, with no way to specify dependencies for standalone scripts without external files like requirements.txt.

Aligning of the stars

Two key ideas have changed my perspective on this problem and inspired juv:

  • Virtual environments are now "cheap". A year ago, they were a necessary evil. uv is such a departure from the status quo that it forces us to rethink best practices. Environments are now created faster than JupyterLab starts - why keep them around at all?

  • PEP 723. Inline script metadata introduces a standard for specifying dependencies for standalone Python scripts. A single file can now contain everything needed to run it, without relying on external files like requirements.txt or pyproject.toml.

So, what if:

  • Environments were disposable by default?
  • Notebooks could specify their own dependencies?

This is the vision of juv

[!NOTE] Dependency management is just one challenge for notebook reproducibility (non-linear execution being another). juv aims to solve this specific pain point for the existing ecosystem. I'm personally excited for initiatives that rethink notebooks from the ground up, making a tool like juv obsolete.

How

PEP 723 (inline script metadata) allows specifying dependencies as comments within Python scripts, enabling self-contained, reproducible execution. This feature could significantly improve reproducibility in the data science ecosystem, since many analyses are shared as standalone code (not packages). However, a lot of data science code lives in notebooks (.ipynb files), not Python scripts (.py files).

juv bridges this gap by:

  • Extending PEP 723-style metadata support from uv to Jupyter notebooks
  • Launching Jupyter sessions for various notebook front ends (e.g., JupyterLab, Notebook, NbClassic) with the specified dependencies

It's a simple Python script that parses the notebook and starts a Jupyter session with the specified dependencies (piggybacking on uv's existing functionality).

Alternatives

juv is opinionated and might not suit your preferences. That's ok! uv is super extensible, and I recommend reading the wonderful documentation to learn about its primitives.

For example, you can achieve a similar workflow using the --with-requirements flag:

uvx --with-requirements=requirements.txt --from=jupyter-core --with=jupyterlab jupyter lab notebook.ipynb

While slightly more verbose and breaking self-containment, this approach totally works and saves you from installing another dependency.

There is also an experimental rewrite in Rust.

Contributing

juv welcomes contributions in the form of bug reports, feature requests, and pull requests. See the CONTRIBUTING.md for more information.

Metadata

Release files for juv 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for juv 0.5.0
File Size Uploaded
juv-0.5.0.tar.gz 376.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for juv 0.5.0
File Interpreter ABI Platform
juv-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 410.5 kB

Release files / juv-0.5.0.tar.gz

Download URL juv-0.5.0.tar.gz
Size 376.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3d2b9b07b46afd850f0c3da61d59855dbb7e25698b34f37a10fcb26839adb12f
BLAKE2b-256 checksum
How to use checksums
b161e5c4f2ddb7ada0e1443ae595a7daf0f204a527dd7dab6c813481febe087e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 30, 2026.

Transparency log

Release files / juv-0.5.0-py3-none-any.whl

Download URL juv-0.5.0-py3-none-any.whl
Size 33.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fae83eda3413e06fef94df6b15c4644ccaffd8518e862b8a6246baa80b9a3fe7
BLAKE2b-256 checksum
How to use checksums
e07f2e52d4136ff0a1cd90a1701bbcd8dd45bf19c9046bb3c2fcc7e377e03f21
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.25

2 release files

0.2.24

2 release files

0.2.23

2 release files

0.2.15

2 release files

0.2.14

2 release files

0.2.13

2 release files

0.2.12

2 release files

0.2.11

2 release files

0.2.10

2 release files

0.2.9

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page