Next-Gen Python Tooling with uv
One fast tool for Python versions, projects, packages, and virtual environments — reproducible by default.
Module 4 · OverviewBy the end of this module you can…
- Explain why “global Python” causes trouble, and what a virtual environment fixes
- Install
uvper-user, without admin rights - Install and switch Python versions with
uv python - Create a project with
uv initand read itspyproject.toml - Add packages, run code, and reproduce environments with
uv.lock - Launch Jupyter Lab inside a project environment
Why This Used to Hurt
Global Python, clashing packages, and “works on my machine”.
The old wayOne global Python for everything
- Download an installer, tick (or forget!) “Add python.exe to PATH”
pip install pandasinto the one shared Python- Project A needs
pandas 1.5; project B needspandas 2.2— only one can win - Upgrade Python → every package disappears
- Colleague installs “the same” packages → gets different versions → different numbers
If you can’t say exactly which package versions produced last quarter’s reserve numbers, you can’t reproduce them for an auditor.
The fixVirtual environments: one sandbox per project
.venv\Python 3.12
pandas 2.2 · numpy 2.1
.venv\Python 3.11
pandas 1.5 · openpyxl
.venv\Python 3.13
scipy · matplotlib
- A virtual environment is just a folder (
.venv) holding a Python interpreter link plus that project’s packages - Projects can’t break each other
- Disposable: delete
.venvand rebuild it in seconds
Historically you juggled python -m venv, activate, pip, pip freeze, pyenv… uv replaces the lot.
Meet uvOne tool instead of six
| Job | Old tool(s) | uv |
|---|---|---|
| Install Python itself | python.org installer, pyenv | uv python install |
| Create environments | venv, virtualenv | automatic |
| Install packages | pip | uv add |
| Pin exact versions | pip-tools, pip freeze | uv.lock |
| Run in the environment | activate + python | uv run |
| Global CLI tools | pipx | uv tool install |
Made by Astral, who also make the ruff linter we’ll use in Module 7.
Installing uv & Python
Per-user, no admin, no installer wizards.
InstallInstalling uv on Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
powershell
Start a helper PowerShell…-ExecutionPolicy ByPass
…allowed to run a script, for this one command onlyirm URL
Invoke-RestMethod: download the install script| iex
Invoke-Expression: run what was downloadedInstalls uv.exe to ~\.local\bin and adds that folder to your User PATH (Module 3!). Then: new terminal → uv --version.
irm … | iex runs code from the internet. Only do it for sources you trust — or use winget install --id astral-sh.uv -e instead.
uv pythonManaging Python runtimes
uv python list # available & installed versions
uv python install 3.12 # download a standalone Python
uv python install 3.13 3.11 # several at once
uv python pin 3.12 # this folder uses 3.12
# (writes .python-version)
uv python dir # where they're stored
uv run python --version # check what a project gets
| python.org installer | uv python | |
|---|---|---|
| Wizard | Yes | No |
| Admin rights | Often | Never |
| PATH edits | Checkbox roulette | None needed |
| Multiple versions | Painful | Trivial |
| Remove a version | Control Panel | uv python uninstall |
You rarely need to install Python explicitly — if a project asks for 3.12 and it’s missing, uv downloads it automatically.
Projects & Environments
uv init, uv add, uv run — and the lock file that makes it reproducible.
uv initStarting a project
PS C:\actuarial_work> uv init lapse_analytics
Initialized project `lapse-analytics`
at `C:\actuarial_work\lapse_analytics`
PS C:\actuarial_work> cd lapse_analytics
PS C:\actuarial_work\lapse_analytics> ls -Force
lapse_analytics\
├── .gitignore files Git should ignore
├── .python-version e.g. 3.12
├── main.py starter script
├── pyproject.toml the project's "passport"
└── README.md describe your project
If Git is installed, uv init also creates a Git repository (.git\). We’ll install Git in Module 5.
pyproject.tomlThe project’s passport
[project]
name = "lapse-analytics"
version = "0.1.0"
description = "Lapse experience study"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"numpy>=2.1.3",
"openpyxl>=3.1.5",
"pandas>=2.2.3",
]
[dependency-groups]
dev = [
"jupyterlab>=4.3.4",
]
- A plain-text, standard file (TOML format) — readable by humans and by every modern Python tool
requires-python: which Pythons are alloweddependencies: what the project needs — with minimum versionsdevgroup: tools for you, not for the analysis itself
Let uv add / uv remove edit this file for you.
uv addAdding packages
PS C:\actuarial_work\lapse_analytics> uv add pandas numpy openpyxl
Using CPython 3.12.8
Creating virtual environment at: .venv
Resolved 9 packages in 412ms
Prepared 7 packages in 1.93s
Installed 7 packages in 588ms
+ et-xmlfile==2.0.0
+ numpy==2.1.3
+ openpyxl==3.1.5
+ pandas==2.2.3
...
pyproject.toml & write uv.lock.venv\ (created if missing)Version numbers on these slides are illustrative — yours will be whatever is current.
uv runRunning code inside the environment
uv run python main.py # run a script
uv run main.py # same thing, shorter
uv run python # interactive Python
uv run python -c "import pandas; print(pandas.__version__)"
uv run checks the environment matches the lock file (fixing it if not), then runs your command inside .venv.
No “activate” step to forget. No accidentally running the wrong Python. The command is the same on every machine.
You can still activate the old way — .venv\Scripts\activate — which needs the execution policy from Module 3. Type deactivate to leave.
Reproducibilitypyproject.toml vs. uv.lock
pyproject.toml — what you asked for
pandas>=2.2.3
Loose ranges. Written by you (via uv add).
uv.lock — what you actually got
pandas==2.2.3 + every sub-dependency + file hashes
Exact, for every platform. Written by uv. Never edit by hand.
uv add …pyproject.toml + uv.lockuv sync → identical environmentCommit uv.lock. Never commit .venv\ — it’s rebuilt from the lock file (Module 5).
ReferenceThe commands you’ll actually use
| Command | Does |
|---|---|
uv init name | New project folder |
uv add pkg / uv add "pkg>=2,<3" | Add a dependency (optionally with a version range) |
uv add --dev pkg | Add a development-only tool |
uv remove pkg | Remove a dependency |
uv sync | Make .venv match uv.lock exactly |
uv lock --upgrade-package pandas | Deliberately move to a newer pandas |
uv tree | Show the dependency tree |
uv run cmd | Run anything inside the environment |
uv self update | Update uv itself |
JupyterJupyter Lab, the uv way
uv add --dev jupyterlab
uv run jupyter lab
- Jupyter is a dev dependency: you use it to explore; the analysis doesn’t need it to run
- Jupyter runs inside the project environment, so
import pandasjust works - It opens your browser at
http://localhost:8888/lab
The terminal is now busy running the Jupyter server. Leave it open while you work.
To stop: go back to the terminal and press Ctrl+C (Module 2’s emergency brake). Open a second tab (Ctrl+Shift+T) for other commands.
We’ll explore notebooks properly in Module 6.
Hands-on Lab · 45 minA reproducible analytics project
- Install
uv; confirm withuv --versionin a new terminal - Install Python 3.12 with
uv python install 3.12 - Create
C:\actuarial_work\lapse_analyticswithuv init - Add
pandas,numpy,openpyxl— andjupyterlabas a dev tool - Inspect
pyproject.tomland find pandas’ exact version inuv.lock - Write and run
check_env.py - Prove nothing leaked into the global system — then destroy and rebuild
.venv
Lab · Steps 1–6Build it
uv --version
uv python install 3.12
cd C:\actuarial_work
uv init lapse_analytics --python 3.12
cd lapse_analytics
uv add pandas numpy openpyxl
uv add --dev jupyterlab
cat pyproject.toml
Select-String 'name = "pandas"' uv.lock -Context 0,1
notepad check_env.py
uv run check_env.py
# check_env.py — which Python, which packages?
import sys
import numpy as np
import pandas as pd
print("Python :", sys.version.split()[0])
print("From :", sys.executable)
print("pandas :", pd.__version__)
print("numpy :", np.__version__)
# A tiny mortality table
df = pd.DataFrame({"age": [30, 40, 50],
"qx": [0.0008, 0.0016, 0.0041]})
df["px"] = 1 - df["qx"]
print(df)
Lab · Step 7Prove it’s isolated — and reproducible
Isolated
# Outside the project, pandas doesn't exist:
cd ~
uv run --no-project python -c "import pandas"
# ModuleNotFoundError: No module named 'pandas'
# Inside, it does — and sys.executable points into .venv:
cd C:\actuarial_work\lapse_analytics
uv run check_env.py
Reproducible
rm .venv -Recurse -Force # destroy the environment
uv sync # rebuild from uv.lock
uv run check_env.py # same versions, same output
The environment is disposable; the lock file is the source of truth. That’s the whole idea.
RecapModule 4 checklist
- I can explain why a shared global Python causes conflicts
- I know a virtual environment is just a disposable
.venvfolder uv --versionworks in a new terminal- I can install and pin Python versions with
uv python - I can create a project, add packages, and run scripts with
uv run - I can explain
pyproject.toml(what I asked for) vs.uv.lock(what I got) - I can launch Jupyter Lab with
uv run jupyter lab— and stop it with Ctrl+C
Source Control with Git
An infinite undo button for your projects — and the end of model_v2_final_FINAL.xlsx.