Why pip upgrades packages automatically
When you ask pip to install a package, it resolves the full dependency graph. If the requested package requires a newer version of a library that you already have, pip treats the newer version as the only solution that satisfies all requirements, so it uninstalls the old one and installs the new one. This behaviour is intentional but can be surprising when you deliberately keep an older version for compatibility reasons.
Pin the versions you need with a constraints file
Create a `constraints.txt` that lists the exact versions you are willing to keep. Pass the file to pip with the `-c` option; pip will treat those entries as hard limits and will abort if a requested package would require a newer version.
For the TensorFlow scenario you could write:
tensorboard==1.14.0
tensorflow-gpu==1.12.3
# any other pinned packages
Install with the constraints file and stop on conflict
Run pip with the `-c` flag and the `--upgrade-strategy=only-if-needed` flag (the default). If the resolver finds that installing `tensorflow` would need a newer `tensorboard` than the one you pinned, it raises an error instead of performing the upgrade.
```bash pip install tensorflow -c constraints.txt --upgrade-strategy=only-if-needed ```
If the command fails, pip prints a clear conflict message, allowing you to choose a compatible version of `tensorflow` manually.
You can also add `--no-deps` to force pip to skip dependency resolution entirely, but then you must manage all dependencies yourself.
pip install tensorflow -c constraints.txt --no-deps # only installs tensorflow itself
Leverage dedicated dependency tools
Tools such as **pip‑tools**, **Poetry**, or **pipenv** generate a lock file that records a fully resolved, conflict‑free set of packages. They refuse to upgrade a dependency unless you explicitly ask for it, and they provide a `pip-sync` command that enforces the lock file.
```bash # Using pip-tools pip-compile requirements.in # creates requirements.txt with exact versions pip-sync requirements.txt # installs exactly those versions, aborts on conflict ```
These workflows eliminate the trial‑and‑error loop by letting the resolver tell you upfront which versions are compatible.
# requirements.in
tensorflow-gpu==1.12.3
# optional: tensorflow (no version) – let pip‑tools pick a compatible build
A practical step‑by‑step for the TensorFlow example
1. Create a virtual environment (`python -m venv .venv && source .venv/bin/activate`). 2. Pin the GPU‑compatible TensorFlow and its known‑good TensorBoard version in `constraints.txt`. 3. Ask pip to install the generic `tensorflow` package using the constraints file. 4. If pip aborts, read the conflict message and adjust the version of `tensorflow` (e.g., `tensorflow==1.13.1`). 5. When you have a set that works, freeze it with `pip freeze > requirements.txt` for future reproducibility.
```bash # constraints.txt tensorboard==1.14.0 tensorflow-gpu==1.12.3
# Attempt install pip install tensorflow -c constraints.txt ```
If the resolver reports: ``` ERROR: Cannot install tensorflow because it requires tensorboard>=2.0, but you have tensorboard==1.14.0. ``` choose an older TensorFlow (`pip install tensorflow==1.13.1 -c constraints.txt`) that still works with your pinned TensorBoard.
This method stops pip from silently swapping packages and gives you a deterministic, repeatable environment.
# Example of narrowing TensorFlow version
pip install "tensorflow<1.14" -c constraints.txt
Takeaway: Use a constraints file or a lock‑file tool to make pip abort on version conflicts instead of auto‑upgrading.
People also ask
Can pip ask for confirmation before upgrading a dependency?
No, pip has no interactive prompt. You must enforce limits with constraints, version pins, or external tools.
What does `--upgrade-strategy=only-if-needed` do?
It upgrades a package only when another requirement forces a newer version; it will still upgrade if the resolver determines that’s the only way to satisfy all constraints.
Is there a way to see the dependency conflict before any changes are made?
Run `pip install <pkg> -c constraints.txt --dry-run` with pip ≥23.1 (the `--dry-run` flag simulates the install and reports conflicts without modifying the environment).
Should I always use a virtual environment for this kind of pinning?
Yes, isolating projects prevents global packages from interfering and makes the constraints file reliable.
Inspired by a public discussion on Stack Overflow. This article is an original explanation for learners.