Filter concepts by levelShowing all levels.

Python · Dependency and Environment Management

Dependency and Environment Management

Isolating packages and Python versions, dev vs. production dependencies, secrets, and lock files — verified against real pip and uv runs

  • venv + version managerisolate packages, then isolate the interpreter itself
  • Secrets never in gitread from the environment, rotate if ever committed
  • Lock filesthe exact resolved version of every dependency, hashed
  • uva much faster, all-in-one alternative to pip + venv + lock

1.Virtual environments and Python version management

A virtual environment isolates a project's installed packages. A Python version manager goes one level further, isolating the interpreter itself, so one project can use 3.10 while another uses 3.13 on the same machine.

Key features

  • python -m venv .venv creates a package-isolated environment using whichever Python ran it
  • A venv does NOT change the Python version — it inherits the one that created it
  • A version manager installs and switches between multiple interpreter versions on a machine
  • uv python install 3.12 (or pyenv install 3.12) fetches a specific interpreter version
  • requires-python in pyproject.toml documents supported versions, but does not install one
Two separate concerns, two separate tools

Version manager

uv python install 3.12

fetches an interpreter build

Python 3.10, 3.12, 3.14

multiple versions, side by side

Virtual environment

uv venv --python 3.12

inherits the chosen interpreter

.venv/

isolated packages, this project only

  • Version manager — which interpreter exists at all
    • uv python install 3.12 — fetches an interpreter build
    • Python 3.10, 3.12, 3.14 — multiple versions, side by side
  • Virtual environment — which packages, for one project
    • uv venv --python 3.12 — inherits the chosen interpreter
    • .venv/ — isolated packages, this project only

Command

python
# venv: isolates PACKAGES for one Python version
python -m venv .venv

# version management: isolates the PYTHON VERSION itself
uv python install 3.12
uv venv --python 3.12

A venv isolates packages, inheriting whatever Python ran it; a version manager isolates the interpreter itself.

2.Development vs. production dependencies

Production dependencies are what the running application needs to actually work. Development dependencies (pytest, ruff, mypy) are only needed while building and testing it — production should never need to install them.

Key features

  • [project.optional-dependencies] dev = [...] in pyproject.toml declares dev-only packages
  • pip install ".[dev]" installs both production dependencies AND the dev extras
  • pip install . (no extras) installs only production dependencies
  • A production deployment should install without [dev] — smaller image, smaller surface
  • Common dev deps: pytest, ruff, mypy; production ones are the runtime libs the app imports

Where each kind belongs

Where each kind belongs
DependencyKindNeeded in production?
requestsproductionYes — the app imports and calls it
pytestdevNo — only runs during testing
ruffdevNo — only runs during linting, never at runtime
djangoproductionYes — the app cannot run without it
pip install . vs. pip install ".[dev]"

pip install .

  • Installs only [project.dependencies]
  • requests: yes — the app imports it
  • pytest, ruff: never installed

pip install ".[dev]"

  • Base dependencies PLUS the dev = [...] extra
  • requests AND pytest, ruff all present
  • Use for local development, never production images
  • pip install .
    • Installs only [project.dependencies]
    • requests: yes — the app imports it
    • pytest, ruff: never installed
  • pip install ".[dev]"
    • Base dependencies PLUS the dev = [...] extra
    • requests AND pytest, ruff all present
    • Use for local development, never production images

Command

python
# pyproject.toml
[project]
dependencies = ["requests>=2.31,<3.0"]

[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.5"]

# production install:
pip install .
# development install:
pip install ".[dev]"

Production dependencies are what the app needs; dev dependencies should never ship — [project.optional-dependencies] separates them.

3.Environment variables and .env files

os.environ.get("NAME") reads an environment variable set on the process. A .env file lists variables in NAME=value lines for local development — python-dotenv loads it into os.environ, since Python does not read it automatically.

Key features

  • os.environ.get("NAME") reads an environment variable, returning None if it is not set
  • os.environ.get("NAME", "default") supplies a fallback if the variable is missing
  • A .env file (NAME=value per line) is NOT read automatically — nothing built-in loads it
  • load_dotenv() from python-dotenv reads a .env file and copies its values into os.environ
  • .env should be in .gitignore — it typically holds local or secret configuration

Reading environment configuration

Reading environment configuration
CallBehavior
os.environ["NAME"]raises KeyError if NAME is not set
os.environ.get("NAME")returns None if NAME is not set
os.environ.get("NAME", "default")returns "default" if NAME is not set
load_dotenv()reads .env from the current directory (or a given path) into os.environ
.env is inert until load_dotenv() copies it in

.env file

DATABASE_URL=... on disk

load_dotenv()

parses and copies each line

os.environ

the real process environment

os.environ.get("NAME")

reads whatever is there NOW

  • .env file — DATABASE_URL=... on disk
    • leads to load_dotenv()
  • load_dotenv() — parses and copies each line
    • leads to os.environ
  • os.environ — the real process environment
    • leads to os.environ.get("NAME")
  • os.environ.get("NAME") — reads whatever is there NOW

Command

python
# .env
DATABASE_URL=postgresql://localhost/mydb
DEBUG=true

# app.py
from dotenv import load_dotenv
load_dotenv()
import os
db_url = os.environ.get("DATABASE_URL")

os.environ.get("NAME") reads real environment variables; a .env file needs load_dotenv() to actually load.

4.Secrets

A secret is a value (API key, password, key) that must never appear in source control. Environment variables are the most common way to hand one to a running app — set by the deploy platform, or locally via a gitignored .env.

Key features

  • A hardcoded secret in source ships to every clone of the repo, forever, in git history
  • os.environ.get("API_KEY") is the standard way app code receives a secret at runtime
  • A local .env file holding real secrets must be in .gitignore — never committed
  • A dedicated secret manager (Vault, AWS Secrets Manager) centralizes secrets for production
  • A leaked secret must be ROTATED, not just removed — the commit history still holds it

Where a secret should (and should not) live

Where a secret should (and should not) live
LocationSafe?
Hardcoded in a .py fileNo — ships in every clone, forever in git history
.env file, gitignoredOK for local development only
Environment variable set by the deploy platformYes — standard for production
A dedicated secret manager (Vault, AWS Secrets Manager)Yes — the production-grade approach, with rotation and access control
Where a secret should live — least to most safe

Hardcoded in a .py file

ships in every clone, forever in git history

.env file, gitignored

OK for local development only

Env var set by the deploy platform

standard for production

Dedicated secret manager

Vault, AWS Secrets Manager — rotation, access control

  1. Hardcoded in a .py file — ships in every clone, forever in git history
  2. .env file, gitignored — OK for local development only
  3. Env var set by the deploy platform — standard for production
  4. Dedicated secret manager — Vault, AWS Secrets Manager — rotation, access control

Command

python
import os

api_key = os.environ.get("STRIPE_API_KEY")
if api_key is None:
    raise RuntimeError("STRIPE_API_KEY is not set")

A secret must never appear in source control — read it from the environment, fail loudly if missing, rotate if leaked.

5.Lock files and reproducible environments

A lock file records the exact version — often with a hash — of every package actually installed, direct and transitive. Installing from it gives the same versions every time, on every machine, instead of whatever a range resolves to today.

Key features

  • uv lock resolves every dependency, direct and transitive, into a lock file
  • A lock file typically includes a hash per package, so an install verifies the download
  • uv sync installs from the lock file exactly, rather than re-resolving version ranges
  • A lock file should be committed — it makes "works on my machine" reproducible elsewhere
  • Re-running uv lock regenerates it against current ranges — deliberate, not automatic

Declared range vs. locked reality

Declared range vs. locked reality
FileHolds
pyproject.tomldependencies = ["requests>=2.31,<3.0"] — a RANGE
uv.lockrequests == 2.34.2, plus every transitive dependency's exact version and hash
A range resolves once, then locks to an exact version

pyproject.toml

requests>=2.31,<3.0 — a RANGE

uv lock

resolves every dep, direct + transitive

uv.lock

requests==2.34.2, exact version + hash

uv sync

installs precisely what is locked

  • pyproject.toml — requests>=2.31,<3.0 — a RANGE
    • leads to uv lock
  • uv lock — resolves every dep, direct + transitive
    • leads to uv.lock
  • uv.lock — requests==2.34.2, exact version + hash
    • leads to uv sync
  • uv sync — installs precisely what is locked

Command

python
uv lock          # resolve and write uv.lock
uv sync           # install EXACTLY what uv.lock specifies

A lock file records the exact resolved version and hash of every dependency — commit it for reproducible installs.

6.pip and uv

pip is Python's standard package installer, included with every Python install. uv is a much faster, newer tool that can install packages, create venvs, manage Python versions, and lock dependencies — often a drop-in pip replacement.

Key features

  • pip install <pkg> and uv pip install <pkg> both install a package — a compatible interface
  • uv venv creates a venv, and can also pick a specific Python version to use
  • uv is written in Rust and is measurably faster than pip at resolving and installing
  • uv lock / uv sync add lock-file-based reproducible installs, which plain pip lacks
  • pip is installed with Python by default; uv is a separate tool installed on its own

pip vs. uv, by capability

pip vs. uv, by capability
Capabilitypipuv
Install a packageYesYes — faster
Create a venvOnly with the separate venv moduleBuilt in (uv venv)
Manage Python versionsNoYes (uv python install)
Lock filesNo (needs pip-tools/Poetry)Built in (uv lock)
pip vs. uv

pip

  • Installed with Python by default — always available
  • Needs the separate venv module for environments
  • No built-in lock files — needs pip-tools/Poetry

uv

  • Written in Rust — measurably faster resolve/install
  • uv venv also manages Python versions directly
  • uv lock / uv sync built in for reproducible installs
  • pip
    • Installed with Python by default — always available
    • Needs the separate venv module for environments
    • No built-in lock files — needs pip-tools/Poetry
  • uv
    • Written in Rust — measurably faster resolve/install
    • uv venv also manages Python versions directly
    • uv lock / uv sync built in for reproducible installs

Command

python
# pip, the standard tool
python -m venv .venv
pip install requests

# uv, a faster all-in-one replacement
uv venv
uv pip install requests

pip is the standard installer; uv is much faster and also handles venvs, Python versions, and lock files.

7.Poetry and pip-tools

Poetry is an all-in-one dependency manager with its own conventions, a built-in lock file, and build/publish commands. pip-tools is lighter — it just compiles a pinned requirements.txt from a looser requirements.in.

Key features

  • Poetry uses [tool.poetry] (or newer [project]) sections in pyproject.toml, and poetry.lock
  • poetry add requests adds a dependency and updates the lock file in one step
  • pip-tools' pip-compile turns a loose requirements.in into a fully pinned requirements.txt
  • pip-tools stays close to plain pip — output is a normal requirements.txt file
  • Both predate uv, which covers similar ground faster, but stay common in existing projects

Command

python
# Poetry
poetry init
poetry add requests
poetry install

# pip-tools
echo "requests>=2.31" > requirements.in
pip-compile requirements.in   # writes requirements.txt, fully pinned
pip install -r requirements.txt

Poetry is an all-in-one manager with its own lock file; pip-tools just compiles a pinned requirements.txt.

8.Conda and pyenv (when relevant)

Conda is a package/environment manager that also handles non-Python dependencies (C libraries, compiled scientific packages) — common in data science. pyenv manages multiple Python interpreter versions on one machine.

Key features

  • Conda environments bundle a Python version AND non-Python system dependencies together
  • Conda is standard for compiled scientific libraries with tricky system dependencies
  • pyenv install 3.12 / pyenv global 3.12 switches the default Python (macOS/Linux)
  • pyenv-win is the Windows equivalent, since pyenv itself does not support Windows directly
  • Neither tool is required for typical backend work — uv or pip + venv covers most projects

Command

python
# Conda
conda create -n myenv python=3.12
conda activate myenv
conda install numpy

# pyenv (macOS/Linux)
pyenv install 3.12
pyenv global 3.12

Conda manages Python plus non-Python system dependencies; pyenv manages multiple Python interpreter versions.

Remember

  • A venv isolates packages, inheriting whatever Python ran it; a version manager isolates the interpreter itself.
  • Production dependencies are what the running app needs; dev dependencies (tests, linters) should never ship.
  • A .env file needs python-dotenv's load_dotenv() to actually load — nothing built-in reads it automatically.
  • A secret must never appear in source control — read it from the environment, and rotate any that was ever committed.

Pro tip

  • A lock file records the exact resolved version and hash of every dependency — commit it for an application.
  • uv is a drop-in-compatible, much faster alternative to pip that also handles venvs, versions, and locking.
  • Poetry is all-in-one with its own lock file; pip-tools just adds a pinning step on top of plain pip.
  • Conda and pyenv are "when relevant" tools — most backend work does not need either.

Next up

  • Code Quality and Tooling

Sources

  • uv — docs.astral.sh/uv
  • Every pip/uv command run for real with pip 26.2.1, uv 0.11.15 · checked 2026-08-21

Spotted a mistake on this page? Tell us.