
pyenv
pyenv
Poetry
Poetry
uv
uv
pyproject.toml
pyproject.toml
Virtual Environment
Virtual Environment
Using just pip install leads to version conflicts, "works on my machine" problems, and no reproducible builds. Modern Python development requires environment isolation, version locking, and consistent tooling - ideally from a single, fast tool.
uv is now the recommended single tool for managing Python versions, virtual environments, and packages. Install it once, globally:
# Linux / macOS
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
After installation, uv handles everything - you don't need pyenv, pip, or venv separately.
Use uv to install and pin a Python version per project. This creates a .python-version file that is committed to git - every developer on the team uses the same version automatically:
# Install a specific Python version
uv python install 3.14.0
# Pin it for this project (creates .python-version)
uv python pin 3.14.0
# Verify
uv python list
If you still prefer pyenv for version management, uv respects .python-version files from pyenv smoothly.
uv init creates a pyproject.toml and uv.lock file. uv sync creates the virtual environment and installs all dependencies from the lockfile in one command:
# New project
uv init myapp
cd myapp
# Or sync an existing repo after cloning
uv sync
The virtualenv is created at .venv/ automatically. You never need to activate it manually when using uv run.
Always add dependencies through uv add - this updates both pyproject.toml and uv.lock atomically, ensuring the lockfile stays consistent:
# Add a runtime dependency
uv add fastapi uvicorn[standard]
# Add a dev-only dependency
uv add --dev pytest ruff ty
# Remove a package
uv remove httpx
Never run pip install directly in a uv-managed project - it bypasses the lock file.
uv run executes commands in the project's virtualenv without activating it. This is the standard way to run scripts, tests, and tools:
# Run your app
uv run python main.py
# Run tests
uv run pytest -v
# Run the linter
uv run ruff check .
# Run the type checker
uv run ty check
# Run a one-off script (auto-installs dependencies from inline script metadata)
uv run scripts/migrate.py
This pattern works identically in CI/CD without any activation step.
Enforce formatting and linting on every commit with pre-commit. Ruff handles both formatting (replacing Black) and linting (replacing flake8 + isort) in a single fast pass:
uv add --dev pre-commit
Create .pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
Install the hooks:
uv run pre-commit install
Now every git commit automatically runs Ruff format + lint with auto-fix.
This is what a well-structured pyproject.toml looks like for a real project - covers metadata, dependencies, dev dependencies, tool configuration for Ruff and ty, and test settings:
[project]
name = "myapp"
version = "1.0.0"
description = "Your project description"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.32",
"httpx>=0.28",
"pydantic>=2.10",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"ruff>=0.9",
"ty>=0.0.1a14",
"pre-commit>=4.0",
]
[project.scripts]
myapp = "myapp.main:app"
[tool.uv]
required-version = ">=0.6"
dev-dependencies = [
"pytest>=8.0",
"ruff>=0.9",
"ty>=0.0.1a14",
]
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "UP", "B", "I"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
[tool.ty]
# ty is the Astral type checker - fast, Pyright-based
python-version = "3.12"
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
| Tool | Best For | Verdict |
|---|---|---|
| venv | Python built-in, simple, reliable | Great for simple scripts and learning |
| Pipenv | Was popular but now slower, abandoned by many | Avoid for new projects |
| Poetry | Mature, excellent dependency resolution, good DX | Solid choice, especially for libraries |
| uv | Rust-based, 10-100x faster, replaces pip+venv+pyenv | Recommended for 2026+ projects |
Absolutely avoid sudo pip install or installing packages directly into system Python. Always, always use virtual environments - whether via uv venv, python -m venv, or Poetry.
| Feature | Poetry | uv |
|---|---|---|
| Speed | Good | Blazing fast (Rust, 10-100x) |
| Lock file | poetry.lock | uv.lock (cross-platform, exact) |
| Virtual env | Auto-managed | Auto-managed (.venv) |
| Python version mgmt | Delegates to pyenv | Built-in (uv python install) |
| Script runner | poetry run | uv run (+ inline script deps) |
| Build/package | poetry build / publish | uv build / publish |
| Maturity | Mature, stable (5+ years) | Rapidly evolving, Astral-backed |
What does `pyenv local 3.12` (or `uv python pin 3.12`) do?
Why should you never use `sudo pip install` for project packages?
The uv.lock file (or poetry.lock) is as important as your code. It pins every transitive dependency to exact versions, ensuring that uv sync on a CI server, a colleague's machine, or a production container installs the exact same packages.
Rules:
.gitignoreuv add, uv remove, or uv lock --upgrade-package <name>uv lock --upgrade upgrades all packages to their latest compatible versions, then review the diff# Upgrade a single package
uv lock --upgrade-package fastapi
# Upgrade all packages (then run tests!)
uv lock --upgrade
# Check what changed
git diff uv.lock
Next: Learn Python Fundamentals if you're new to Python, or explore Python Tricks & Patterns for practical patterns that save time.
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

The definitive production reference for Python asynchronous programming: event loop internals, structured concurrency with TaskGroup, free-threaded CPython 3.13, connection pooling, and profiling event loop starvation.
Read more
Master Python asyncio in 2026: Coroutines, TaskGroup vs gather, uvloop internals, preventing event loop starvation, and async semaphore pooling.
Read more
Master advanced mypy strict type checking patterns using ParamSpec, TypeVarTuple, Protocols, and custom type guards in large Python codebases.
Read more