diff --git a/docs/configuration.md b/docs/configuration.md index c93322310a1..b81edd26513 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -48,6 +48,7 @@ virtualenvs.in-project = null virtualenvs.options.always-copy = true virtualenvs.options.system-site-packages = false virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs +virtualenvs.prefer-active-python = false ``` ## Displaying a single configuration setting @@ -188,6 +189,13 @@ Give the virtual environment access to the system site-packages directory. Applies on virtualenv creation. Defaults to `false`. +### `virtualenvs.prefer-active-python` (experimental) + +**Type**: boolean + +Use currently activated Python version to create a new venv. +Defaults to `false`, which means Python version used during Poetry installation is used. + ### `repositories.` **Type**: string diff --git a/docs/managing-environments.md b/docs/managing-environments.md index 632db282917..605ef9940cc 100644 --- a/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -18,7 +18,7 @@ To achieve this, it will first check if it's currently running inside a virtual If it is, it will use it directly without creating a new one. But if it's not, it will use one that it has already created or create a brand new one for you. -By default, Poetry will try to use the currently activated Python version +By default, Poetry will try to use the Python version used during Poetry's installation to create the virtual environment for the current project. However, for various reasons, this Python version might not be compatible @@ -26,6 +26,21 @@ with the `python` requirement of the project. In this case, Poetry will try to find one that is and use it. If it's unable to do so then you will be prompted to activate one explicitly, see [Switching environments](#switching-between-environments). +{{% note %}} +If you use a tool like [pyenv](https://github.com/pyenv/pyenv) to manage different Python versions, +you can set the experimental `virtualenvs.prefer-active-python` option to `true`. Poetry +than will try to find the current `python` of your shell. + +For instance, if your project requires a newer Python than is available with +your system, a standard workflow would be: + +```bash +pyenv install 3.9.8 +pyenv local 3.9.8 # Activate Python 3.9 for the current project +poetry install +``` +{{% /note %}} + ## Switching between environments Sometimes this might not be feasible for your system, especially Windows where `pyenv` diff --git a/src/poetry/config/config.py b/src/poetry/config/config.py index 636fa2097dd..bf4898bfdd5 100644 --- a/src/poetry/config/config.py +++ b/src/poetry/config/config.py @@ -38,6 +38,7 @@ class Config: "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), "options": {"always-copy": False, "system-site-packages": False}, + "prefer-active-python": False, }, "experimental": {"new-installer": True}, "installer": {"parallel": True, "max-workers": None}, @@ -138,6 +139,7 @@ def _get_normalizer(name: str) -> Callable: "virtualenvs.in-project", "virtualenvs.options.always-copy", "virtualenvs.options.system-site-packages", + "virtualenvs.options.prefer-active-python", "experimental.new-installer", "installer.parallel", }: diff --git a/src/poetry/console/commands/config.py b/src/poetry/console/commands/config.py index 96df3cd4514..785abe361fe 100644 --- a/src/poetry/console/commands/config.py +++ b/src/poetry/console/commands/config.py @@ -78,6 +78,11 @@ def unique_config_values(self) -> Dict[str, Tuple[Any, Any, Any]]: lambda val: str(Path(val)), str(Path(CACHE_DIR) / "virtualenvs"), ), + "virtualenvs.prefer-active-python": ( + boolean_validator, + boolean_normalizer, + False, + ), "experimental.new-installer": ( boolean_validator, boolean_normalizer, diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 5eadf3cdfd2..3c18aaa62e1 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -30,6 +30,7 @@ import tomlkit import virtualenv +from cleo.io.outputs.output import Verbosity from packaging.tags import Tag from packaging.tags import interpreter_name from packaging.tags import interpreter_version @@ -456,6 +457,30 @@ class EnvManager: def __init__(self, poetry: "Poetry") -> None: self._poetry = poetry + def _detect_active_python(self, io: "IO") -> str: + executable = None + + try: + io.write_line( + "Trying to detect current active python executable as specified in the config.", + verbosity=Verbosity.VERBOSE, + ) + executable = decode( + subprocess.check_output( + list_to_shell_command( + ["python", "-c", '"import sys; print(sys.executable)"'] + ), + shell=True, + ).strip() + ) + io.write_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE) + except CalledProcessError: + io.write_line( + "Unable to detect the current active python executable. Falling back to default.", + verbosity=Verbosity.VERBOSE, + ) + return executable + def activate(self, python: str, io: "IO") -> "Env": venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: @@ -784,6 +809,12 @@ def create_venv( create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") venv_path = self._poetry.config.get("virtualenvs.path") + prefer_active_python = self._poetry.config.get( + "virtualenvs.prefer-active-python" + ) + + if not executable and prefer_active_python: + executable = self._detect_active_python(io) if root_venv: venv_path = cwd / ".venv" @@ -820,7 +851,7 @@ def create_venv( # If an executable has been specified, we stop there # and notify the user of the incompatibility. # Otherwise, we try to find a compatible Python version. - if executable: + if executable and not prefer_active_python: raise NoCompatiblePythonVersionFound( self._poetry.package.python_versions, python_patch ) diff --git a/tests/console/commands/test_config.py b/tests/console/commands/test_config.py index 3278b19a170..f515eb4fc67 100644 --- a/tests/console/commands/test_config.py +++ b/tests/console/commands/test_config.py @@ -56,6 +56,7 @@ def test_list_displays_default_value_if_not_set( virtualenvs.options.always-copy = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} +virtualenvs.prefer-active-python = false """ assert expected == tester.io.fetch_output() @@ -79,6 +80,7 @@ def test_list_displays_set_get_setting( virtualenvs.options.always-copy = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} +virtualenvs.prefer-active-python = false """ assert config.set_config_source.call_count == 0 @@ -126,6 +128,7 @@ def test_list_displays_set_get_local_setting( virtualenvs.options.always-copy = false virtualenvs.options.system-site-packages = false virtualenvs.path = {venv_path} # {config_cache_dir / 'virtualenvs'} +virtualenvs.prefer-active-python = false """ assert config.set_config_source.call_count == 1