Serializing package requirements in marimo notebooks

Serializing package requirements in marimo notebooks

One of marimo’s goals is to make notebooks reproducible, down to the packages used in them. To that end, it’s now possible to create marimo notebooks that have their package requirements serialized into them as a top-level comment. Given a notebook with inlined requirements, marimo can run it in an isolated virtual environment with a single command:

marimo edit --sandbox notebook.py

This creates a fresh virtual environment, or sandbox, and installs the dependencies before opening the notebook. marimo’s opt-in package management features can even track imports and automatically add them to your notebook’s inlined requirements.

In other words, marimo makes it possible to create and share standalone notebooks, without shipping requirements.txt files alongside them. This provides two key benefits:

  1. Notebooks that carry their own dependencies are easy to share — just send the .py file!
  2. Isolating a notebook from other installed packages prevents obscure bugs arising from environment pollution, while also hardening reproducibility.

Package sandboxing is powered by uv, an exciting new package manager for Python that is extremely fast, and that recently implemented a Python standard for inline metadata. Before uv, package sandboxing was impractical — installing packages was slow, and no mainstream package manager had implemented seamless support for inlined requirements.

Reproducibility, down to the packages

The motivation for building package sandboxing into marimo comes from a decade of our own experience of working with Jupyter notebooks — either our own, or our colleagues — that failed to reproduce. These notebooks often failed to reproduce for reasons due to package management, such as:

  1. The notebook author didn’t track packages in a requirements.txt or pyproject.toml at all.
  2. The author did track packages at some point, but failed to keep the requirements file up-to-date.

In practice, this means a large fraction of Python notebooks are relegated to being archaeological artifacts that provide us a glimpse into work that was done in the past, instead of programs that we can execute today — a real blow to reproducibility in science and reusability in engineering.

When we first created marimo, we wanted to make it possible for marimo notebooks to document their own dependencies in the notebook file, and for marimo to be able to hydrate a fresh virtual environment containing only the packages that the notebook file specifies. In other words, we wanted marimo to provide a package sandbox for notebooks. Indeed, this is what Pluto.jl, a reactive notebook for Julia, does. As the Pluto authors have pointed out, Excel sheets can be shipped around as standalone files and run on anyone’s computer — why should notebooks be any different?

But package management in Python is a controversial topic, and inventing our own package manager capable of satisfying our criteria was decidedly something we didn’t want to do. So, we waited, and in the meantime, we built integrations with package managers so that marimo could install missing packages on your behalf.

Thankfully, our friends at Astral eventually developed uv, a package manager that supports inline script metadata and isolated virtual environments, while also happening to be blazingly fast. Encouraged by uv’s rapid adoption and adherence to Python standards, we decided to adopt it to implement package sandboxing.

Usage

marimo’s package sandbox is opt-in, and can be enabled at the command-line. Here are a few examples:

# Edit a notebook in a sandboxed environment
marimo edit --sandbox notebook.py
 
# Run a notebook as a read-only app in a sandboxed environment
marimo run --sandbox notebook.py
 
# Create a new notebook in a sandboxed environment
marimo new --sandbox

Because we store marimo notebooks as pure Python files, you can also run them as Python scripts with all their required dependencies using uv:

uv run notebook.py

Serializing package requirements into notebooks

When used with --sandbox, marimo automatically keeps tracks of the packages your notebook uses them and writes them to your notebook file. You can also manually add dependencies to the notebook file using

uv add --script notebook.py <package_name>

Implementation

marimo’s sandbox was made possible by a confluence of technologies: marimo’s static analysis and Python file format (Python, not JSON!), advancements in Python standards, and an implementation of these standards in uv.

There are two main parts to our implementation:

  1. Tracking the dependencies of the notebook in real-time
  2. Creating a virtual environment for the notebook, with its required dependencies

Tracking the dependencies

When you edit a marimo notebook opened with --sandbox, marimo tracks the set of packages used by your notebook in real-time. We do this with static analysis: using Python’s ast module, marimo parses each cell after it has been executed and extract the dependencies from the import statements. Once we have the imported modules, we convert them to package names using a simple heuristic (a table) and check if the package is installed with uv pip list. If the package is not installed, we ask the user if they’d like to install it.

To add the package metadata to the notebook, we run uv add --script <notebook.py> <packages> (and similarly, uv remove), which adheres to PEP 723. PEP 723 is a Python Enhancement Proposal for “Inline script metadata” that makes it possible to specify the dependencies of a script in the script itself.

This process leaves us with a list of dependencies at the top of the notebook:

# /// script
# requires-python = ">=3.11"
# dependencies = [
#     "marimo",
#     "altair==5.4.1",
#     "polars==1.7.1",
# ]
# ///

It is worth noting that leveraging PEP 723 is only possible because unlike Jupyter notebooks, marimo notebooks are stored as pure Python files, letting marimo take advantage of the exciting new developments in the Python ecosystem.

Creating an isolated virtual environment

Once we have the dependencies, we can create an isolated virtual environment for the notebook. We do this by running marimo edit --sandbox <notebook.py>

This grabs all the dependencies listed in the notebook file, creates a temporary requirements.txt and passes it to uv. The final command looks something like:

uv run --with /tmp/folder/requirements.txt marimo edit <notebook.py>

This command creates a new virtual environment, installs the dependencies that are required for running the notebook, and opens the notebook as usual via the marimo CLI. In future, we plan to add additional features such as reading from and writing to a pyproject.toml.

Community

We’re already making use of sandboxed notebooks in our marimo spotlights, making it easy for anyone to run notebooks created by our amazing community. We hope that marimo’s package sandbox makes it easier for you to create and share standalone notebooks, too!

Join the marimo community

If you’re interested in helping shape marimo’s future, here are some ways you can get involved: