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