Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: respect virtualenvs.prefer-active-python on poetry new and init #7100

Merged
merged 3 commits into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/poetry/console/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import sys

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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(
precision=2,
prefer_active_python=config.get("virtualenvs.prefer-active-python"),
io=self.io,
).to_string()
)

question = self.create_question(
f"Compatible Python versions [<comment>{default_python}</comment>]: ",
default=default_python,
Expand Down
18 changes: 13 additions & 5 deletions src/poetry/console/commands/new.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import sys

from contextlib import suppress

from cleo.helpers import argument
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
42 changes: 26 additions & 16 deletions src/poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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."
Expand All @@ -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(
precision: int = 3,
prefer_active_python: bool = False,
io: None | IO = None,
) -> Version:
version = ".".join(str(v) for v in sys.version_info[:precision])

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(
Expand All @@ -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(".")[:precision])

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
Expand Down Expand Up @@ -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(
precision=2, prefer_active_python=prefer_active_python, io=self._io
).to_string()

venv_path = self._poetry.config.virtualenvs_path

Expand Down
46 changes: 46 additions & 0 deletions tests/console/commands/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 <you@example.com>' --name 'my-package'",
interactive=False,
)

expected = f"""\
[tool.poetry.dependencies]
python = "^{python}"
"""

assert expected in pyproject_file.read_text()
50 changes: 50 additions & 0 deletions tests/console/commands/test_new.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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()