From b777bcda819961092ae87ca7512e0b27c576d19c Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 5 Oct 2022 18:19:28 +0200 Subject: [PATCH 1/3] Add the "base" config level concept, to allow configuration of all virtual environments sharing the same base. The new functionality serves a use case which was not previously possible with pip configuration files, namely the situation where you have a base Python installation and want to influence the pip configuration for all derivative virtual environments *without* changing the config for all other environments on a machine (global), or for all other environment run by the same user (user). Concretely, this could be used for a centrally managed network mounted filesystem based Python installation, from which multiple users can build virtual environments and inside which a specific pip configuration is needed (e.g. an index URL). --- docs/html/topics/configuration.md | 27 ++++++++++++++++-------- news/9752.feature.rst | 1 + src/pip/_internal/configuration.py | 18 +++++++++++++--- tests/unit/test_configuration.py | 33 ++++++++++++++++++++++++++++-- tests/unit/test_options.py | 2 +- 5 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 news/9752.feature.rst diff --git a/docs/html/topics/configuration.md b/docs/html/topics/configuration.md index e4aafcd2b98..c544d0c700c 100644 --- a/docs/html/topics/configuration.md +++ b/docs/html/topics/configuration.md @@ -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. (added in pip `v23`) - `site`: per-environment configuration file; i.e. per-virtualenv. ### Location @@ -47,8 +48,11 @@ 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` +: {file}`\{sys.prefix\}/pip.conf` ``` ```{tab} MacOS @@ -63,8 +67,11 @@ 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` +: {file}`\{sys.prefix\}/pip.conf` ``` ```{tab} Windows @@ -81,8 +88,11 @@ 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` +: {file}`\{sys.prefix\}\\pip.ini` ``` ### `PIP_CONFIG_FILE` @@ -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 diff --git a/news/9752.feature.rst b/news/9752.feature.rst new file mode 100644 index 00000000000..d515267be21 --- /dev/null +++ b/news/9752.feature.rst @@ -0,0 +1 @@ +In the case of virtual environments, configuration files are now also included from the base installation. diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index 8fd46c9b8e0..6cce8bcbcce 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -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__) @@ -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("~"), @@ -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], @@ -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] diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index c6b44d45aad..b0d655d8fb6 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -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] @@ -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 @@ -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"}) @@ -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"}) diff --git a/tests/unit/test_options.py b/tests/unit/test_options.py index ada5e1c3076..39396512a97 100644 --- a/tests/unit/test_options.py +++ b/tests/unit/test_options.py @@ -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", From 93ade8586e3eb3792250f1f0eb924c1664bc4df7 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 16 Nov 2022 21:17:33 +0100 Subject: [PATCH 2/3] Update docs/html/topics/configuration.md Co-authored-by: Tzu-ping Chung --- docs/html/topics/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/html/topics/configuration.md b/docs/html/topics/configuration.md index c544d0c700c..ddeee0826a4 100644 --- a/docs/html/topics/configuration.md +++ b/docs/html/topics/configuration.md @@ -26,7 +26,7 @@ pip has 4 "levels" of configuration files: - `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. (added in pip `v23`) +- `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 From 81d6053ee3ce5c4a8b0572cb80c542ab9f7e461e Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 17 Nov 2022 17:25:30 +0100 Subject: [PATCH 3/3] Use the VIRTUAL_ENV environment variable in the configuration documentation This follows the discussion in https://github.com/pypa/pip/pull/11487/files#r988625394, that despite the VIRTUAL_ENV environment variable not being the technically correct value, it is more readily understood by readers than ``sys.prefix``. --- docs/html/topics/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/html/topics/configuration.md b/docs/html/topics/configuration.md index ddeee0826a4..521bc9af4b9 100644 --- a/docs/html/topics/configuration.md +++ b/docs/html/topics/configuration.md @@ -52,7 +52,7 @@ Base : {file}`\{sys.base_prefix\}/pip.conf` Site -: {file}`\{sys.prefix\}/pip.conf` +: {file}`$VIRTUAL_ENV/pip.conf` ``` ```{tab} MacOS @@ -71,7 +71,7 @@ Base : {file}`\{sys.base_prefix\}/pip.conf` Site -: {file}`\{sys.prefix\}/pip.conf` +: {file}`$VIRTUAL_ENV/pip.conf` ``` ```{tab} Windows @@ -92,7 +92,7 @@ Base : {file}`\{sys.base_prefix\}\\pip.ini` Site -: {file}`\{sys.prefix\}\\pip.ini` +: {file}`%VIRTUAL_ENV%\\pip.ini` ``` ### `PIP_CONFIG_FILE`