pip install --upgrade yields different results from pip install

Created on 11 Dec 2019  Â·  2Comments  Â·  Source: pypa/pip

Environment

  • pip version: 8.1.1 and upgrade to 19.3.1
  • Python version: 3.5.2
  • OS: Ubuntu 18.04

(This is an issue in the context of upgrading pip from one version to another)

Description
Upgrading a package after upgrading pip did not result in the same outcome as installing that package initially with the upgraded version of pip.

Specifically, the jupyter package was installed with pip 8.1.1 using python 3.5.2 which resulted in its dependency package ipython being installed to version 7.10.1. No error was produced for this. Later after pip install --upgrade pip, the jupyter package was upgraded via pip install --upgrade jupyter and this resulted in an error message that ipython requires a different version of python (>= 3.6).

When instead pip 19.3.1 is used from the beginning in a clean venv, no error is produced, and jupyter's dependency ipython is installed to version 7.9.0.

Thus, installing the jupyter package using the current pip version via pip install results in a different configuration than installing the jupyter package via upgrading with pip install --upgrade after updating the pip version.

Expected behavior
My intuitive expectation as a user is that the pip install --upgrade command on a package results in the singular, deterministic setup of that package in its most updated form at that point in time. The expectation is the same from pip install. The expected behavior is then that those two commands result in the same package configuration, but this was observed to not be the case.

How to Reproduce

Method 1:

  1. Starting with python 3.5.2 and pip 8.1.1 on ubuntu 18.04 in a venv, install the package jupyter with the outdated pip
python 3.5 -m venv .venv
. .venv/bin/activate
python3.5 -m pip install jupyter
  1. Upgrade pip to version 19.3.1 then upgrade the jupyter package
python3.5 -m pip install --upgrade pip
python3.5 -m pip install --upgrade jupyter

Results in the error message below, that ipython requires a different python version >=3.6

Method 2:

  1. Starting with python 3.5.2 and pip 19.3.1 on ubuntu 18.04 in a venv, install the package jupyter
python3.5 -m venv .venv
. .venv/bin/activate
python3.5 -m pip install --upgrade pip
python3.5 -m pip install jupyter

No errors result.

Output

After Method 1 Step 1 above, it seems that jupyter installs successfully, and the following warning is shown:

You are using pip version 8.1.1, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

At this point pip freeze shows jupyter==1.0.0 and ipython==7.10.1, and ipywidgets==7.5.1.

After/during Step 2 the following error is shown:

Requirement already satisfied, skipping upgrade: ipython>=4.0.0; python_version >= "3.3" in /home/nafty/.local/lib/python3.5/site-packages (from ipywidgets->jupyter) (7.10.1)
ERROR: Package 'ipython' requires a different Python: 3.5.2 not in '>=3.6'

At this point pip freeze shows the same versions still (jupyter 1.0.0, ipython 7.10.1).

After Method 2 no errors are shown and pip freeze shows jupyter==1.0.0 and ipython==7.9.0

Summary

Seems to me that the outdated pip v8.1.1 installed jupyter with ipython version 7.10.1 which is a version that is not supposed to become installed on python 3.5.2 (but, I guess we cannot expect any guarantees of correctness from an outdated version of pip...). Then/however, the updated/current version of pip does not rectify this situation; perhaps this would be a reasonable expectation on pip...? Furthermore, this leads to a scenario where installing the 'current' version of the jupyter package via install --upgrade results in a different configuration than via installing directly via pip install using an updated pip, which is probably an undesired mismatched situation to be in...

Another issue is that while it is true that ipython 7.10.1 requires python >= 3.6 (as the error says), the situation is that I am trying to install jupyter which does not require ipython 7.10.1 (in fact, it naturally collects 7.9.0---but it pathologically already has 7.10.1 which shouldn't actually be there), so really python 3.6+ isn't actually required (unlike the error says...). This is a situation that is 'fixable' by pip, and a user would benefit from the situation being fixed.

(The actual error that produces this is a raising of UnsupportedPythonVersion exception in a call to _check_dist_requires_python, which is defined here).

triage support

Most helpful comment

My intuitive expectation as a user is that the pip install --upgrade command on a package results in the singular, deterministic setup of that package in its most updated form at that point in time.

This is not the case because pip only upgrades packages if needed. The behaviour that matches your expectation would be pip install --upgrade --upgrade-strategy=eager. This was the default before pip 10.0, but was changed because the “always upgrade to latest” behaviour would introduce unexpected API breakage when a user does not intend to upgrade a dependency (e.g. IPython in your case).

My personal interpretation of the current behaviour is that Requires-Python is checked when the dependency is first installed. pip does not enforce it again when checking whether it needs to be upgraded, because since it is installed it is presumed to be compatible, and there must be a reason if it it has an incompatible Requires-Python (e.g. maybe the user explicitly disabled that check). But the “reason” you got that incompatible python_version is because pip 8.x predates that configuration (Requires-Python was implemented in pip 9.x in 2016), so the incompatible IPython was installed by mistake.

Technically this can be prevented if we also checks Requires-Python when deciding whether a dependency needs to be updated. But this would break some currently working workflows (they will need to set --ignore-requires-python), with only limited merit. So I’m tempted to say the current behaviour is good enough, and the user needs to fix the environment manually in edge cases like this.

All 2 comments

My intuitive expectation as a user is that the pip install --upgrade command on a package results in the singular, deterministic setup of that package in its most updated form at that point in time.

This is not the case because pip only upgrades packages if needed. The behaviour that matches your expectation would be pip install --upgrade --upgrade-strategy=eager. This was the default before pip 10.0, but was changed because the “always upgrade to latest” behaviour would introduce unexpected API breakage when a user does not intend to upgrade a dependency (e.g. IPython in your case).

My personal interpretation of the current behaviour is that Requires-Python is checked when the dependency is first installed. pip does not enforce it again when checking whether it needs to be upgraded, because since it is installed it is presumed to be compatible, and there must be a reason if it it has an incompatible Requires-Python (e.g. maybe the user explicitly disabled that check). But the “reason” you got that incompatible python_version is because pip 8.x predates that configuration (Requires-Python was implemented in pip 9.x in 2016), so the incompatible IPython was installed by mistake.

Technically this can be prevented if we also checks Requires-Python when deciding whether a dependency needs to be updated. But this would break some currently working workflows (they will need to set --ignore-requires-python), with only limited merit. So I’m tempted to say the current behaviour is good enough, and the user needs to fix the environment manually in edge cases like this.

I think pip should still provide some functionality around this, so that users don't have to investigate substantially to understand the issue or be misled by the error message to think that they need to change the python version—which in the above case was not actually needed. It seems much more efficient that pip would identify this issue quickly by aggregating all the dependencies requirements and realizing that 7.10.1 is not needed than for the user to manually figure that out and fix it.

How about a non-breaking optional flag somewhere... Instead of making existing users need to use an --ignore-requires-python flag, provide an optional --with-requires-python or --check-requires-python flag for a user in this situation to use. That way, after the error message, which warns about ipython being incompatible due to python version, the user will have something simple to do about it: pip install --upgrade --with-requires-python ipython, and then pip would go ahead and figure out the actual requirement on ipython due to all the dependency relations to installed packages and realize that 7.10.1 is not actually needed, and essentially downgrade/fix to 7.9.0. (Maybe this is problematic in that a 'downgrade' results from --upgrade, which is then counter intuitive..? Can be solved with an informative error message instead and a second --repair step advised)

Another point is about pip check, which returned No broken requirements found. and/but is documented to "Verify installed packages have compatible dependencies." In this case the installed packages (jupyter) had a non-compatible dependency (ipython 7.10.1). Seems that pip check should have caught it. So maybe pip check should check Requires-Python on every package that it checks..? Or even without that additional overhead an optional flag would still be useful so that the user can target it to a specific problematic package, like ipython above.

Again in the above case the error message was presented but didn't really advise the user of what to do about it... If anything, the error message misleads the user to thinking that changing to python 3.6+ is required, which was not actually the case. An improved pip check feature would be useful here to understand the situation, such as by calling pip check --with-requires-python jupyter (or ipython directly, even..) which would result in pip discovering that ipython is incompatible due to Requires-Python and also discovering that the actual requirement on ipython is just >=5.0.0 due to jupyter. This could then be reported to the user, and the user would be in a good position to do something to fix it, like confidently uninstalling and reinstalling ipython while knowing what to expect (that a version >=5.0.0 and <7.10.1 will be installed, so the result 7.9.0 will be acceptable), or even using an optional flag pip install --upgrade --with-requires-python --repair-broken ipython -style command.

Was this page helpful?
0 / 5 - 0 ratings