Reusing code
Your project, my-app, needs to download web pages. Writing that from scratch would take weeks. Instead you reuse a package: code someone else wrote, tested and shared so anyone can install it. Your project then depends on it.
Packages live in a registry, a public online store. Python’s is PyPI, the Python Package Index; JavaScript’s is the npm registry. A package manager is the tool that fetches them: pip for Python, npm for JavaScript.
Declaring and installing
You don’t copy packages in by hand. You declare them in a file: here, the one line requests>=2.32,<3 in requirements.txt. pyproject.toml can hold the same list; JavaScript projects use package.json.
Many packages use semantic versioning: MAJOR.MINOR.PATCH. PATCH fixes bugs, MINOR adds features, MAJOR may break code that uses it. So >=2.32,<3 accepts new fixes and features, but never version 3.
pip install -r requirements.txt reads your file, asks PyPI which versions exist and picks the newest that fits the rule. In this example, that is requests 2.32.3. pip downloads it and installs it.
requests needs packages of its own: charset-normalizer, idna, urllib3 and certifi. pip installs those too. They are transitive dependencies (dependencies of your dependencies): you wrote one line and got five packages.
Repeatable installs
They all go into a virtual environment: a folder of packages just for this project, made with python -m venv .venv. Another project gets its own, so each can use different versions without clashing. npm uses node_modules.
Now record exactly what got installed. pip freeze lists every package with its exact version; saved to a file, that is a lock file. All five are pinned (fixed) with ==, transitive ones included. npm writes package-lock.json for you.
A teammate, or a server, installs from the lock file instead. Even after a newer urllib3 comes out, they get the very same five versions you tested. Same file in, same packages out: a repeatable install.
Staying safe
A package’s code runs on your machine, with your permissions. Install only what you trust: check the exact name, since look-alikes such as reqeusts are a known trick, and prefer well-known, maintained packages. A lock file with hashes (pip --require-hashes) refuses a swapped download.
What happens
;; step 1/10 · reuse
project my-app: your own code
needs to download web pages over HTTP
package code someone else wrote and shared, ready to install
here: requests
dependency a package your project needs to run
The versions are real PyPI releases, frozen at 1 November 2024. Install today and pip picks newer ones (requests itself has moved on), which is exactly why lock files exist. pip’s real resolver also backtracks when two packages need clashing versions, and it skips yanked releases. Here nothing clashes, so the newest version that fits always wins.