Skip to main content

🧰 Python Package Management with pip

PyPI hosts over 600,000 packages β€” a web framework, a data library, an HTTP client for almost anything you can name. pip is the tool that fetches, installs, and pins them. Master it and you rarely reinvent the wheel; misuse it and you invite conflicts and security holes.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what PyPI and pip are and how they work together
  • Install, upgrade, inspect, and uninstall packages with the core pip commands
  • Read and write version specifiers (==, >=, ~=) correctly
  • Manage a project with requirements.txt and layered requirement files
  • Reduce supply-chain risk with pinning, hashes, and vulnerability scanning

Estimated Time: 35–45 minutes  β€’  Difficulty: Beginner–Intermediate

Hands-on: Set up a small Flask project with layered, pinned, security-scanned dependencies.

In This Lesson

pip and PyPI

pip (the "Pip Installs Packages" installer) is the standard tool for adding third-party code to a Python project. It downloads packages from PyPI β€” the Python Package Index at pypi.org β€” the public "app store" for Python libraries, hosting well over 600,000 projects.

flowchart LR Dev[You] -->|pip install| Pip[pip] Pip -->|downloads from| PyPI[(PyPI
600k+ packages)] Pip -->|installs into| Env[.venv site-packages] Pip -->|reads / writes| Req[requirements.txt]

πŸ“– Key Terms

Package: reusable Python code published so others can install it (e.g. requests, django).

Wheel (.whl): a pre-built distribution format that installs fast with no compilation step.

Dependency: a package your package needs. pip installs these automatically.

pip ships with Python (3.4+), so it's almost always already there. Always run it against an activated virtual environment (previous lesson) so installs stay isolated. Verify and update it with:

# Check pip's version (both forms work)
pip --version
python -m pip --version

# Keep pip itself up to date
python -m pip install --upgrade pip

πŸ’‘ python -m pip vs bare pip

Prefer python -m pip .... It guarantees you're using the pip that belongs to that specific Python, which avoids the classic confusion of installing into one interpreter while running another.

Essential pip Commands

A handful of commands cover almost all day-to-day work.

Installing

# Install the latest version
pip install requests

# Install an exact version
pip install requests==2.31.0

# Install within a range
pip install "requests>=2.28,<3.0"

# Install everything a project needs
pip install -r requirements.txt

# Install your own project in "editable" mode (changes take effect live)
pip install -e .

Inspecting

pip list                 # everything installed
pip show requests        # details about one package
pip list --outdated      # what has newer versions available
pip freeze               # installed packages in requirements format

Uninstalling

pip uninstall requests

⚠️ pip search is gone

The old pip search command was disabled because it overloaded PyPI's servers. To find packages, browse pypi.org directly. To list available versions of a known package, use pip index versions requests.

Version Specifiers

When you declare a dependency, you also declare which versions are acceptable. Getting this right is the difference between "works on my machine" and reproducible builds.

OperatorExampleMeaning
==requests==2.31.0Exactly this version
>=requests>=2.31.0This version or newer
<requests<3.0.0Anything below 3.0.0
!=requests!=2.30.0Any version except this one
~=requests~=2.31.0"Compatible release": >=2.31.0, <2.32.0
combinedrequests>=2.28,<3.0At least 2.28 but below 3.0

πŸ’‘ Rule of thumb

For applications you deploy, pin exact versions (or use a lock file) so production matches development. For libraries other people install, use ranges (>=, ~=) so your library plays nicely alongside their other dependencies.

Dependency Resolution

When you install a package, pip also installs everything it depends on β€” and everything those depend on. Since pip 20.3, a stricter resolver checks that the whole tree is mutually compatible, and reports an error if no combination works.

flowchart TD App[Your App] --> A[Package A
needs C>=1.0,<2.0] App --> B[Package B
needs C>=1.5] A --> C[Package C] B --> C

Here both A and B depend on C. The resolver looks for a version of C satisfying both constraints β€” for example C 1.6. If A demanded C<1.5 while B demanded C>=1.5, no version could satisfy both and pip would report a conflict.

βœ… Avoiding "dependency hell"

  • Always work inside a virtual environment so each project resolves independently.
  • Pin versions for deployments; use ranges for libraries.
  • Update dependencies regularly so you take small steps, not scary version leaps.
  • For complex projects, let a lock file (Poetry, uv, pip-tools) resolve and record the full tree.

Managing requirements.txt

A requirements.txt file lists a project's dependencies so anyone can install them in one command and get the same environment.

# Snapshot the current environment (all packages, exact versions)
pip freeze > requirements.txt

# Recreate the environment elsewhere
pip install -r requirements.txt

For most applications, a hand-maintained file of direct dependencies is clearer than a raw freeze. You can also comment it:

# requirements.txt
Django>=4.2,<5.0
requests==2.31.0
python-dotenv>=1.0.0
psycopg2-binary==2.9.9   # PostgreSQL driver

Layered requirement files

Real projects separate concerns: shared deps, plus extras for development and production. Use -r to include one file from another.

# requirements-base.txt  β€” needed everywhere
Django>=4.2,<5.0
requests>=2.31.0
python-dotenv>=1.0.0

# requirements-dev.txt    β€” local development only
-r requirements-base.txt
pytest>=7.3
black>=23.3
ruff>=0.1

# requirements-prod.txt   β€” production only
-r requirements-base.txt
gunicorn>=21.2
psycopg2-binary==2.9.9
# Developers install:
pip install -r requirements-dev.txt
# The production server installs:
pip install -r requirements-prod.txt

πŸ’‘ pip-tools and uv make this rigorous

Write only your top-level wants in a requirements.in, then compile a fully-pinned, hashed requirements.txt:

# with pip-tools
pip install pip-tools
pip-compile requirements.in            # -> pinned requirements.txt
pip-sync requirements.txt              # make the env match exactly

# uv does the same, much faster
uv pip compile requirements.in -o requirements.txt

Supply-Chain Security

Installing a package runs someone else's code with your permissions. That convenience is also a risk vector, so treat dependencies with care.

⚠️ Common threats

  • Typosquatting: malicious packages with names close to popular ones (reqeusts, python-dotenv-). Double-check the exact name.
  • Compromised maintainers: a legitimate package's account is hijacked and a bad release published.
  • Dependency confusion: a public package is uploaded with the same name as your internal one.
  • Known vulnerabilities: a dependency has a published CVE you haven't patched.

Defenses

  • Pin versions in production so a surprise release can't slip in unnoticed.
  • Scan for vulnerabilities regularly with pip-audit.
  • Verify integrity with hashes so a tampered file is rejected at install time.
  • Avoid sudo pip β€” install into a virtual environment, not system Python.
# Scan installed packages for known vulnerabilities
pip install pip-audit
pip-audit

# Or scan a requirements file
pip-audit -r requirements.txt

Hash-checking mode makes pip refuse any file whose contents don't match the recorded hash:

# requirements.txt with a pinned hash
requests==2.31.0 \
    --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1

Tools like pip-compile --generate-hashes or uv produce these files for you.

Hands-on Exercise

πŸ‹οΈ Set up a project with layered, secured dependencies

Objective: Build a small Flask project the professional way β€” isolated environment, layered requirement files, pinned versions, and a security scan.

Instructions

  1. Create and activate a virtual environment.
  2. Install Flask, requests, and python-dotenv.
  3. Write layered requirement files (base / dev / prod).
  4. Freeze a fully-pinned lock file and run a vulnerability scan.
mkdir weather_app && cd weather_app
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# Install direct dependencies
pip install flask requests python-dotenv
# requirements-base.txt
flask>=3.0,<4.0
requests>=2.31,<3.0
python-dotenv>=1.0,<2.0

# requirements-dev.txt
-r requirements-base.txt
pytest>=7.3
ruff>=0.1

# requirements-prod.txt
-r requirements-base.txt
gunicorn>=21.2
# Produce a fully-pinned snapshot and scan it
pip freeze > requirements-lock.txt
pip install pip-audit
pip-audit -r requirements-lock.txt
πŸ’‘ Hint

Add a .gitignore containing .venv/, __pycache__/, and .env so you never commit the environment or secrets. Commit the requirement files, not the .venv folder.

βœ… What success looks like

pip list shows Flask, requests, and python-dotenv plus their dependencies. pip-audit reports "No known vulnerabilities found" (or names any it finds so you can bump the version). You now have a base/dev/prod split and a pinned lock file β€” exactly how production teams manage dependencies.

🎯 Quick Quiz

Question 1: What does requests~=2.31.0 allow?

Question 2: Which command captures the current environment's packages into a file you can reinstall from?

Question 3: Which practice best reduces supply-chain risk from your dependencies?

Best Practices

βœ… Do

  • Run pip inside an activated virtual environment, and prefer python -m pip.
  • Pin versions (or use a lock file) for anything you deploy.
  • Keep layered requirement files (base/dev/prod) and commit them to version control.
  • Run pip-audit in CI so vulnerabilities fail the build.
  • Update dependencies in small, regular steps.

⚠️ Don't

  • Don't sudo pip install into system Python β€” it risks your OS and skips isolation.
  • Don't leave dependencies unpinned in production; a surprise release can break or compromise you.
  • Don't blindly copy install commands β€” verify the exact package name to dodge typosquats.
  • Don't commit your .venv folder or .env secrets.

πŸ’‘ Beyond pip

For larger projects, higher-level tools build on the same foundation: Poetry (dependency resolution, lock files, packaging), pip-tools (compile pinned/hashed files), and uv (a Rust-based, drop-in-fast replacement for pip and venv). Learn pip first β€” the concepts transfer directly to all of them.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • pip installs packages from PyPI into your active environment; prefer python -m pip.
  • Version specifiers (==, >=, ~=) control which versions are acceptable β€” pin apps, range libraries.
  • pip's resolver installs the whole dependency tree and flags conflicts it can't satisfy.
  • requirements.txt (ideally layered base/dev/prod) makes environments reproducible.
  • Guard the supply chain: pin versions, verify hashes, and scan with pip-audit.

πŸ“š Further Reading

πŸš€ What's Next?

You've now covered Python's backend landscape, isolated environments, and dependency management. Next we cross into a different ecosystem with the same fundamentals: PHP for Backend Development.

πŸŽ‰ Nicely done!

Environments plus disciplined package management is the professional Python baseline β€” you've got both.