From 9a6e18d9559e26c89cccf27134c96e091c9a8d3d Mon Sep 17 00:00:00 2001 From: finswimmer Date: Mon, 14 Jun 2021 06:25:18 +0200 Subject: [PATCH 1/5] add flag `--use-env` to specify the python executable to use --- poetry/console/application.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/poetry/console/application.py b/poetry/console/application.py index 42cc4377043..ca9f8c1ace8 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -265,7 +265,7 @@ def configure_env( poetry = command.poetry env_manager = EnvManager(poetry) - env = env_manager.create_venv(io) + env = env_manager.create_venv(io, executable=event.io.input.option("use-env")) if env.is_venv() and io.is_verbose(): io.write_line(f"Using virtualenv: {env.path}") @@ -327,6 +327,11 @@ def _default_definition(self) -> "Definition": definition.add_option( Option("--no-plugins", flag=True, description="Disables plugins.") ) + definition.add_option( + Option( + "--use-env", flag=False, description="The python executable to use." + ), + ) return definition From efeb6951af472f4b618f0dd2a7d88e0bb8c2b640 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Mon, 14 Jun 2021 12:47:37 +0200 Subject: [PATCH 2/5] pass path to executable for getting a specific venv --- poetry/utils/env.py | 47 ++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index d35fb221313..d4eb3dab5bb 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -13,6 +13,7 @@ from contextlib import contextmanager from copy import deepcopy +from functools import lru_cache from pathlib import Path from subprocess import CalledProcessError from typing import Any @@ -427,6 +428,26 @@ class EnvManager: def __init__(self, poetry: Poetry) -> None: self._poetry = poetry + @lru_cache() + def _python_version(self, executable: str) -> str: + try: + python_version = decode( + subprocess.check_output( + list_to_shell_command( + [ + executable, + "-c", + "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", + ] + ), + shell=True, + ) + ) + except CalledProcessError as e: + raise EnvCommandError(e) + + return python_version + def activate(self, python: str, io: IO) -> "Env": venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: @@ -548,11 +569,18 @@ def deactivate(self, io: IO) -> None: envs_file.write(envs) - def get(self, reload: bool = False) -> Union["VirtualEnv", "SystemEnv"]: + def get( + self, reload: bool = False, executable: Optional[str] = None + ) -> Union["VirtualEnv", "SystemEnv"]: if self._env is not None and not reload: return self._env - python_minor = ".".join([str(v) for v in sys.version_info[:2]]) + if executable: + python_version = self._python_version(executable) + python_version = Version.parse(python_version.strip()) + python_minor = f"{python_version.major}.{python_version.minor}" + else: + python_minor = ".".join([str(v) for v in sys.version_info[:2]]) venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: @@ -743,7 +771,7 @@ def create_venv( return self._env cwd = self._poetry.file.parent - env = self.get(reload=True) + env = self.get(reload=True, executable=executable) if not env.is_sane(): force = True @@ -769,18 +797,7 @@ def create_venv( python_patch = ".".join([str(v) for v in sys.version_info[:3]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]]) if executable: - python_patch = decode( - subprocess.check_output( - list_to_shell_command( - [ - executable, - "-c", - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", - ] - ), - shell=True, - ).strip() - ) + python_patch = self._python_version(executable) python_minor = ".".join(python_patch.split(".")[:2]) supported_python = self._poetry.package.python_constraint From 3d054ca374a6b23e783b9ad525d7cb142acc8cec Mon Sep 17 00:00:00 2001 From: finswimmer Date: Mon, 14 Jun 2021 13:33:20 +0200 Subject: [PATCH 3/5] remove code duplication to get python version from executable --- poetry/utils/env.py | 78 +++++++++++---------------------------------- 1 file changed, 18 insertions(+), 60 deletions(-) diff --git a/poetry/utils/env.py b/poetry/utils/env.py index d4eb3dab5bb..95b6db3187c 100644 --- a/poetry/utils/env.py +++ b/poetry/utils/env.py @@ -429,7 +429,7 @@ def __init__(self, poetry: Poetry) -> None: self._poetry = poetry @lru_cache() - def _python_version(self, executable: str) -> str: + def _python_version(self, executable: str, precision: int) -> Version: try: python_version = decode( subprocess.check_output( @@ -437,7 +437,7 @@ def _python_version(self, executable: str) -> str: [ executable, "-c", - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", + f"\"import sys; print('.'.join([str(s) for s in sys.version_info[:{precision}]]))\"", ] ), shell=True, @@ -446,7 +446,7 @@ def _python_version(self, executable: str) -> str: except CalledProcessError as e: raise EnvCommandError(e) - return python_version + return Version.parse(python_version.strip()) def activate(self, python: str, io: IO) -> "Env": venv_path = self._poetry.config.get("virtualenvs.path") @@ -468,23 +468,7 @@ def activate(self, python: str, io: IO) -> "Env": # Executable in PATH or full executable path pass - try: - python_version = decode( - subprocess.check_output( - list_to_shell_command( - [ - python, - "-c", - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", - ] - ), - shell=True, - ) - ) - except CalledProcessError as e: - raise EnvCommandError(e) - - python_version = Version.parse(python_version.strip()) + python_version = self._python_version(python, precision=3) minor = f"{python_version.major}.{python_version.minor}" patch = python_version.text @@ -576,8 +560,7 @@ def get( return self._env if executable: - python_version = self._python_version(executable) - python_version = Version.parse(python_version.strip()) + python_version = self._python_version(executable, precision=3) python_minor = f"{python_version.major}.{python_version.minor}" else: python_minor = ".".join([str(v) for v in sys.version_info[:2]]) @@ -721,23 +704,7 @@ def remove(self, python: str) -> "Env": # Executable in PATH or full executable path pass - try: - python_version = decode( - subprocess.check_output( - list_to_shell_command( - [ - python, - "-c", - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", - ] - ), - shell=True, - ) - ) - except CalledProcessError as e: - raise EnvCommandError(e) - - python_version = Version.parse(python_version.strip()) + python_version = self._python_version(python, precision=3) minor = f"{python_version.major}.{python_version.minor}" name = f"{base_env_name}-py{minor}" @@ -794,14 +761,17 @@ def create_venv( if not name: name = self._poetry.package.name - python_patch = ".".join([str(v) for v in sys.version_info[:3]]) - python_minor = ".".join([str(v) for v in sys.version_info[:2]]) if executable: - python_patch = self._python_version(executable) - python_minor = ".".join(python_patch.split(".")[:2]) + python_patch = self._python_version(executable, precision=3) + python_minor = f"{python_patch.major}.{python_patch.minor}" + else: + python_patch = Version.parse( + ".".join([str(v) for v in sys.version_info[:3]]) + ) + python_minor = ".".join([str(v) for v in sys.version_info[:2]]) supported_python = self._poetry.package.python_constraint - if not supported_python.allows(Version.parse(python_patch)): + if not supported_python.allows(python_patch): # The currently activated or chosen Python version # is not compatible with the Python constraint specified # for the project. @@ -810,7 +780,7 @@ def create_venv( # Otherwise, we try to find a compatible Python version. if executable: raise NoCompatiblePythonVersionFound( - self._poetry.package.python_versions, python_patch + self._poetry.package.python_versions, str(python_patch) ) io.write_line( @@ -843,29 +813,17 @@ def create_venv( io.write_line(f"Trying {python}") try: - python_patch = decode( - subprocess.check_output( - list_to_shell_command( - [ - python, - "-c", - "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", - ] - ), - stderr=subprocess.STDOUT, - shell=True, - ).strip() - ) + python_patch = self._python_version(python, precision=3) except CalledProcessError: continue if not python_patch: continue - if supported_python.allows(Version.parse(python_patch)): + if supported_python.allows(python_patch): io.write_line(f"Using {python} ({python_patch})") executable = python - python_minor = ".".join(python_patch.split(".")[:2]) + python_minor = f"{python_patch.major}.{python_patch.minor}" break if not executable: From 3caee1674d5a1c62cc84d4aab5279d344191d111 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Tue, 15 Jun 2021 06:16:27 +0200 Subject: [PATCH 4/5] allow version number only as parameter, the same way like it is done for `poetry env use` --- poetry/console/application.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/poetry/console/application.py b/poetry/console/application.py index ca9f8c1ace8..f5fe993523d 100644 --- a/poetry/console/application.py +++ b/poetry/console/application.py @@ -21,6 +21,7 @@ from cleo.io.outputs.output import Output from poetry.__version__ import __version__ +from poetry.core.semver.version import Version from .command_loader import CommandLoader from .commands.command import Command @@ -264,8 +265,17 @@ def configure_env( io = event.io poetry = command.poetry + try: + python_version = Version.parse(event.io.input.option("use-env")) + python = f"python{python_version.major}" + if python_version.precision > 1: + python += f".{python_version.minor}" + except ValueError: + # Executable in PATH or full executable path + python = event.io.input.option("use-env") + env_manager = EnvManager(poetry) - env = env_manager.create_venv(io, executable=event.io.input.option("use-env")) + env = env_manager.create_venv(io, executable=python) if env.is_venv() and io.is_verbose(): io.write_line(f"Using virtualenv: {env.path}") From 3dd71eed788477fc56d0f7cb7496aa272c810426 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Tue, 15 Jun 2021 06:33:56 +0200 Subject: [PATCH 5/5] update documentation about the `--use-env` parameter --- docs/cli.md | 2 ++ docs/managing-environments.md | 36 ++++++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 0a1b8a265af..45f1f0f9abf 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -27,6 +27,8 @@ then `--help` combined with any of those can give you more information. * `--no-ansi`: Disable ANSI output. * `--version (-V)`: Display this application version. * `--no-interaction (-n)`: Do not ask any interactive question. +* `--no-plugins` Disables plugins. +* `--use-env=USE-ENV` The python executable to use. ## new diff --git a/docs/managing-environments.md b/docs/managing-environments.md index d9b728825a1..fe2713cbc5c 100644 --- a/docs/managing-environments.md +++ b/docs/managing-environments.md @@ -18,8 +18,8 @@ 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 -to create the virtual environment for the current project. +By default, Poetry will use the Python used during installation to create the virtual environment +for the current project. However, for various reasons, this Python version might not be compatible with the `python` requirement of the project. In this case, Poetry will try @@ -36,7 +36,7 @@ would be: ```bash pyenv install 2.7.15 pyenv local 2.7.15 # Activate Python 2.7 for the current project -poetry install +poetry install --use-env python2.7 ``` {{% /note %}} @@ -70,6 +70,36 @@ special `system` Python version to retrieve the default behavior: poetry env use system ``` +# Multiple environments + +You can use `poetry env use` to create multiple environments for your project with different interpreters. `poetry env use` +will store the information about the used currently used python version in `{cachedir}/virtualenvs/env.toml` by default. + +Sometimes it might be feasible to run a poetry command like `poetry shell` or `poetry run` on an environment without +switching the default. For this purpose the global option `--use-env` exists. + +Let's assume we already have an evironment for python3.7 and want to add another one for python3.8 without setting it +as the new default one: + +```bash +poetry install --use-env python3.8 +``` + +We can even open a new shell for this environment: + +```bash +poetry shell --use-env python3.8 +``` + +Or run a specific command: + +```bash +poetry run --use-env python3.8 python --version +``` + +Just like `poetry env use` the `--use-env` parameter expects a full path to an executable, a python executable in `$PATH` +or minor Python version. + ## Displaying the environment information If you want to get basic information about the currently activated virtual environment,