New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

The Ecosystem & Tooling

You can write Python forever with nothing but the standard library, but the moment you want to talk to a web API, parse a spreadsheet, or run a test suite, you'll reach for code other people wrote - and that's where tooling matters as much as the language itself.

This phase is the everyday toolbox: installing packages, keeping them from turning your machine into a junk drawer, and the three tools (formatter, linter, test runner) that separate "a script that works on my laptop" from "a project a team can live in."

pip - the package installer

What it actually is. pip is Python's package installer: it downloads libraries from PyPI (the Python Package Index, a giant public repository) and makes them importable in your code.

📝 Package - a reusable library someone published (e.g. requests for HTTP, rich for pretty terminal output). PyPI - the official place pip downloads them from.

$ python -m pip install requests
Collecting requests
  Downloading requests-2.32.3-py3-none-any.whl (64 kB)
Installing collected packages: requests
Successfully installed requests-2.32.3

What just happened: pip fetched requests (and its dependencies) from PyPI and installed it where Python can find it - now import requests works. python -m pip, not bare pip, guarantees you're using the pip belonging to this Python, not another one lurking on your PATH.

Virtual environments - one isolated box per project

The problem it solves. Say project A needs requests 2.20 and project B needs 2.32. Install packages globally and the two fight over one shared pile of libraries - upgrading one silently breaks the other. This is "dependency hell," exactly as fun as it sounds.

What it actually is. A virtual environment (venv) is a private, throwaway folder holding its own copy of Python and its own packages, isolated from every other project and the system Python. Each project gets its own box; what you install in one can't touch another.

$ python -m venv .venv
$ source .venv/bin/activate        # macOS/Linux
(.venv) $ python -m pip install requests

On Windows the activation line is different:

> python -m venv .venv
> .venv\Scripts\activate
(.venv) > python -m pip install requests

What just happened: python -m venv .venv created a folder containing a fresh, empty Python. activate switched your shell to use it - the (.venv) prompt prefix is your sign you're inside the box. Now pip install drops packages into .venv only; delete the folder and the environment is gone, harming nothing else. To leave, run deactivate.

💡 Key point. Make a virtual environment for every project, the moment you start it - the single habit that prevents the most common, most baffling "it worked yesterday" failures. The .venv folder is disposable - never commit it to Git.

⚠️ Gotcha - installing into the wrong place. Forget to activate before pip install, and the package lands in your global Python (or fails with a permissions error) - your project won't see it. Check the (.venv) prefix: no prefix, no isolation.

Recording dependencies - requirements.txt and pyproject.toml

The problem it solves. Your .venv lives only on your machine, but a teammate (or server, or future-you on a new laptop) needs to recreate the same set of packages - so you record them in a file.

requirements.txt - the simple, classic format: one package per line, usually with a pinned version.

$ python -m pip freeze > requirements.txt
$ cat requirements.txt
certifi==2024.7.4
charset-normalizer==3.3.2
idna==3.7
requests==2.32.3
urllib3==2.2.2

What just happened: pip freeze printed every installed package with its exact version, and > saved that list to requirements.txt. Anyone can recreate your environment with pip install -r requirements.txt and get identical versions - no guessing, no drift.

pyproject.toml - the modern, richer format: a single config file, defined by a Python standard, that describes your project and its dependencies (and configures your tools, below). A minimal one:

[project]
name = "my-app"
version = "0.1.0"
dependencies = [
    "requests>=2.32",
]

What just happened: This declares the project's name, version, and its dependency on requests 2.32+. Modern tooling reads this one file instead of scattering config across many. requirements.txt is fine for a first project; pyproject.toml is where things head as it grows.

📝 Pinning - recording an exact version (requests==2.32.3) so installs are reproducible; >=2.32 is a looser range that picks up newer compatible releases. Pin for apps you deploy; ranges are common for libraries.

📝 You'll increasingly see uv. By 2026 many teams reach for uv, a single fast tool that bundles the install/venv/lockfile steps above (it replaces pip and venv with uv add, uv run, and a uv.lock). Learn pip and venv first - they're the universal baseline every Python install has - then uv is an easy step up once these click.

The quality tools - black, ruff, pytest at a glance

These three show up on almost every serious Python project. Install them into your venv (pip install black ruff pytest).

black - the formatter

What it actually is. black is an opinionated code formatter: it rewrites your file into one consistent style - spacing, line breaks, quotes - so nobody on the team argues about formatting again. It makes the decisions; you stop thinking about them.

$ black app.py
reformatted app.py

All done! ✨ 🍰 ✨
1 file reformatted.

What just happened: black rewrote app.py in place to match its canonical style - messy indentation and inconsistent quotes became uniform. You don't configure much; that's the point, its whole pitch is "no options to bikeshed over."

ruff - the linter

What it actually is. A linter reads your code without running it and flags likely problems: unused imports, undefined names, suspicious patterns. ruff is a very fast one, now the common choice.

$ ruff check app.py
app.py:1:8: F401 [*] `os` imported but unused
app.py:14:5: F821 Undefined name `reqeusts`
Found 2 errors.

What just happened: ruff caught two things before running the code: an unused import os, and a typo'd reqeusts (should be requests) that would've crashed at runtime - instant feedback instead of a confusing traceback later.

📝 Formatter vs. linter - a formatter changes how code looks (style); a linter warns about what code does (likely bugs and smells). Complementary, not competing.

pytest - the test runner

What it actually is. pytest runs your tests: write plain functions named test_* that assert what should be true, and pytest finds, runs, and reports pass/fail on them. (The why of testing is its own topic - see Why Test At All.)

# test_math.py
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
$ pytest
========================= test session starts =========================
collected 1 item

test_math.py .                                                  [100%]

========================== 1 passed in 0.01s ==========================

What just happened: pytest discovered test_add (it looks for test_* functions automatically), ran it, and the asserts held, printing a green dot and 1 passed. Break the math and that dot becomes an F with a clear diff of expected-vs-actual. No boilerplate test class required; plain functions and assert are enough.

Recap

  1. pip installs packages from PyPI; prefer python -m pip so you hit the right Python.
  2. A virtual environment (python -m venv .venv + activate) gives each project its own isolated box of packages - make one per project, every time, and never commit .venv.
  3. requirements.txt (pip freeze) and pyproject.toml record your dependencies so anyone can recreate the environment.
  4. black formats, ruff lints, pytest runs tests - the everyday trio that keeps a project clean, correct, and pleasant to work in.

Next: the part that makes code feel Pythonic - the idioms locals use, and the gotchas that bite everyone exactly once.


← Phase 7: Errors & I/O · Guide overview · Phase 9: Idioms & Common Gotchas →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. What is pip?

2. What problem does a virtual environment solve?

3. What does each of black, ruff, and pytest do?