Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 47 of 898 min

Pinning dependencies, so it still runs next year

The file that makes a project reproducible

# requirements.txt
requests==2.32.3
pandas==2.2.2
python-dotenv==1.0.1
bash
python3 -m pip install -r requirements.txt

Without this file, "install the packages" means whatever versions happen to be current the day somebody tries, which is not the same set you tested against.

Exact pins, and the ranges you will see

requests==2.32.3        # exactly this
requests>=2.31          # this or newer — resolves differently over time
requests~=2.32.0        # 2.32.x but not 2.33 — compatible-release
requests!=2.30.0        # anything but the broken one

The convention worth following: applications pin exactly, libraries specify ranges. An application is installed once into a known environment and you want that environment identical everywhere. A library is installed alongside other people's dependencies, and pinning exactly guarantees a conflict with somebody.

pip freeze is not the same as your requirements

bash
python3 -m pip freeze > requirements.txt

This writes every package in the environment, including the ones installed as dependencies of your dependencies — typically 40 lines when you asked for four things. It is a snapshot, not a statement of intent, and six months later nobody can tell which of the 40 you actually need.

The maintainable arrangement is two files: requirements.in listing what you asked for, and a generated requirements.txt with everything pinned. pip-tools (pip-compile) does the generating; uv pip compile does the same thing much faster. poetry and pdm bundle the same idea with project management.

Any of these is fine. Having neither, and a hand-edited freeze, is what breaks.

Transitive dependencies are the ones that bite

You installed pandas. pandas needs numpy. You never named numpy, and a numpy release can still break your program. This is why a lock file records the whole resolved tree, not only your direct choices.

bash
python3 -m pip list --outdated
python3 -m pip show pandas          # what it requires, and what requires it

Upgrading deliberately

bash
python3 -m pip install --upgrade pandas
pytest

Upgrade one thing, run the tests, commit. Upgrading everything at once and finding a failure gives you a bisect over 40 packages. This is the practical payoff of the testing module: a test suite is what makes a dependency upgrade a five-minute job instead of a day of manual checking.

Two commands never to run

bash
sudo pip install requests
pip install --break-system-packages requests

Installing into the system Python can break tools your operating system depends on, and on some distributions the package manager will overwrite what you did anyway. Use a virtual environment, always, even for a one-file script. The previous lesson covers creating one; this is why the rule has no exceptions.

Security, briefly and honestly

bash
python3 -m pip install pip-audit
pip-audit

pip-audit checks your installed versions against a database of known vulnerabilities. It is free, takes seconds, and belongs in CI.

Two risks worth naming. Typosquatting: a package named requsts or python-dateutil2 that exists only to run code on installation. Check the name character by character against the documentation, and look at the project's page before installing something you have not used. A compromised release of a legitimate package, which is rarer and which pinning protects you from until you upgrade — another reason not to upgrade everything blindly.

Installing a package runs code from the internet on your machine, with your permissions. That is not a reason to avoid packages; it is a reason to know what you are installing and to keep the list short.

What breaks two years later, even with pins

Honesty about the limits. A pinned requirements.txt still fails to install eventually, for reasons outside your control:

  • an old release withdrawn from the index
  • no prebuilt wheel for your newer Python, so it tries to compile and needs a toolchain you do not have
  • a transitive dependency that was never pinned resolving to something incompatible

For anything that genuinely must run identically in five years, the answer is a container image or a vendored copy of the packages, not a text file of versions. A requirements.txt gets you months to a couple of years of reproducibility, reliably. It does not get you a decade, and anyone who says otherwise has not tried.

The minimum for any project you share

Four lines in the README: create a virtual environment, activate it, install from requirements, run this command. Plus the Python version you developed against. That is enough for somebody else to get your code running without asking you a question, which is the whole point.

The one thing to keep

Applications pin exact versions and libraries give ranges, and a `pip freeze` snapshot is not the same as a record of what you actually asked for.

Before you move on

A project's requirements.txt says `pandas>=2.0`. It installed cleanly in March and a fresh install in September fails inside code nobody changed. What is the most likely explanation?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly