Skip to content

Commit 763aaf8

Browse files
Merge pull request #397 from PyAr/automatic-pip-upgrade
Get pip automatically upgraded to latest version (unless explicitly avoided)
2 parents cc6bb9f + d620de2 commit 763aaf8

File tree

6 files changed

+56
-18
lines changed

6 files changed

+56
-18
lines changed

fades/envbuilder.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def post_setup(self, context):
121121
self.env_bin_path = context.bin_path
122122

123123

124-
def create_venv(requested_deps, interpreter, is_current, options, pip_options):
124+
def create_venv(requested_deps, interpreter, is_current, options, pip_options, avoid_pip_upgrade):
125125
"""Create a new virtualvenv with the requirements of this script."""
126126
# create virtualenv
127127
env = _FadesEnvBuilder()
@@ -135,7 +135,9 @@ def create_venv(requested_deps, interpreter, is_current, options, pip_options):
135135
installed = {}
136136
for repo in requested_deps.keys():
137137
if repo in (REPO_PYPI, REPO_VCS):
138-
mgr = PipManager(env_bin_path, pip_installed=pip_installed, options=pip_options)
138+
mgr = PipManager(
139+
env_bin_path, pip_installed=pip_installed, options=pip_options,
140+
avoid_pip_upgrade=avoid_pip_upgrade)
139141
else:
140142
logger.warning("Install from %r not implemented", repo)
141143
continue

fades/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ def go():
271271
'--freeze', action='store', metavar='FILEPATH',
272272
help="dump all the dependencies and its versions to the specified filepath "
273273
"(operating normally beyond that)")
274+
parser.add_argument(
275+
'--avoid-pip-upgrade', action='store_true',
276+
help="disable the automatic pip upgrade that happens after the virtualenv is created "
277+
"and before the dependencies begin to be installed.")
274278

275279
mutexg = parser.add_mutually_exclusive_group()
276280
mutexg.add_argument(
@@ -416,7 +420,7 @@ def go():
416420

417421
# Create a new venv
418422
venv_data, installed = envbuilder.create_venv(
419-
indicated_deps, args.python, is_current, options, pip_options)
423+
indicated_deps, args.python, is_current, options, pip_options, args.avoid_pip_upgrade)
420424
# store this new venv in the cache
421425
venvscache.store(installed, venv_data, interpreter, options)
422426

fades/pipmanager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@
3838
class PipManager():
3939
"""A manager for all PIP related actions."""
4040

41-
def __init__(self, env_bin_path, pip_installed=False, options=None):
41+
def __init__(self, env_bin_path, pip_installed=False, options=None, avoid_pip_upgrade=False):
4242
"""Init."""
4343
self.env_bin_path = env_bin_path
4444
self.pip_installed = pip_installed
4545
self.options = options
4646
self.pip_exe = os.path.join(self.env_bin_path, "pip")
4747
basedir = helpers.get_basedir()
4848
self.pip_installer_fname = os.path.join(basedir, "get-pip.py")
49+
self.avoid_pip_upgrade = avoid_pip_upgrade
4950

5051
def install(self, dependency):
5152
"""Install a new dependency."""
@@ -54,6 +55,12 @@ def install(self, dependency):
5455
"doing it manually (just wait a little, all should go well)")
5556
self._brute_force_install_pip()
5657

58+
# Always update pip to get latest behaviours (specially regarding security); this has
59+
# the nice side effect of getting logged the pip version that is used.
60+
if not self.avoid_pip_upgrade:
61+
python_exe = os.path.join(self.env_bin_path, "python")
62+
helpers.logged_exec([python_exe, '-m', 'pip', 'install', 'pip', '--upgrade'])
63+
5764
# split to pass several tokens on multiword dependency (this is very specific for '-e' on
5865
# external requirements, but implemented generically; note that this does not apply for
5966
# normal reqs, because even if it originally is 'foo > 1.2', after parsing it loses the

man/fades.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ fades - A system that automatically handles the virtualenvs in the cases normall
2626
[\fB-a\fR][\fB--autoimport\fR]
2727
[\fB--freeze\fR]
2828
[\fB-m\fR][\fB--module\fR]
29+
[\fB--avoid-pip-upgrade\fR]
2930
[child_program [child_options]]
3031

3132
\fBfades\fR can be used to execute directly your script, or put it with a #! at your script's beginning.
@@ -137,6 +138,10 @@ Will operate exactly as without the command, but also it will dump the revisions
137138
.BR -m ", " --module
138139
Run library module as a script (same behaviour than Python's \fB-m\fR parameter).
139140

141+
.TP
142+
.BR --avoid-pip-upgrade
143+
Disable the automatic \fBpip\fR upgrade that happens after the virtualenv is created and before the dependencies begin to be installed.
144+
140145

141146
.SH EXAMPLES
142147

tests/test_envbuilder.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import tempfile
2222
import unittest
2323
from datetime import datetime, timedelta
24-
from unittest.mock import Mock, patch
24+
from unittest.mock import Mock, patch, call
2525

2626
from pkg_resources import parse_requirements
2727

@@ -65,6 +65,7 @@ def test_create_simple(self):
6565
}
6666
interpreter = 'python3'
6767
is_current = True
68+
avoid_pip_upgrade = False
6869
options = {"virtualenv_options": [],
6970
"pyvenv_options": [],
7071
}
@@ -74,8 +75,8 @@ def test_create_simple(self):
7475
mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')
7576
mock_mgr_c.return_value = fake_manager = self.FakeManager()
7677
fake_manager.really_installed = {'dep1': 'v1', 'dep2': 'v2'}
77-
venv_data, installed = envbuilder.create_venv(requested, interpreter, is_current,
78-
options, pip_options)
78+
venv_data, installed = envbuilder.create_venv(
79+
requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)
7980

8081
self.assertEqual(venv_data, {
8182
'env_bin_path': 'env_bin_path',
@@ -88,21 +89,26 @@ def test_create_simple(self):
8889
'dep2': 'v2',
8990
}
9091
})
92+
expected_pipmanager_call = call(
93+
'env_bin_path', pip_installed='pip_installed', options=[],
94+
avoid_pip_upgrade=avoid_pip_upgrade)
95+
self.assertEqual(mock_mgr_c.call_args, expected_pipmanager_call)
9196

9297
def test_create_vcs(self):
9398
requested = {
9499
REPO_VCS: [parsing.VCSDependency("someurl")]
95100
}
96101
interpreter = 'python3'
97102
is_current = True
103+
avoid_pip_upgrade = False
98104
options = {"virtualenv_options": [], "pyvenv_options": []}
99105
pip_options = []
100106
with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:
101107
with patch.object(envbuilder, 'PipManager') as mock_mgr_c:
102108
mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')
103109
mock_mgr_c.return_value = self.FakeManager()
104-
venv_data, installed = envbuilder.create_venv(requested, interpreter, is_current,
105-
options, pip_options)
110+
venv_data, installed = envbuilder.create_venv(
111+
requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)
106112

107113
self.assertEqual(venv_data, {
108114
'env_bin_path': 'env_bin_path',
@@ -117,6 +123,7 @@ def test_unknown_repo(self):
117123
}
118124
interpreter = 'python3'
119125
is_current = True
126+
avoid_pip_upgrade = False
120127
options = {"virtualenv_options": [],
121128
"pyvenv_options": [],
122129
}
@@ -125,7 +132,8 @@ def test_unknown_repo(self):
125132
with patch.object(envbuilder, 'PipManager') as mock_mgr_c:
126133
mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')
127134
mock_mgr_c.return_value = self.FakeManager()
128-
envbuilder.create_venv(requested, interpreter, is_current, options, pip_options)
135+
envbuilder.create_venv(
136+
requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)
129137

130138
self.assertLoggedWarning("Install from 'unknown' not implemented")
131139

@@ -135,6 +143,7 @@ def test_non_existing_dep(self):
135143
}
136144
interpreter = 'python3'
137145
is_current = True
146+
avoid_pip_upgrade = False
138147
options = {'virtualenv_options': [],
139148
'pyvenv_options': [],
140149
}
@@ -147,11 +156,8 @@ def test_non_existing_dep(self):
147156
with patch.object(envbuilder, 'destroy_venv', spec=True) as mock_destroy:
148157
with self.assertRaises(FadesError) as cm:
149158
envbuilder.create_venv(
150-
requested,
151-
interpreter,
152-
is_current,
153-
options,
154-
pip_options)
159+
requested, interpreter, is_current, options, pip_options,
160+
avoid_pip_upgrade)
155161
self.assertEqual(str(cm.exception), 'Dependency installation failed')
156162
mock_destroy.assert_called_once_with('env_path')
157163

@@ -163,6 +169,7 @@ def test_different_versions(self):
163169
}
164170
interpreter = 'python3'
165171
is_current = True
172+
avoid_pip_upgrade = False
166173
options = {"virtualenv_options": [],
167174
"pyvenv_options": [],
168175
}
@@ -172,8 +179,8 @@ def test_different_versions(self):
172179
mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')
173180
mock_mgr_c.return_value = fake_manager = self.FakeManager()
174181
fake_manager.really_installed = {'dep1': 'vX', 'dep2': 'v2'}
175-
_, installed = envbuilder.create_venv(requested, interpreter, is_current, options,
176-
pip_options)
182+
_, installed = envbuilder.create_venv(
183+
requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)
177184

178185
self.assertEqual(installed, {
179186
REPO_PYPI: {

tests/test_pipmanager.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ def test_install(mocker):
7676
pip_path = os.path.join(BIN_PATH, "pip")
7777
mock = mocker.patch.object(helpers, "logged_exec")
7878
mgr.install("foo")
79+
80+
# check it always upgrades pip, and then the proper install
81+
python_path = os.path.join(BIN_PATH, "python")
82+
c1 = mocker.call([python_path, "-m", "pip", "install", "pip", "--upgrade"])
83+
c2 = mocker.call([pip_path, "install", "foo"])
84+
assert mock.call_args_list == [c1, c2]
85+
86+
87+
def test_install_without_pip_upgrade(mocker):
88+
mgr = PipManager(BIN_PATH, pip_installed=True, avoid_pip_upgrade=True)
89+
pip_path = os.path.join(BIN_PATH, "pip")
90+
mock = mocker.patch.object(helpers, "logged_exec")
91+
mgr.install("foo")
7992
mock.assert_called_with([pip_path, "install", "foo"])
8093

8194

@@ -105,7 +118,7 @@ def test_install_with_options_using_equal(mocker):
105118

106119
def test_install_raise_error(mocker, logged):
107120
mgr = PipManager(BIN_PATH, pip_installed=True)
108-
mocker.patch.object(helpers, "logged_exec", side_effect=Exception("Kapow!"))
121+
mocker.patch.object(helpers, "logged_exec", side_effect=['ok', Exception("Kapow!")])
109122
with pytest.raises(Exception):
110123
mgr.install("foo")
111124

0 commit comments

Comments
 (0)