diff --git a/docs/configuration.md b/docs/configuration.md index b81edd26513..6204d450e9b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -46,6 +46,8 @@ cache-dir = "/path/to/cache/directory" virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = true +virtualenvs.options.no-pip = false +virtualenvs.options.no-setuptools = false virtualenvs.options.system-site-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs virtualenvs.prefer-active-python = false @@ -148,11 +150,6 @@ Defaults to `true`. If set to `false`, poetry will install dependencies into the current python environment. -{{% note %}} -When setting this configuration to `false`, the Python environment used must have `pip` -installed and available. -{{% /note %}} - ### `virtualenvs.in-project` **Type**: boolean @@ -181,6 +178,37 @@ Defaults to `{cache-dir}/virtualenvs` (`{cache-dir}\virtualenvs` on Windows). If set to `true` the `--always-copy` parameter is passed to `virtualenv` on creation of the venv. Thus all needed files are copied into the venv instead of symlinked. Defaults to `false`. +### `virtualenvs.options.no-pip` + +**Type**: boolean + +If set to `true` the `--no-pip` parameter is passed to `virtualenv` on creation of the venv. This means when a new +virtual environment is created, `pip` will not be installed in the environment. +Defaults to `false`. + +{{% note %}} +Poetry, for its internal operations, uses the `pip` wheel embedded in the `virtualenv` package installed as a dependency +in Poetry's runtime environment. If a user runs `poetry run pip` when this option is set to `true`, the `pip` the +embedded instance of `pip` is used. + +You can safely set this, along with `no-setuptools`, to `true`, if you desire a virtual environment with no additional +packages. This is desirable for production environments. +{{% /note %}} + +### `virtualenvs.options.no-setuptools` + +**Type**: boolean + +If set to `true` the `--no-setuptools` parameter is passed to `virtualenv` on creation of the venv. This means when a new +virtual environment is created, `setuptools` will not be installed in the environment. Poetry, for its internal operations, +does not require `setuptools` and this can safely be set to `true`. +Defaults to `false`. + +{{% warning %}} +Some development tools like IDEs, make an assumption that `setuptools` (and other) packages are always present and +available within a virtual environment. This can cause some features in these tools to not work as expected. +{{% /warning %}} + ### `virtualenvs.options.system-site-packages` **Type**: boolean diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index ec6f34bb338..254f7a218ff 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -37,7 +37,16 @@ class Config: "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), - "options": {"always-copy": False, "system-site-packages": False}, + "options": { + "always-copy": False, + "system-site-packages": False, + # we default to False here in order to prevent development environment + # breakages for IDEs etc. as when working in these environments + # assumptions are often made about virtual environments having pip and + # setuptools. + "no-pip": False, + "no-setuptools": False, + }, "prefer-active-python": False, }, "experimental": {"new-installer": True}, diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 058e0560284..a177436de4f 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -72,6 +72,16 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any, Any]]: boolean_normalizer, False, ), + "virtualenvs.options.no-pip": ( + boolean_validator, + boolean_normalizer, + False, + ), + "virtualenvs.options.no-setuptools": ( + boolean_validator, + boolean_normalizer, + False, + ), "virtualenvs.path": ( str, lambda val: str(Path(val)), diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index 656bbb63a47..c2c76c11b7d 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -465,7 +465,7 @@ def _pep517_metadata(cls, path: Path) -> PackageInfo: pass with ephemeral_environment( - with_pip=True, with_wheel=True, with_setuptools=True + flags={"no-pip": False, "no-setuptools": False, "no-wheel": False} ) as venv: # TODO: cache PEP 517 build environment corresponding to each project venv dest_dir = venv.path.parent / "dist" diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 544e5fe78ae..ee5e34dfa50 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -966,12 +966,6 @@ def create_venv( venv, executable=executable, flags=self._poetry.config.get("virtualenvs.options"), - # TODO: in a future version switch remove pip/setuptools/wheel - # poetry does not need them these exists today to not break developer - # environment assumptions - with_pip=True, - with_setuptools=True, - with_wheel=True, ) # venv detection: @@ -1834,9 +1828,6 @@ def _bin(self, bin: str) -> str: def ephemeral_environment( executable: str | Path | None = None, flags: dict[str, bool] = None, - with_pip: bool = False, - with_wheel: bool | None = None, - with_setuptools: bool | None = None, ) -> ContextManager[VirtualEnv]: with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv @@ -1845,9 +1836,6 @@ def ephemeral_environment( path=venv_dir.as_posix(), executable=executable, flags=flags, - with_pip=with_pip, - with_wheel=with_wheel, - with_setuptools=with_setuptools, ) yield VirtualEnv(venv_dir, venv_dir) diff --git a/tests/console/commands/env/test_use.py b/tests/console/commands/env/test_use.py index 34bbf9ad0da..ba26064765c 100644 --- a/tests/console/commands/env/test_use.py +++ b/tests/console/commands/env/test_use.py @@ -71,10 +71,12 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( mock_build_env.assert_called_with( venv_py37, executable="/usr/bin/python3.7", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) envs_file = TOMLFile(venv_cache / "envs.toml") diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 4d340ba8b33..b63f95327ee 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -56,6 +56,8 @@ def test_list_displays_default_value_if_not_set( virtualenvs.create = true virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.no-pip = false +virtualenvs.options.no-setuptools = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} virtualenvs.prefer-active-python = false @@ -80,6 +82,8 @@ def test_list_displays_set_get_setting( virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.no-pip = false +virtualenvs.options.no-setuptools = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} virtualenvs.prefer-active-python = false @@ -128,6 +132,8 @@ def test_list_displays_set_get_local_setting( virtualenvs.create = false virtualenvs.in-project = null virtualenvs.options.always-copy = false +virtualenvs.options.no-pip = false +virtualenvs.options.no-setuptools = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} virtualenvs.prefer-active-python = false diff --git a/tests/test_factory.py b/tests/test_factory.py index 8c623cc5932..3513b3034e6 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -299,6 +299,8 @@ def test_create_poetry_with_local_config(fixture_dir: FixtureDirGetter): assert not poetry.config.get("virtualenvs.in-project") assert not poetry.config.get("virtualenvs.create") assert not poetry.config.get("virtualenvs.options.always-copy") + assert not poetry.config.get("virtualenvs.options.no-pip") + assert not poetry.config.get("virtualenvs.options.no-setuptools") assert not poetry.config.get("virtualenvs.options.system-site-packages") diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index afc5aa9207a..4472674024a 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -19,6 +19,7 @@ from poetry.core.toml.file import TOMLFile from poetry.factory import Factory +from poetry.repositories.installed_repository import InstalledRepository from poetry.utils._compat import WINDOWS from poetry.utils.env import GET_BASE_PREFIX from poetry.utils.env import EnvCommandError @@ -196,10 +197,12 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file( m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", executable="/usr/bin/python3.7", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) envs_file = TOMLFile(Path(tmp_dir) / "envs.toml") @@ -331,10 +334,12 @@ def test_activate_activates_different_virtualenv_with_envs_file( m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.6", executable="/usr/bin/python3.6", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) assert envs_file.exists() @@ -392,10 +397,12 @@ def test_activate_activates_recreates_for_different_patch( build_venv_m.assert_called_with( Path(tmp_dir) / f"{venv_name}-py3.7", executable="/usr/bin/python3.7", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) remove_venv_m.assert_called_with(Path(tmp_dir) / f"{venv_name}-py3.7") @@ -827,10 +834,12 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.7", executable="python3", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) @@ -858,10 +867,12 @@ def test_create_venv_tries_to_find_a_compatible_python_executable_using_specific m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.9", executable="python3.9", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) @@ -948,10 +959,12 @@ def test_create_venv_uses_patch_version_to_detect_compatibility( m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py{version.major}.{version.minor}", executable=None, - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) @@ -987,10 +1000,12 @@ def test_create_venv_uses_patch_version_to_detect_compatibility_with_executable( m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py{version.major}.{version.minor - 1}", executable=f"python{version.major}.{version.minor - 1}", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) @@ -1055,10 +1070,12 @@ def test_activate_with_in_project_setting_does_not_fail_if_no_venvs_dir( m.assert_called_with( poetry.file.parent / ".venv", executable="/usr/bin/python3.7", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, ) envs_file = TOMLFile(Path(tmp_dir) / "virtualenvs" / "envs.toml") @@ -1106,6 +1123,36 @@ def test_env_system_packages(tmp_path: Path, poetry: Poetry): assert "include-system-site-packages = true" in pyvenv_cfg.read_text() +@pytest.mark.parametrize( + ("flags", "packages"), + [ + ({"no-pip": False}, {"pip", "wheel"}), + ({"no-pip": False, "no-wheel": True}, {"pip"}), + ({"no-pip": True}, set()), + ({"no-setuptools": False}, {"setuptools"}), + ({"no-setuptools": True}, set()), + ({"no-pip": True, "no-setuptools": False}, {"setuptools"}), + ({"no-wheel": False}, {"wheel"}), + ({}, set()), + ], +) +def test_env_no_pip( + tmp_path: Path, poetry: Poetry, flags: dict[str, bool], packages: set[str] +): + venv_path = tmp_path / "venv" + EnvManager(poetry).build_venv(path=venv_path, flags=flags) + env = VirtualEnv(venv_path) + installed_repository = InstalledRepository.load(env=env, with_dependencies=True) + installed_packages = { + package.name + for package in installed_repository.packages + # workaround for BSD test environments + if package.name != "sqlite3" + } + + assert installed_packages == packages + + def test_env_finds_the_correct_executables(tmp_dir: str, manager: EnvManager): venv_path = Path(tmp_dir) / "Virtual Env" manager.build_venv(str(venv_path), with_pip=True) @@ -1256,10 +1303,12 @@ def test_create_venv_accepts_fallback_version_w_nonzero_patchlevel( m.assert_called_with( config_virtualenvs_path / f"{venv_name}-py3.5", executable="python3.5", - flags={"always-copy": False, "system-site-packages": False}, - with_pip=True, - with_setuptools=True, - with_wheel=True, + flags={ + "always-copy": False, + "system-site-packages": False, + "no-pip": False, + "no-setuptools": False, + }, )