Programming

Python Environments: venv vs Poetry

·2 min read

Python's packaging story has stabilized in the last few years. There is no single answer to "how should I manage dependencies," but there are three reasonable answers, and a small number of mistakes that consistently waste an afternoon. This is what to use and why.

venv: the standard library answer

Since Python 3.3, venv ships with the interpreter. It creates a directory with its own python binary and site-packages. Activate it, and pip install writes there instead of system Python.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
deactivate

This is fine for solo scripts and small projects. The pain shows up when you want a lockfile, deterministic builds, or one tool that publishes to PyPI.

pip-tools: the minimal upgrade

If you like venv but want a lockfile, pip-tools gives you pip-compile. You write a requirements.in with top-level deps; pip-compile resolves and pins the full tree into requirements.txt.

# requirements.in
fastapi
sqlalchemy[asyncio]
pytest

# Compile
pip-compile requirements.in
pip-sync requirements.txt

Poetry: dependencies + packaging + publish

Poetry is a single tool for dependency management, virtual environment management, and PyPI publishing. Its pyproject.toml + poetry.lock pair is the closest Python comes to Cargo.toml + Cargo.lock.

poetry new my-project
cd my-project
poetry add fastapi sqlalchemy
poetry add --group dev pytest ruff
poetry install
poetry run pytest
poetry shell

The killer feature is the resolver: Poetry will refuse to install a dependency set that can't be resolved, instead of silently picking an old version like raw pip does.

uv: the fast newcomer

Astral's uv (the same people who make ruff) is a Rust-written drop-in replacement for pip + venv + pip-tools. It's 10-100× faster and has been stable enough for production for over a year now. If you're starting fresh and don't need Poetry's publishing workflow, uv is the boring default.

uv venv
uv pip install -r requirements.txt
uv pip compile requirements.in -o requirements.txt

Per-project Python versions

pyenv manages multiple Python interpreters, with a .python-version file selecting one per directory. Combine with uv or Poetry and you have a clean per-project setup.

The mistakes to avoid

  • Don't pip install into system Python. Most distros now block this with PEP 668. Use pipx for CLI tools, venv for everything else.
  • Don't commit your .venv/. Add it to .gitignore like you would node_modules/.
  • Don't trust pip freeze as a lockfile. It captures the resolved tree but not the constraints — re-running pip install -r from a frozen file is reproducible only on the same Python version with the same wheels available.

Related