Skip to content

Commit 293c91e

Browse files
zoobapradyunsg
authored andcommitted
Configuration files may now also be stored under sys.prefix (#6268)
* Rename kinds.VENV to kinds.SITE and site_config_files to global_config_files * Add tests for config file options * Deprecate --venv in pip config
1 parent 61e5970 commit 293c91e

File tree

6 files changed

+119
-54
lines changed

6 files changed

+119
-54
lines changed

news/5060.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Configuration files may now also be stored under ``sys.prefix``

src/pip/_internal/commands/configuration.py

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from pip._internal.cli.status_codes import ERROR, SUCCESS
77
from pip._internal.configuration import Configuration, kinds
88
from pip._internal.exceptions import PipError
9-
from pip._internal.locations import venv_config_file
9+
from pip._internal.locations import running_under_virtualenv, site_config_file
10+
from pip._internal.utils.deprecation import deprecated
1011
from pip._internal.utils.misc import get_prog
1112

1213
logger = logging.getLogger(__name__)
@@ -23,7 +24,7 @@ class ConfigurationCommand(Command):
2324
set: Set the name=value
2425
unset: Unset the value associated with name
2526
26-
If none of --user, --global and --venv are passed, a virtual
27+
If none of --user, --global and --site are passed, a virtual
2728
environment configuration file is used if one is active and the file
2829
exists. Otherwise, all modifications happen on the to the user file by
2930
default.
@@ -73,12 +74,23 @@ def __init__(self, *args, **kwargs):
7374
help='Use the user configuration file only'
7475
)
7576

77+
self.cmd_opts.add_option(
78+
'--site',
79+
dest='site_file',
80+
action='store_true',
81+
default=False,
82+
help='Use the current environment configuration file only'
83+
)
84+
7685
self.cmd_opts.add_option(
7786
'--venv',
7887
dest='venv_file',
7988
action='store_true',
8089
default=False,
81-
help='Use the virtualenv configuration file only'
90+
help=(
91+
'[Deprecated] Use the current environment configuration '
92+
'file in a virtual environment only'
93+
)
8294
)
8395

8496
self.parser.insert_option_group(0, self.cmd_opts)
@@ -127,27 +139,41 @@ def run(self, options, args):
127139
return SUCCESS
128140

129141
def _determine_file(self, options, need_value):
130-
file_options = {
131-
kinds.USER: options.user_file,
132-
kinds.GLOBAL: options.global_file,
133-
kinds.VENV: options.venv_file
134-
}
135-
136-
if sum(file_options.values()) == 0:
142+
# Convert legacy venv_file option to site_file or error
143+
if options.venv_file and not options.site_file:
144+
if running_under_virtualenv():
145+
options.site_file = True
146+
deprecated(
147+
"The --venv option has been deprecated.",
148+
replacement="--site",
149+
gone_in="19.3",
150+
)
151+
else:
152+
raise PipError(
153+
"Legacy --venv option requires a virtual environment. "
154+
"Use --site instead."
155+
)
156+
157+
file_options = [key for key, value in (
158+
(kinds.USER, options.user_file),
159+
(kinds.GLOBAL, options.global_file),
160+
(kinds.SITE, options.site_file),
161+
) if value]
162+
163+
if not file_options:
137164
if not need_value:
138165
return None
139-
# Default to user, unless there's a virtualenv file.
140-
elif os.path.exists(venv_config_file):
141-
return kinds.VENV
166+
# Default to user, unless there's a site file.
167+
elif os.path.exists(site_config_file):
168+
return kinds.SITE
142169
else:
143170
return kinds.USER
144-
elif sum(file_options.values()) == 1:
145-
# There's probably a better expression for this.
146-
return [key for key in file_options if file_options[key]][0]
171+
elif len(file_options) == 1:
172+
return file_options[0]
147173

148174
raise PipError(
149175
"Need exactly one file to operate upon "
150-
"(--user, --venv, --global) to perform."
176+
"(--user, --site, --global) to perform."
151177
)
152178

153179
def list_values(self, options, args):

src/pip/_internal/configuration.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
2222
)
2323
from pip._internal.locations import (
24-
legacy_config_file, new_config_file, running_under_virtualenv,
25-
site_config_files, venv_config_file,
24+
global_config_files, legacy_config_file, new_config_file, site_config_file,
2625
)
2726
from pip._internal.utils.misc import ensure_dir, enum
2827
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -58,7 +57,7 @@ def _disassemble_key(name):
5857
kinds = enum(
5958
USER="user", # User Specific
6059
GLOBAL="global", # System Wide
61-
VENV="venv", # Virtual Environment Specific
60+
SITE="site", # [Virtual] Environment Specific
6261
ENV="env", # from PIP_CONFIG_FILE
6362
ENV_VAR="env-var", # from Environment Variables
6463
)
@@ -82,7 +81,7 @@ def __init__(self, isolated, load_only=None):
8281
# type: (bool, Kind) -> None
8382
super(Configuration, self).__init__()
8483

85-
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None]
84+
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.SITE, None]
8685
if load_only not in _valid_load_only:
8786
raise ConfigurationError(
8887
"Got invalid value for load_only - should be one of {}".format(
@@ -94,7 +93,7 @@ def __init__(self, isolated, load_only=None):
9493

9594
# The order here determines the override order.
9695
self._override_order = [
97-
kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR
96+
kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
9897
]
9998

10099
self._ignore_env_names = ["version", "help"]
@@ -351,7 +350,7 @@ def _iter_config_files(self):
351350
yield kinds.ENV, []
352351

353352
# at the base we have any global configuration
354-
yield kinds.GLOBAL, list(site_config_files)
353+
yield kinds.GLOBAL, list(global_config_files)
355354

356355
# per-user configuration next
357356
should_load_user_config = not self.isolated and not (
@@ -362,8 +361,7 @@ def _iter_config_files(self):
362361
yield kinds.USER, [legacy_config_file, new_config_file]
363362

364363
# finally virtualenv configuration first trumping others
365-
if running_under_virtualenv():
366-
yield kinds.VENV, [venv_config_file]
364+
yield kinds.SITE, [site_config_file]
367365

368366
def _get_parser_to_modify(self):
369367
# type: () -> Tuple[str, RawConfigParser]

src/pip/_internal/locations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,12 @@ def virtualenv_no_global():
135135
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
136136
bin_py = '/usr/local/bin'
137137

138-
site_config_files = [
138+
global_config_files = [
139139
os.path.join(path, config_basename)
140140
for path in appdirs.site_config_dirs('pip')
141141
]
142142

143-
venv_config_file = os.path.join(sys.prefix, config_basename)
143+
site_config_file = os.path.join(sys.prefix, config_basename)
144144
new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename)
145145

146146

tests/unit/test_configuration.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from pip._internal.exceptions import ConfigurationError
1010
from pip._internal.locations import (
11-
new_config_file, site_config_files, venv_config_file,
11+
global_config_files, new_config_file, site_config_file,
1212
)
1313
from tests.lib.configuration_helpers import ConfigurationMixin, kinds
1414

@@ -27,8 +27,8 @@ def test_user_loading(self):
2727
self.configuration.load()
2828
assert self.configuration.get_value("test.hello") == "2"
2929

30-
def test_venv_loading(self):
31-
self.patch_configuration(kinds.VENV, {"test.hello": "3"})
30+
def test_site_loading(self):
31+
self.patch_configuration(kinds.SITE, {"test.hello": "3"})
3232

3333
self.configuration.load()
3434
assert self.configuration.get_value("test.hello") == "3"
@@ -90,8 +90,8 @@ class TestConfigurationPrecedence(ConfigurationMixin):
9090
# Tests for methods to that determine the order of precedence of
9191
# configuration options
9292

93-
def test_env_overides_venv(self):
94-
self.patch_configuration(kinds.VENV, {"test.hello": "1"})
93+
def test_env_overides_site(self):
94+
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
9595
self.patch_configuration(kinds.ENV, {"test.hello": "0"})
9696
self.configuration.load()
9797

@@ -111,16 +111,16 @@ def test_env_overides_global(self):
111111

112112
assert self.configuration.get_value("test.hello") == "0"
113113

114-
def test_venv_overides_user(self):
114+
def test_site_overides_user(self):
115115
self.patch_configuration(kinds.USER, {"test.hello": "2"})
116-
self.patch_configuration(kinds.VENV, {"test.hello": "1"})
116+
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
117117
self.configuration.load()
118118

119119
assert self.configuration.get_value("test.hello") == "1"
120120

121-
def test_venv_overides_global(self):
121+
def test_site_overides_global(self):
122122
self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
123-
self.patch_configuration(kinds.VENV, {"test.hello": "1"})
123+
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
124124
self.configuration.load()
125125

126126
assert self.configuration.get_value("test.hello") == "1"
@@ -141,8 +141,8 @@ def test_env_not_overriden_by_environment_var(self):
141141
assert self.configuration.get_value("test.hello") == "1"
142142
assert self.configuration.get_value(":env:.hello") == "5"
143143

144-
def test_venv_not_overriden_by_environment_var(self):
145-
self.patch_configuration(kinds.VENV, {"test.hello": "2"})
144+
def test_site_not_overriden_by_environment_var(self):
145+
self.patch_configuration(kinds.SITE, {"test.hello": "2"})
146146
os.environ["PIP_HELLO"] = "5"
147147

148148
self.configuration.load()
@@ -182,8 +182,8 @@ def test_no_specific_given_modification(self):
182182
else:
183183
assert False, "Should have raised an error."
184184

185-
def test_venv_modification(self):
186-
self.configuration.load_only = kinds.VENV
185+
def test_site_modification(self):
186+
self.configuration.load_only = kinds.SITE
187187
self.configuration.load()
188188

189189
# Mock out the method
@@ -192,9 +192,9 @@ def test_venv_modification(self):
192192

193193
self.configuration.set_value("test.hello", "10")
194194

195-
# get the path to venv config file
195+
# get the path to site config file
196196
assert mymock.call_count == 1
197-
assert mymock.call_args[0][0] == venv_config_file
197+
assert mymock.call_args[0][0] == site_config_file
198198

199199
def test_user_modification(self):
200200
# get the path to local config file
@@ -224,4 +224,4 @@ def test_global_modification(self):
224224

225225
# get the path to user config file
226226
assert mymock.call_count == 1
227-
assert mymock.call_args[0][0] == site_config_files[-1]
227+
assert mymock.call_args[0][0] == global_config_files[-1]

tests/unit/test_options.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
import pip._internal.configuration
77
from pip._internal import main
8-
from pip._internal.commands import DownloadCommand
8+
from pip._internal.commands import ConfigurationCommand, DownloadCommand
9+
from pip._internal.exceptions import PipError
910
from tests.lib.options_helpers import AddFakeCommandMixin
1011

1112

@@ -383,23 +384,62 @@ def test_client_cert(self):
383384
class TestOptionsConfigFiles(object):
384385

385386
def test_venv_config_file_found(self, monkeypatch):
386-
# strict limit on the site_config_files list
387+
# strict limit on the global_config_files list
387388
monkeypatch.setattr(
388-
pip._internal.configuration, 'site_config_files', ['/a/place']
389+
pip._internal.configuration, 'global_config_files', ['/a/place']
389390
)
390391

391-
# If we are running in a virtualenv and all files appear to exist,
392-
# we should see two config files.
393-
monkeypatch.setattr(
394-
pip._internal.configuration,
395-
'running_under_virtualenv',
396-
lambda: True,
397-
)
398-
monkeypatch.setattr(os.path, 'exists', lambda filename: True)
399392
cp = pip._internal.configuration.Configuration(isolated=False)
400393

401394
files = []
402395
for _, val in cp._iter_config_files():
403396
files.extend(val)
404397

405398
assert len(files) == 4
399+
400+
@pytest.mark.parametrize(
401+
"args, expect",
402+
(
403+
([], None),
404+
(["--global"], "global"),
405+
(["--site"], "site"),
406+
(["--user"], "user"),
407+
(["--global", "--user"], PipError),
408+
(["--global", "--site"], PipError),
409+
(["--global", "--site", "--user"], PipError),
410+
)
411+
)
412+
def test_config_file_options(self, monkeypatch, args, expect):
413+
cmd = ConfigurationCommand()
414+
# Replace a handler with a no-op to avoid side effects
415+
monkeypatch.setattr(cmd, "get_name", lambda *a: None)
416+
417+
options, args = cmd.parser.parse_args(args + ["get", "name"])
418+
if expect is PipError:
419+
with pytest.raises(PipError):
420+
cmd._determine_file(options, need_value=False)
421+
else:
422+
assert expect == cmd._determine_file(options, need_value=False)
423+
424+
def test_config_file_venv_option(self, monkeypatch):
425+
cmd = ConfigurationCommand()
426+
# Replace a handler with a no-op to avoid side effects
427+
monkeypatch.setattr(cmd, "get_name", lambda *a: None)
428+
429+
collected_warnings = []
430+
431+
def _warn(message, *a, **kw):
432+
collected_warnings.append(message)
433+
monkeypatch.setattr("warnings.warn", _warn)
434+
435+
options, args = cmd.parser.parse_args(["--venv", "get", "name"])
436+
assert "site" == cmd._determine_file(options, need_value=False)
437+
assert collected_warnings
438+
assert "--site" in collected_warnings[0]
439+
440+
# No warning or error if both "--venv" and "--site" are specified
441+
collected_warnings[:] = []
442+
options, args = cmd.parser.parse_args(["--venv", "--site", "get",
443+
"name"])
444+
assert "site" == cmd._determine_file(options, need_value=False)
445+
assert not collected_warnings

0 commit comments

Comments
 (0)