1

I am currently trying to upgrade pip and then install paramiko inside a python script because the script itself makes use of paramiko. Below is a simple case of what I am trying to accomplish

import pip

try:
        import paramiko
except ImportError:
        pip.main(["install", "--upgrade", "pip"])
        reload(pip)
        pip.main(["install", "--user", "paramiko"])
        import paramiko

ssh = paramiko.SSHClient()

I am running this script in an Ubuntu 16.04 VM with python 2.7 which doesn't have paramiko and pip version 8.1.1. For the output of the pip upgrade after running the script, I get:

Collecting pip Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB) 100% |████████████████████████████████| 1.3MB 712kB/s Installing collected packages: pip Successfully installed pip-8.1.1 You are using pip version 8.1.1, however version 9.0.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command.

After that, it tries to install paramiko as expected, but it fails because it basically can't install the Cryptography dependency that paramiko uses. However, if it was using the upgraded pip, this would not be a problem. I have reverted the VM back to a previous snapshot to manually install paramiko by doing

pip install --upgrade pip
pip install --user paramiko

in the shell and it works, but I need to be able to perform this in a script.

Note, I also reverted the VM to a previous snapshot and tried installing paramiko with the above command in the shell, but without performing the pip upgrade first, and as expected I get the same error as what the script gives when trying to install paramiko. Now I have done a bit of testing by trying to having this in the script:

pip.main(["install", "--upgrade", "pip"])
reload(pip)
pip.main(["install", "--upgrade", "pip"])
reload(pip)

and as for the output I get this:

Collecting pip
Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)

100% |████████████████████████████████| 1.3MB 712kB/s Installing collected packages: pip Successfully installed pip-8.1.1 You are using pip version 8.1.1, however version 9.0.1 is available. 

You should consider upgrading via the 'pip install --upgrade pip' command.

Collecting pip
Using cached pip-9.0.1-py2.py3-none-any.whl

Installing collected packages: pip Successfully installed pip-8.1.1

You are using pip version 8.1.1, however version 9.0.1 is available.

You should consider upgrading via the 'pip install --upgrade pip' command.

Notice how it says using cached pip-9.0.1 at the second pip upgrade. Does this mean after the reload, the script is using pip 9.0.1? If so then why is the paramiko install not using the upgraded pip? How can I get the paramiko to use the upgraded pip?

bitscuit
  • 976
  • 1
  • 11
  • 26
  • You might need to run `sudo apt-get install build-essential libssl-dev libffi-dev python-dev -y` before this as cryptography library needs them. – hurturk Jun 06 '17 at 22:59
  • I am trying to avoid the use of sudo right now because this script might be run on a VM later where the permissions will be restricted, and I am also trying to minimize the amount of installs/changes to the system. – bitscuit Jun 06 '17 at 23:24

2 Answers2

1

Do you try to remove the pip package from the sys.modules array?

import pip
import sys
import site

print ('PIP Version: ' + pip.__version__)

pip.main(["install", "--upgrade", "pip"])

user_path  = site.getusersitepackages()
sytem_path = site.getsitepackages()

sys.path = [user_path] + system_path + sys.path

pips = [module for module in sys.modules if module.startswith('pip')]
for pip_module in pips:
    sys.modules.pop(pip_module)

del pip

import pip
print ('PIP Version: ' + pip.__version__)

Runinng on my machine with pip 8.1.1 installed, the output I get was:

 begnini@coffee-machine:~# python update.py
 PIP Version: 8.1.1
 Collecting pip
  Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 651kB/s
 Installing collected packages: pip
  Found existing installation: pip 8.1.1
    Uninstalling pip-8.1.1:
      Successfully uninstalled pip-8.1.1
 Successfully installed pip-9.0.1
 PIP Version: 9.0.1

EDIT: Like we discussed, the problem was with pip trying to search first in the main python lib directory instead /usr/local/lib dir. To fix this, I added the local lib dir in the first position of the system path array. With this modification, the bug should be fixed.

EDIT 2: Added site paths instead use hardcoded dirs.

Begnini
  • 126
  • 6
  • So, I tried your solution and it's still not working. I literally copied and pasted what you have, and for the version before the upgrade I have 8.1.1 (expected), but after the upgrade the version is still 8.1.1. The interesting thing is that after the script runs, if I were to re-run the script or just check the version from the shell, I get 9.0.1. It's like the script has to end before the changes take effect. Also, during the upgrade, my output messages are different from yours, I don't get the "Found existing..." and "Uninstalling...". In it's place I have "Successfully installed 8.1.1". – bitscuit Jun 06 '17 at 23:41
  • is it possible that the script is just using a cached version that's outdated or something? Or maybe just looking in the wrong folder? – bitscuit Jun 06 '17 at 23:43
  • Reading your response, I think your pip is installed with apt, right (python-pip)? With apt, pip directory is `/usr/lib/python2.7/dist-packages/pip`, after the update, the new pip directory is `/usr/lib/python2.7/dist-packages/pip`. You can confirm this printing `pip.__path__`. – Begnini Jun 07 '17 at 13:50
  • Before upgrading pip: ['/usr/lib/python2.7/dist-packages/pip']. After upgrading pip: ['/home/user1/.local/lib/python2.7/site-packages/pip'] – bitscuit Jun 07 '17 at 14:26
  • Does this mean that the script can't use the new pip version because it's in a new directory? Is it possible to upgrade pip to a directory I specify in the script and then reload pip in such a way that the script will use the pip in that directory? – bitscuit Jun 07 '17 at 15:17
  • I update the code to workaround the bug. Working with pip 8.1.1 installed by apt. To work you have to point the dir to your local directory ('/home/user1/.local/lib/python2.7/site-packages/pip') – Begnini Jun 07 '17 at 22:56
  • Tried out your change and it works now, but is there any way to generalize it for any VM? Say if the upgrade path is different? From your change, it seems as though the script doesn't realize sys.path has been updated. I read somewhere that you can re-initialize the sys.path through "import site" and then "reload(site)". I tried that in place of appending the new pip path to sys.path, but it didn't work. Any ideas? – bitscuit Jun 08 '17 at 14:30
  • Update: I did think of one way to generalize it and that's to get a list of all the directories that contain the pip directory and then just add them to the sys.path. Waste of resources and slows down the script, but it is more robust than having a hard coded path. Also when I said any VM, I meant Linux VM (usually ubuntu). – bitscuit Jun 08 '17 at 15:06
  • Of course. I updated my post to get the dirs where pip could be installed. – Begnini Jun 08 '17 at 18:30
0

So after some digging around, it looks like this is a pip bug that has been patched. Unfortunately, I may not always have control over the machine that will run this script (i.e. can't apply the patch) so my solution was just to use os.execl() to restart the script whenever the second import for paramiko in the except section fails.

As for the bug, in summary, after a pip upgrade, the wrong version was reported, but pip was upgraded. So if the script was ran again, the new version of pip was used which allowed paramiko to be installed properly, hence the solution to restart the script.

For guidance on using os.execl() since python documentation is not very clear on it, follow this link

Sample of how my code looks now:

import pip
import sys
import os

try:
        import paramiko
except ImportError:
        pip.main(["install", "--upgrade", "pip"])
        pip.main(["install", "--user", "paramiko"])
        try:
            import paramiko
        except ImportError:
            os.execl(sys.executable, 'python', __file__, *sys.argv[1:])

ssh = paramiko.SSHClient()
bitscuit
  • 976
  • 1
  • 11
  • 26