Python Virtual Environments

Virtual environment creation and dependency management with venv, uv, and pip.

uv (Primary Tool)

Install uv — Rust-based Python package manager, 10-100x faster than pip
curl -LsSf https://astral.sh/uv/install.sh | sh
# or on Arch
pacman -S uv
uv init — create a new Python project with pyproject.toml
uv init my-network-tool
cd my-network-tool
# Creates: pyproject.toml, .python-version, README.md, src/my_network_tool/__init__.py
uv add — add dependencies, updates pyproject.toml and uv.lock automatically
uv add requests paramiko netmiko
uv add click rich                    # add more later
uv add --dev pytest ruff mypy       # dev-only dependencies
uv remove — remove a dependency
uv remove netmiko
uv sync — install all dependencies from uv.lock (deterministic, reproducible)
uv sync                  # install all deps including dev
uv sync --no-dev         # production only
uv run — execute a command inside the virtual environment without activating it
uv run python script.py
uv run pytest
uv run ruff check .
uv run mypy src/

# Run a script that needs dependencies you haven't added to the project
uv run --with httpx python -c "import httpx; print(httpx.get('https://example.com'))"
uv lock — regenerate the lockfile after manual pyproject.toml edits
uv lock
uv lock --upgrade           # upgrade all packages to latest compatible
uv lock --upgrade-package requests   # upgrade just one package
uv pip — drop-in pip replacement for when you need pip-style commands
uv pip install requests          # install into current venv
uv pip list                      # list installed packages
uv pip freeze > requirements.txt # export for legacy projects

.python-version

Pin Python version — uv reads this file to determine which Python to use
echo "3.12" > .python-version

# uv will download and use this version automatically
uv run python --version    # Python 3.12.x
Install a specific Python — uv manages Python installations too
uv python install 3.12
uv python install 3.11
uv python list              # show available versions

pyproject.toml

Anatomy of pyproject.toml — the modern Python project config file
[project]
name = "network-tool"
version = "0.1.0"
description = "Network automation scripts"
requires-python = ">=3.11"
dependencies = [
    "requests>=2.31",
    "paramiko>=3.4",
    "click>=8.1",
]

[project.scripts]
nettool = "network_tool.cli:main"   # creates a CLI command

[dependency-groups]
dev = [
    "pytest>=8.0",
    "ruff>=0.4",
    "mypy>=1.9",
]

[tool.ruff]
line-length = 100

[tool.mypy]
strict = true
project.scripts — make your tool installable as a CLI command
[project.scripts]
backup = "network_tool.backup:main"

# After uv sync, run it directly:
# $ backup sw1 sw2 sw3

venv (Standard Library)

Create and activate a venv — built into Python, no extra tools needed
python -m venv .venv
source .venv/bin/activate         # zsh/bash
# prompt changes to (.venv) $

deactivate                        # leave the venv
Why venv exists — isolate project dependencies from system Python
# Without venv: pip install requests puts it in /usr/lib/python3.x/
# With venv: pip install requests puts it in .venv/lib/python3.x/
# Each project gets its own dependency tree

pip (Legacy)

pip install — install packages from PyPI
pip install requests              # latest
pip install requests==2.31.0      # exact version
pip install "requests>=2.31,<3"   # version range
pip install -e .                  # editable install of current project
pip freeze and requirements.txt — snapshot current environment
pip freeze > requirements.txt     # export all installed packages with versions
pip install -r requirements.txt   # reproduce the environment
requirements.txt format — pin versions for reproducibility
requests==2.31.0
paramiko==3.4.0
click==8.1.7
netmiko==4.3.0
rich==13.7.0
Why uv over pip — speed and correctness
# pip install: resolves dependencies at install time, can conflict
# uv add:     resolves all dependencies together, writes lockfile
# uv sync:    installs from lockfile, deterministic and fast

# pip:  pip install requests  (3-10 seconds)
# uv:   uv add requests      (0.1-0.5 seconds)

Project Layout

Standard project structure — src layout with uv
my-network-tool/
├── .python-version          # "3.12"
├── pyproject.toml           # project metadata and dependencies
├── uv.lock                  # deterministic lockfile (commit this)
├── src/
│   └── network_tool/
│       ├── __init__.py
│       ├── cli.py           # click/argparse entry point
│       ├── backup.py        # backup logic
│       └── models.py        # dataclasses, types
├── tests/
│   ├── __init__.py
│   ├── test_backup.py
│   └── test_models.py
└── .gitignore
Essential .gitignore entries — never commit the venv or cache
.venv/
__pycache__/
*.pyc
dist/
*.egg-info/
.mypy_cache/
.ruff_cache/

Workflow Cheat Sheet

New project from scratch — uv handles everything
uv init my-tool && cd my-tool
uv add click requests
uv run python -c "import click; print(click.__version__)"
Clone and set up existing project — one command to install everything
git clone https://github.com/user/project.git
cd project
uv sync          # reads pyproject.toml + uv.lock, creates .venv, installs everything
uv run pytest    # run tests
Quick one-off script — run with dependencies without creating a project
uv run --with requests --with rich python quick_check.py
# Installs requests and rich into a temporary environment, runs the script