ModernCS
Session 1.190 minFree preview

Setting Up Without Losing a Week

uv, a virtual environment, JupyterLab running locally, and reading the error when it breaks.

By the end of this session you will be able to:

  • Create an isolated Python project with uv and install pandas into it without touching your system Python
  • Start JupyterLab and prove, from inside a notebook, that it is running against that project and no other
  • Delete your environment, rebuild it from the lockfile, and get byte-identical package versions back

What actually eats the week

The classic first week of a data course goes like this. You install Python from python.org. Your machine already had one, so now there are two. You run pip install pandas and it succeeds. You open a notebook, type import pandas, and get ModuleNotFoundError. You install it again, in a different terminal, and now it works. Two weeks later a tutorial tells you to pip install something else, that something else needs an older NumPy, pip quietly downgrades it, and three notebooks that worked on Tuesday stop working on Thursday.

None of that is your fault, and none of it is about Python being hard. It is one problem wearing four costumes: you do not know which Python interpreter is running, and you do not know what is installed in it. Every fix below exists to make those two questions answerable in one second.

The tool that answers them is uv, from Astral. It installs Python versions, creates the virtual environment, resolves dependencies, and writes a lockfile, all as one program. You do not need conda, you do not need pyenv, and you will not run pip directly again in this course.

One project, one environment, one command each

Install uv first. On macOS or Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows, in PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

The installer prints one line telling you how to put uv on your PATH. Do what it says, or just close the terminal and open a new one. Then confirm:

uv --version

Now make a project. A project is a folder with a pyproject.toml in it; that file is the record of what your work depends on.

uv init --python 3.13 analytics
cd analytics

That writes pyproject.toml, a .python-version file holding the string 3.13, a README.md, and a small source directory you can ignore for notebook work. If you do not have Python 3.13 on the machine, uv downloads it for you. It does not touch or replace the Python your operating system uses.

Add what you need:

uv add pandas numpy

Three things just happened. uv created .venv/ inside the folder, resolved pandas and NumPy along with everything they depend on, and wrote uv.lock recording the exact version and hash of every single package. Look at pyproject.toml and you will see your two names under [project] dependencies. Look at uv.lock and you will see dozens of entries. That difference is the point: pyproject.toml is what you asked for, uv.lock is what you got.

You never activate the environment. Instead you prefix commands with uv run, which syncs the environment against the lockfile and then runs the command inside it:

uv run python -c "import pandas; print(pandas.__version__)"

That should print a 3.x version. pandas 3.0 shipped in January 2026 and is the version this course assumes.

Making JupyterLab point at the right place

JupyterLab is a server plus a kernel. The server draws the interface; the kernel is the Python process that actually runs your cells. They do not have to be the same environment, and when a notebook says ModuleNotFoundError for a package you are certain you installed, it is almost always because they are not.

So you register your project's environment as a named kernel, once:

uv add --dev ipykernel
uv run ipython kernel install --user --env VIRTUAL_ENV $(pwd)/.venv --name=analytics

On Windows PowerShell, swap $(pwd) for $PWD. The --dev flag puts ipykernel in a development dependency group, because it is part of how you work, not part of what your analysis needs.

Now start the server:

uv run --with jupyter jupyter lab

--with jupyter supplies JupyterLab for this run without adding it to your project's dependencies. Your browser opens. Create a new notebook and pick the analytics kernel from the launcher or the kernel picker in the top right.

Then, before anything else, run this in the first cell:

import sys
import pandas
 
print(sys.executable)
print(pandas.__version__)

sys.executable must end in analytics/.venv/bin/python (or analytics\.venv\Scripts\python.exe on Windows). If it points anywhere else, you are on the wrong kernel and nothing you do next will be trustworthy. This two line check takes three seconds and will save you hours over the next twenty sessions.

Reproducible is a property of the lockfile

"It works on my machine" is a lockfile problem. pyproject.toml says pandas, which will happily mean 3.0.5 today and 3.2.0 next March. uv.lock says exactly 3.0.5, with a hash.

To rebuild an environment exactly as recorded:

uv sync --locked

--locked means: fail loudly if uv.lock is missing or out of date, rather than silently resolving something new. That is what you want on another machine, on a grader's machine, and in any automated check. The related flag --frozen uses the lockfile as-is without checking it against pyproject.toml.

Commit pyproject.toml, uv.lock and .python-version to git. Never commit .venv/; it is large, machine specific, and rebuildable in seconds.

Reading the error instead of reinstalling

Three failures cover most of what you will hit this week.

uv: command not found right after installing means the shell you are in loaded its PATH before uv existed. Open a new terminal. If it still fails, run the PATH line the installer printed.

ModuleNotFoundError: No module named 'pandas' inside a notebook, when uv run python -c "import pandas" works fine in the terminal, is the kernel mismatch above. Check sys.executable, switch kernels, re-run.

No solution found when resolving dependencies from uv add means no combination of versions satisfies everything at once. Read the next few lines; uv names the conflicting packages and the constraint that broke. Usually the culprit is requires-python in your pyproject.toml being wider than a package supports.

Try it

Build the project above from an empty folder, then break it on purpose and repair it.

  1. uv init --python 3.13 analytics, cd analytics, uv add pandas numpy, uv add --dev ipykernel.
  2. Register the kernel and start JupyterLab. Run the sys.executable cell and read the path out loud.
  3. Note the pandas version it printed. Quit the server.
  4. Delete the environment entirely: rm -rf .venv (Remove-Item -Recurse -Force .venv on Windows).
  5. Run uv sync --locked, then uv run python -c "import pandas; print(pandas.__version__)".

Success condition: step 5 prints the same pandas version as step 3, and it took under thirty seconds. If it printed a different version, you resolved instead of syncing; check that uv.lock is still there.

Common mistakes

  • Activating the venv and running pip install. It works, and it puts a package in your environment that is in no lockfile, so it vanishes on the next machine. Use uv add, always. uv run handles activation for you.
  • Installing packages from inside a notebook cell. The shell escape magic for pip has the same problem, and it frequently installs into a different environment than the kernel is using. Stop the server, run uv add in the terminal, start it again.
  • Assuming the notebook uses the environment your terminal is in. They are separate processes. The kernel picker decides, not your shell.
  • Committing .venv/ and ignoring uv.lock. This is exactly backwards, and it is the single most common reason a shared project will not run.

Where this goes next

Your environment is now boring and rebuildable, which is the only interesting thing about an environment. Next session, Python You Actually Need, covers variables, lists, functions and loops, taken only as far as data work requires, in the notebook you just got running.

That was one session of 6 in this phase.

Data Science runs to 4 phases. Buy the whole course, or just the phase you need.