Skip to content

Commit

Permalink
Merge pull request pypa#11487 from pelson/feature/base-prefix-config
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr authored Jan 31, 2023
2 parents 2c09e9c + 8d34a11 commit 56e5fa3
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 11 deletions.
21 changes: 16 additions & 5 deletions docs/html/topics/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ and how they are related to pip's various command line options.

## Configuration Files

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

pip has 3 "levels" of configuration files:
pip has 4 "levels" of configuration files:

- `global`: system-wide configuration file, shared across users.
- `user`: per-user configuration file.
- `global`: system-wide configuration file, shared across all users.
- `user`: per-user configuration file, shared across all environments.
- `base` : per-base environment configuration file, shared across all virtualenvs with the same base. (available since pip 23.0)
- `site`: per-environment configuration file; i.e. per-virtualenv.

### Location
Expand All @@ -47,6 +48,9 @@ User
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
Base
: {file}`\{sys.base_prefix\}/pip.conf`
Site
: {file}`$VIRTUAL_ENV/pip.conf`
```
Expand All @@ -63,6 +67,9 @@ User
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
Base
: {file}`\{sys.base_prefix\}/pip.conf`
Site
: {file}`$VIRTUAL_ENV/pip.conf`
```
Expand All @@ -81,6 +88,9 @@ User
The legacy "per-user" configuration file is also loaded, if it exists: {file}`%HOME%\\pip\\pip.ini`
Base
: {file}`\{sys.base_prefix\}\\pip.ini`
Site
: {file}`%VIRTUAL_ENV%\\pip.ini`
```
Expand All @@ -102,6 +112,7 @@ order:
- `PIP_CONFIG_FILE`, if given.
- Global
- User
- Base
- Site

Each file read overrides any values read from previous files, so if the
Expand Down
1 change: 1 addition & 0 deletions news/9752.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In the case of virtual environments, configuration files are now also included from the base installation.
18 changes: 15 additions & 3 deletions src/pip/_internal/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,20 @@
kinds = enum(
USER="user", # User Specific
GLOBAL="global", # System Wide
SITE="site", # [Virtual] Environment Specific
BASE="base", # Base environment specific (e.g. for all venvs with the same base)
SITE="site", # Environment Specific (e.g. per venv)
ENV="env", # from PIP_CONFIG_FILE
ENV_VAR="env-var", # from Environment Variables
)
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
OVERRIDE_ORDER = (
kinds.GLOBAL,
kinds.USER,
kinds.BASE,
kinds.SITE,
kinds.ENV,
kinds.ENV_VAR,
)
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.BASE, kinds.SITE

logger = getLogger(__name__)

Expand Down Expand Up @@ -70,6 +78,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
]

base_config_file = os.path.join(sys.base_prefix, CONFIG_BASENAME)
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
legacy_config_file = os.path.join(
os.path.expanduser("~"),
Expand All @@ -78,6 +87,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
)
new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
return {
kinds.BASE: [base_config_file],
kinds.GLOBAL: global_config_files,
kinds.SITE: [site_config_file],
kinds.USER: [legacy_config_file, new_config_file],
Expand Down Expand Up @@ -344,6 +354,8 @@ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
# The legacy config file is overridden by the new config file
yield kinds.USER, config_files[kinds.USER]

yield kinds.BASE, config_files[kinds.BASE]

# finally virtualenv configuration first trumping others
yield kinds.SITE, config_files[kinds.SITE]

Expand Down
33 changes: 31 additions & 2 deletions tests/unit/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ def test_user_loading(self) -> None:
self.configuration.load()
assert self.configuration.get_value("test.hello") == "2"

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

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

def test_site_loading(self) -> None:
self.patch_configuration(kinds.SITE, {"test.hello": "4"})

self.configuration.load()
assert self.configuration.get_value("test.hello") == "4"

def test_environment_config_loading(self, monkeypatch: pytest.MonkeyPatch) -> None:
contents = """
[test]
Expand Down Expand Up @@ -107,6 +113,15 @@ def test_no_such_key_error_message_missing_option(self) -> None:
with pytest.raises(ConfigurationError, match=pat):
self.configuration.get_value("global.index-url")

def test_overrides_normalization(self) -> None:
# Check that normalized names are used in precedence calculations.
# Reminder: USER has higher precedence than GLOBAL.
self.patch_configuration(kinds.USER, {"test.hello-world": "1"})
self.patch_configuration(kinds.GLOBAL, {"test.hello_world": "0"})
self.configuration.load()

assert self.configuration.get_value("test.hello_world") == "1"


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

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

def test_site_overides_base(self) -> None:
self.patch_configuration(kinds.BASE, {"test.hello": "2"})
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
self.configuration.load()

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

def test_site_overides_user(self) -> None:
self.patch_configuration(kinds.USER, {"test.hello": "2"})
self.patch_configuration(kinds.SITE, {"test.hello": "1"})
Expand All @@ -147,6 +169,13 @@ def test_site_overides_global(self) -> None:

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

def test_base_overides_user(self) -> None:
self.patch_configuration(kinds.USER, {"test.hello": "2"})
self.patch_configuration(kinds.BASE, {"test.hello": "1"})
self.configuration.load()

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

def test_user_overides_global(self) -> None:
self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
self.patch_configuration(kinds.USER, {"test.hello": "2"})
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ def test_venv_config_file_found(self, monkeypatch: pytest.MonkeyPatch) -> None:
for _, val in cp.iter_config_files():
files.extend(val)

assert len(files) == 4
assert len(files) == 5

@pytest.mark.parametrize(
"args, expect",
Expand Down

0 comments on commit 56e5fa3

Please sign in to comment.