Skip to content

Lesson 4 of 9 · 20m

Virtual Environments and pyproject.toml

Why every project gets its own venv, what pyproject.toml replaced, and how a real project is laid out.

Every Python developer's first production bug is a dependency conflict: Project A needs requests==2.28, Project B needs requests==2.31, and installing one breaks the other because they share a global site-packages. A virtual environment is a per-project Python install — its own interpreter, its own packages. Nothing outside it, nothing shared.

The workflow everyone actually uses

python -m venv .venv        # create the isolated environment
source .venv/bin/activate   # enter it (Windows: .venv\Scriptsctivate)
pip install requests        # installs INTO .venv, not globally
python -c "import requests" # uses the venv's copy
deactivate                  # leave

.venv/ goes in .gitignore — it is a local artifact, reproducible from your dependency list. The project travels; the environment is rebuilt on each machine.

pyproject.toml is the project's file

[project]
name = "sudosec-tools"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "requests>=2.31,<3",
    "rich>=13",
]

[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]

One file now holds everything setup.py, requirements.txt, setup.cfg, and MANIFEST.in used to scatter across. pip install -e ".[dev]" installs your package in editable mode plus the dev tools — the standard first command on any clone.

A project that scales past one file

mytool/
  pyproject.toml
  README.md
  src/mytool/__init__.py
  src/mytool/cli.py
  tests/test_mytool.py

The src/ layout forces you to test the installed package, not whatever happens to be in the working directory — the classic "works on my machine" bug where a dev accidentally imports their uninstalled local copy.

Try it yourself

// The pyproject idea in JS terms — one manifest for deps + dev deps:
const project = { deps: ["requests>=2.31"], dev: ["pytest", "ruff", "mypy"] };
console.log("One file:", Object.keys(project).join(", "));

The rule

One venv per project, src/ layout, pyproject.toml as the single manifest. Anything else is debt.