Skip to content

Commit caddbb1

Browse files
committed
Implement the new base prefix config "level".
1 parent e6e7c12 commit caddbb1

File tree

5 files changed

+60
-14
lines changed

5 files changed

+60
-14
lines changed

docs/html/topics/configuration.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ and how they are related to pip's various command line options.
1919

2020
## Configuration Files
2121

22-
Configuration files can change the default values for command line option.
23-
They are written using a standard INI style configuration files.
22+
Configuration files can change the default values for command line options.
23+
They are written using standard INI style configuration files.
2424

25-
pip has 3 "levels" of configuration files:
25+
pip has 4 "levels" of configuration files:
2626

27-
- `global`: system-wide configuration file, shared across users.
28-
- `user`: per-user configuration file.
27+
- `global`: system-wide configuration file, shared across all users.
28+
- `user`: per-user configuration file, shared across all environments.
29+
- `base` : per-base environment configuration file, shared across all virtualenvs with the same base.
2930
- `site`: per-environment configuration file; i.e. per-virtualenv.
3031

3132
### Location
@@ -47,8 +48,11 @@ User
4748
4849
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
4950
51+
Base
52+
: {file}`\{sys.base_prefix\}/pip.conf`
53+
5054
Site
51-
: {file}`$VIRTUAL_ENV/pip.conf`
55+
: {file}`\{sys.prefix\}/pip.conf`
5256
```
5357

5458
```{tab} MacOS
@@ -63,8 +67,11 @@ User
6367
6468
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
6569
70+
Base
71+
: {file}`\{sys.base_prefix\}/pip.conf`
72+
6673
Site
67-
: {file}`$VIRTUAL_ENV/pip.conf`
74+
: {file}`\{sys.prefix\}/pip.conf`
6875
```
6976

7077
```{tab} Windows
@@ -81,8 +88,11 @@ User
8188
8289
The legacy "per-user" configuration file is also loaded, if it exists: {file}`%HOME%\\pip\\pip.ini`
8390
91+
Base
92+
: {file}`\{sys.base_prefix\}\\pip.ini`
93+
8494
Site
85-
: {file}`%VIRTUAL_ENV%\\pip.ini`
95+
: {file}`\{sys.prefix\}\\pip.ini`
8696
```
8797

8898
### `PIP_CONFIG_FILE`
@@ -102,6 +112,7 @@ order:
102112
- `PIP_CONFIG_FILE`, if given.
103113
- Global
104114
- User
115+
- Base
105116
- Site
106117

107118
Each file read overrides any values read from previous files, so if the

news/9752.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Config files are now included from the base installation in the case of virtual environments.

src/pip/_internal/configuration.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@
3636
kinds = enum(
3737
USER="user", # User Specific
3838
GLOBAL="global", # System Wide
39-
SITE="site", # [Virtual] Environment Specific
39+
BASE="base", # Base environment specific (e.g. for all venvs with the same base)
40+
SITE="site", # Environment Specific (e.g. per venv)
4041
ENV="env", # from PIP_CONFIG_FILE
4142
ENV_VAR="env-var", # from Environment Variables
4243
)
43-
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
44-
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
44+
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.BASE, kinds.SITE, kinds.ENV, kinds.ENV_VAR
45+
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.BASE, kinds.SITE
4546

4647
logger = getLogger(__name__)
4748

@@ -70,6 +71,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
7071
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
7172
]
7273

74+
base_config_file = os.path.join(sys.base_prefix, CONFIG_BASENAME)
7375
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
7476
legacy_config_file = os.path.join(
7577
os.path.expanduser("~"),
@@ -78,6 +80,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
7880
)
7981
new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
8082
return {
83+
kinds.BASE: [base_config_file],
8184
kinds.GLOBAL: global_config_files,
8285
kinds.SITE: [site_config_file],
8386
kinds.USER: [legacy_config_file, new_config_file],
@@ -344,6 +347,8 @@ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
344347
# The legacy config file is overridden by the new config file
345348
yield kinds.USER, config_files[kinds.USER]
346349

350+
yield kinds.BASE, config_files[kinds.BASE]
351+
347352
# finally virtualenv configuration first trumping others
348353
yield kinds.SITE, config_files[kinds.SITE]
349354

tests/unit/test_configuration.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,18 @@ def test_user_loading(self) -> None:
2424
self.configuration.load()
2525
assert self.configuration.get_value("test.hello") == "2"
2626

27-
def test_site_loading(self) -> None:
28-
self.patch_configuration(kinds.SITE, {"test.hello": "3"})
27+
def test_base_loading(self) -> None:
28+
self.patch_configuration(kinds.BASE, {"test.hello": "3"})
2929

3030
self.configuration.load()
3131
assert self.configuration.get_value("test.hello") == "3"
3232

33+
def test_site_loading(self) -> None:
34+
self.patch_configuration(kinds.SITE, {"test.hello": "4"})
35+
36+
self.configuration.load()
37+
assert self.configuration.get_value("test.hello") == "4"
38+
3339
def test_environment_config_loading(self, monkeypatch: pytest.MonkeyPatch) -> None:
3440
contents = """
3541
[test]
@@ -107,6 +113,15 @@ def test_no_such_key_error_message_missing_option(self) -> None:
107113
with pytest.raises(ConfigurationError, match=pat):
108114
self.configuration.get_value("global.index-url")
109115

116+
def test_overrides_normalization(self) -> None:
117+
# Check that normalized names are used in precedence calculations.
118+
# Reminder: USER has higher precedence than GLOBAL.
119+
self.patch_configuration(kinds.USER, {"test.hello-world": "1"})
120+
self.patch_configuration(kinds.GLOBAL, {"test.hello_world": "0"})
121+
self.configuration.load()
122+
123+
assert self.configuration.get_value("test.hello_world") == "1"
124+
110125

111126
class TestConfigurationPrecedence(ConfigurationMixin):
112127
# Tests for methods to that determine the order of precedence of
@@ -133,6 +148,13 @@ def test_env_overides_global(self) -> None:
133148

134149
assert self.configuration.get_value("test.hello") == "0"
135150

151+
def test_site_overides_base(self) -> None:
152+
self.patch_configuration(kinds.BASE, {"test.hello": "2"})
153+
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
154+
self.configuration.load()
155+
156+
assert self.configuration.get_value("test.hello") == "1"
157+
136158
def test_site_overides_user(self) -> None:
137159
self.patch_configuration(kinds.USER, {"test.hello": "2"})
138160
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
@@ -147,6 +169,13 @@ def test_site_overides_global(self) -> None:
147169

148170
assert self.configuration.get_value("test.hello") == "1"
149171

172+
def test_base_overides_user(self) -> None:
173+
self.patch_configuration(kinds.USER, {"test.hello": "2"})
174+
self.patch_configuration(kinds.BASE, {"test.hello": "1"})
175+
self.configuration.load()
176+
177+
assert self.configuration.get_value("test.hello") == "1"
178+
150179
def test_user_overides_global(self) -> None:
151180
self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
152181
self.patch_configuration(kinds.USER, {"test.hello": "2"})

tests/unit/test_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ def test_venv_config_file_found(self, monkeypatch: pytest.MonkeyPatch) -> None:
588588
for _, val in cp.iter_config_files():
589589
files.extend(val)
590590

591-
assert len(files) == 4
591+
assert len(files) == 5
592592

593593
@pytest.mark.parametrize(
594594
"args, expect",

0 commit comments

Comments
 (0)