Installing packages with pip and pinning requirements
With an environment active, installing packages is one command. Doing it so that your project still works in six months, on somebody else's machine, takes a little more.
pip
python -m pip install requests
python -m pip install requests pandas
python -m pip install "django>=5.0,<6.0"
python -m pip install --upgrade requests
python -m pip uninstall requests
python -m pip rather than bare pip, for the reason in the last lesson: it
installs into the Python you just named.
python -m pip list
python -m pip show requests
show gives the version, the location, and — usefully — what depends on it and
what it requires.
Where packages come from
PyPI, the Python Package Index, at pypi.org. Anyone can publish to it, which is worth remembering.
Check before installing something unfamiliar. Typosquatting is real:
packages named requets or python-dateutil2 exist to catch mistyped installs,
and some have shipped malware. A minute on the PyPI page tells you a lot —
recent releases, a linked repository, download numbers that match its
reputation.
For anything widely used — requests, pandas, django — you are fine. The
risk is in obscure packages and typos.
Pinning versions
This is the part that matters.
python -m pip install requests
That installs whatever is current today. A colleague running the same command next month gets a different version, and possibly different behaviour.
Record what you actually used:
python -m pip freeze > requirements.txt
certifi==2026.6.15
charset-normalizer==3.4.1
idna==3.10
requests==2.32.3
urllib3==2.3.0
Exact versions, including the packages your packages needed. Anyone can now reproduce your environment:
python -m pip install -r requirements.txt
Commit requirements.txt. Never commit .venv/. The file is the recipe;
the folder is the meal.
Two kinds of requirements file
pip freeze captures everything, which is right for deploying and heavy-handed
for describing a project. You cannot tell from that list which packages you
actually asked for — certifi and idna are there because requests needed
them.
A common approach is to keep two files. requirements.in holds what you chose:
requests
pandas
and requirements.txt is the full pinned output generated from it. Tools like
pip-tools or uv do that generation.
For a small project, one hand-maintained file with loose constraints is fine:
requests>=2.32,<3.0
pandas>=2.0
>=2.32,<3.0 accepts bug fixes and refuses the next major version — which, by
the convention most packages follow, is where breaking changes land.
What not to do is leave it unpinned entirely and hope. A build that worked last week and fails today, with no change on your side, is almost always an unpinned dependency that released a new version.
Development dependencies
Testing and linting tools are needed to work on a project, not to run it:
# requirements-dev.txt
-r requirements.txt
pytest
ruff
mypy
The -r requirements.txt line includes the main file, so developers install one
thing and get both. Production installs only requirements.txt — smaller,
faster, less to go wrong.
pyproject.toml
Modern projects increasingly declare dependencies in pyproject.toml instead:
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests>=2.32,<3.0",
]
[project.optional-dependencies]
dev = ["pytest", "ruff"]
python -m pip install -e .
python -m pip install -e ".[dev]"
-e installs your project in editable mode, so your own code is importable from
anywhere in the project while still being edited in place.
You will meet both. requirements.txt is everywhere and simple;
pyproject.toml is the direction things are moving and holds tool
configuration too. Either is fine — recognising both matters more.
Reading a version number
2.32.3 is major, minor, patch, by the semantic versioning convention:
- Patch — bug fixes, safe to take
- Minor — new features, backwards compatible
- Major — breaking changes; read the notes before upgrading
The convention is widely followed and not guaranteed. Pinning is what protects you either way.
Keeping up to date
python -m pip list --outdated
Upgrading has a real cost, and so does not upgrading — old dependencies accumulate security problems and eventually become painful to move off. A reasonable habit: review monthly, take patch updates readily, read release notes for major ones, and do it when you have time to test rather than the day before a deadline.
Never upgrade everything at once on a project you cannot test.
When install fails
error: externally-managed-environment on Linux means you are trying to
install into the system Python, which your distribution protects. The fix is a
virtual environment — this is the operating system telling you to do the right
thing.
A compiler error during install usually means a package with C extensions has no prebuilt wheel for your platform or Python version. Often the Python version is too new; check what the package supports.
Could not find a version that satisfies the requirement — a typo in the
name, or the package genuinely does not support your Python version.
Permission errors mean you are installing globally without permission. The
answer is a virtual environment, not sudo pip. Running sudo pip can damage
the Python your operating system depends on.
Practice
- In a fresh environment, install
requestsand confirm the import works. - Run
pip show requestsand note what it requires. - Run
pip freezeand see the packages you did not ask for. - Write
requirements.txt, delete the environment, rebuild it from the file, and confirm everything works. - Install a specific older version with
==, then upgrade it and comparepip listbefore and after. - Create
requirements-dev.txtincluding the main file pluspytest. Install from it. - Run
pip list --outdated. - Write a minimal
pyproject.tomlwith one dependency and install it with-e .. - Look up
requestson pypi.org. Find its repository, licence and release history. - Try installing without an environment active on Linux and read whatever your system says.
Next: putting all of this together into a project structure you would not be embarrassed to share.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship