From 82e9b4e53f0e84275d1cf4d71852952a91c8d9e3 Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:34:44 +0100 Subject: [PATCH 1/3] feat: turn EnvManager method into staticmethod which detect active python --- src/poetry/utils/env.py | 42 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 408d0c0f232..0877a9a17eb 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -20,7 +20,6 @@ from subprocess import CalledProcessError from typing import TYPE_CHECKING from typing import Any -from typing import cast import packaging.tags import tomlkit @@ -521,7 +520,8 @@ def __init__(self, poetry: Poetry, io: None | IO = None) -> None: self._poetry = poetry self._io = io or NullIO() - def _full_python_path(self, python: str) -> str: + @staticmethod + def _full_python_path(python: str) -> str: try: executable = decode( subprocess.check_output( @@ -536,23 +536,23 @@ def _full_python_path(self, python: str) -> str: return executable - def _detect_active_python(self) -> str | None: + @staticmethod + def _detect_active_python(io: None | IO = None) -> str | None: + io = io or NullIO() executable = None try: - self._io.write_error_line( + io.write_error_line( ( "Trying to detect current active python executable as specified in" " the config." ), verbosity=Verbosity.VERBOSE, ) - executable = self._full_python_path("python") - self._io.write_error_line( - f"Found: {executable}", verbosity=Verbosity.VERBOSE - ) + executable = EnvManager._full_python_path("python") + io.write_error_line(f"Found: {executable}", verbosity=Verbosity.VERBOSE) except EnvCommandError: - self._io.write_error_line( + io.write_error_line( ( "Unable to detect the current active python executable. Falling" " back to default." @@ -561,11 +561,16 @@ def _detect_active_python(self) -> str | None: ) return executable - def _get_python_version(self) -> tuple[int, int, int]: - version_info = tuple(sys.version_info[:3]) + @staticmethod + def get_python_version( + precious: int = 3, + prefer_active_python: bool = False, + io: None | IO = None, + ) -> Version: + version = ".".join(str(v) for v in sys.version_info[:precious]) - if self._poetry.config.get("virtualenvs.prefer-active-python"): - executable = self._detect_active_python() + if prefer_active_python: + executable = EnvManager._detect_active_python(io) if executable: python_patch = decode( @@ -577,9 +582,9 @@ def _get_python_version(self) -> tuple[int, int, int]: ).strip() ) - version_info = tuple(int(v) for v in python_patch.split(".")[:3]) + version = ".".join(str(v) for v in python_patch.split(".")[:precious]) - return cast("tuple[int, int, int]", version_info) + return Version.parse(version) def activate(self, python: str) -> Env: venv_path = self._poetry.config.virtualenvs_path @@ -692,7 +697,12 @@ def get(self, reload: bool = False) -> Env: if self._env is not None and not reload: return self._env - python_minor = ".".join([str(v) for v in self._get_python_version()[:2]]) + prefer_active_python = self._poetry.config.get( + "virtualenvs.prefer-active-python" + ) + python_minor = self.get_python_version( + precious=2, prefer_active_python=prefer_active_python, io=self._io + ).to_string() venv_path = self._poetry.config.virtualenvs_path From 6136140c32f1fadebc5e5a1ccc3c2ab6b16cb42f Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:39:44 +0100 Subject: [PATCH 2/3] feat: take `virtualenvs.prefer-active-python` into account on `poetry init` --- src/poetry/console/commands/init.py | 17 +++++++---- tests/console/commands/test_init.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 92cd249f084..ee068e9ca31 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -78,8 +76,9 @@ def handle(self) -> int: from poetry.core.pyproject.toml import PyProjectTOML from poetry.core.vcs.git import GitConfig + from poetry.config.config import Config from poetry.layouts import layout - from poetry.utils.env import SystemEnv + from poetry.utils.env import EnvManager project_path = Path.cwd() @@ -161,10 +160,16 @@ def handle(self) -> int: python = self.option("python") if not python: - current_env = SystemEnv(Path(sys.executable)) - default_python = "^" + ".".join( - str(v) for v in current_env.version_info[:2] + config = Config.create() + default_python = ( + "^" + + EnvManager.get_python_version( + precious=2, + prefer_active_python=config.get("virtualenvs.prefer-active-python"), + io=self.io, + ).to_string() ) + question = self.create_question( f"Compatible Python versions [{default_python}]: ", default=default_python, diff --git a/tests/console/commands/test_init.py b/tests/console/commands/test_init.py index f78e9a64d17..c4d0171f43a 100644 --- a/tests/console/commands/test_init.py +++ b/tests/console/commands/test_init.py @@ -2,10 +2,12 @@ import os import shutil +import subprocess import sys from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -26,6 +28,7 @@ from poetry.core.packages.package import Package from pytest_mock import MockerFixture + from poetry.config.config import Config from poetry.poetry import Poetry from tests.helpers import TestRepository from tests.types import FixtureDirGetter @@ -1061,3 +1064,46 @@ def test_package_include( f'python = "^3.10"\n' ) assert expected in tester.io.fetch_output() + + +@pytest.mark.parametrize( + ["prefer_active", "python"], + [ + (True, "1.1"), + (False, f"{sys.version_info[0]}.{sys.version_info[1]}"), + ], +) +def test_respect_prefer_active_on_init( + prefer_active: bool, + python: str, + config: Config, + mocker: MockerFixture, + tester: CommandTester, + source_dir: Path, +): + from poetry.utils.env import GET_PYTHON_VERSION_ONELINER + + orig_check_output = subprocess.check_output + + def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: + if GET_PYTHON_VERSION_ONELINER in cmd: + return "1.1.1" + + return orig_check_output(cmd, *_, **__) + + mocker.patch("subprocess.check_output", side_effect=mock_check_output) + + config.config["virtualenvs"]["prefer-active-python"] = prefer_active + pyproject_file = source_dir / "pyproject.toml" + + tester.execute( + "--author 'Your Name ' --name 'my-package'", + interactive=False, + ) + + expected = f"""\ +[tool.poetry.dependencies] +python = "^{python}" +""" + + assert expected in pyproject_file.read_text() From dbda35ec8c9fc1b59fc587eabebb0b2c63d0849b Mon Sep 17 00:00:00 2001 From: finswimmer Date: Sat, 26 Nov 2022 09:40:08 +0100 Subject: [PATCH 3/3] feat: take `virtualenvs.prefer-active-python` into account on `poetry new` --- src/poetry/console/commands/init.py | 2 +- src/poetry/console/commands/new.py | 18 ++++++++--- src/poetry/utils/env.py | 8 ++--- tests/console/commands/test_new.py | 50 +++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index ee068e9ca31..b1c400e1042 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -164,7 +164,7 @@ def handle(self) -> int: default_python = ( "^" + EnvManager.get_python_version( - precious=2, + precision=2, prefer_active_python=config.get("virtualenvs.prefer-active-python"), io=self.io, ).to_string() diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index 968a3a2a90b..492a7258a94 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -1,7 +1,5 @@ from __future__ import annotations -import sys - from contextlib import suppress from cleo.helpers import argument @@ -31,8 +29,9 @@ def handle(self) -> int: from poetry.core.vcs.git import GitConfig + from poetry.config.config import Config from poetry.layouts import layout - from poetry.utils.env import SystemEnv + from poetry.utils.env import EnvManager if self.io.input.option("directory"): self.line_error( @@ -71,8 +70,17 @@ def handle(self) -> int: if author_email: author += f" <{author_email}>" - current_env = SystemEnv(Path(sys.executable)) - default_python = "^" + ".".join(str(v) for v in current_env.version_info[:2]) + poetry_config = Config.create() + default_python = ( + "^" + + EnvManager.get_python_version( + precision=2, + prefer_active_python=poetry_config.get( + "virtualenvs.prefer-active-python" + ), + io=self.io, + ).to_string() + ) layout_ = layout_cls( name, diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 0877a9a17eb..7a3584a9e98 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -563,11 +563,11 @@ def _detect_active_python(io: None | IO = None) -> str | None: @staticmethod def get_python_version( - precious: int = 3, + precision: int = 3, prefer_active_python: bool = False, io: None | IO = None, ) -> Version: - version = ".".join(str(v) for v in sys.version_info[:precious]) + version = ".".join(str(v) for v in sys.version_info[:precision]) if prefer_active_python: executable = EnvManager._detect_active_python(io) @@ -582,7 +582,7 @@ def get_python_version( ).strip() ) - version = ".".join(str(v) for v in python_patch.split(".")[:precious]) + version = ".".join(str(v) for v in python_patch.split(".")[:precision]) return Version.parse(version) @@ -701,7 +701,7 @@ def get(self, reload: bool = False) -> Env: "virtualenvs.prefer-active-python" ) python_minor = self.get_python_version( - precious=2, prefer_active_python=prefer_active_python, io=self._io + precision=2, prefer_active_python=prefer_active_python, io=self._io ).to_string() venv_path = self._poetry.config.virtualenvs_path diff --git a/tests/console/commands/test_new.py b/tests/console/commands/test_new.py index 79f09e20f27..58560b01e89 100644 --- a/tests/console/commands/test_new.py +++ b/tests/console/commands/test_new.py @@ -1,7 +1,11 @@ from __future__ import annotations +import subprocess +import sys + from pathlib import Path from typing import TYPE_CHECKING +from typing import Any import pytest @@ -10,7 +14,9 @@ if TYPE_CHECKING: from cleo.testers.command_tester import CommandTester + from pytest_mock import MockerFixture + from poetry.config.config import Config from poetry.poetry import Poetry from tests.types import CommandTesterFactory @@ -170,3 +176,47 @@ def test_command_new_with_readme(fmt: str | None, tester: CommandTester, tmp_dir poetry = verify_project_directory(path, package, package, None) assert poetry.local_config.get("readme") == f"README.{fmt or 'md'}" + + +@pytest.mark.parametrize( + ["prefer_active", "python"], + [ + (True, "1.1"), + (False, f"{sys.version_info[0]}.{sys.version_info[1]}"), + ], +) +def test_respect_prefer_active_on_new( + prefer_active: bool, + python: str, + config: Config, + mocker: MockerFixture, + tester: CommandTester, + tmp_dir: str, +): + from poetry.utils.env import GET_PYTHON_VERSION_ONELINER + + orig_check_output = subprocess.check_output + + def mock_check_output(cmd: str, *_: Any, **__: Any) -> str: + if GET_PYTHON_VERSION_ONELINER in cmd: + return "1.1.1" + + return orig_check_output(cmd, *_, **__) + + mocker.patch("subprocess.check_output", side_effect=mock_check_output) + + config.config["virtualenvs"]["prefer-active-python"] = prefer_active + + package = "package" + path = Path(tmp_dir) / package + options = [path.as_posix()] + tester.execute(" ".join(options)) + + pyproject_file = path / "pyproject.toml" + + expected = f"""\ +[tool.poetry.dependencies] +python = "^{python}" +""" + + assert expected in pyproject_file.read_text()