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

uses python-finder during env activate #5778

Closed
Closed
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
67 changes: 59 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ virtualenv = "(>=20.4.3,<20.4.5 || >=20.4.7)"
xattr = { version = "^0.9.7", markers = "sys_platform == 'darwin'" }
urllib3 = "^1.26.0"
dulwich = "^0.20.44"
pythonfinder = "^1.2.10"

[tool.poetry.dev-dependencies]
tox = "^3.18"
Expand Down Expand Up @@ -155,6 +156,7 @@ module = [
'crashtest.*',
'pexpect.*',
'pkginfo.*',
'pythonfinder',
'requests_toolbelt.*',
'shellingham.*',
'virtualenv.*',
Expand Down
7 changes: 6 additions & 1 deletion src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ class Config:
"prefer-active-python": False,
"prompt": "{project_name}-py{python_version}",
},
"experimental": {"new-installer": True, "system-git-client": False},
"experimental": {
"new-installer": True,
"system-git-client": False,
"python-finder": True,
},
"installer": {"parallel": True, "max-workers": None, "no-binary": None},
}

Expand Down Expand Up @@ -266,6 +270,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
"virtualenvs.options.system-site-packages",
"virtualenvs.options.prefer-active-python",
"experimental.new-installer",
"experimental.python-finder",
"experimental.system-git-client",
"installer.parallel",
}:
Expand Down
72 changes: 55 additions & 17 deletions src/poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from poetry.core.semver.version import Version
from poetry.core.toml.file import TOMLFile
from poetry.core.utils.helpers import temporary_directory
from pythonfinder import Finder
from virtualenv.seed.wheels.embed import get_embed_wheel

from poetry.utils._compat import WINDOWS
Expand Down Expand Up @@ -505,6 +506,13 @@ def __init__(self, expected: str, given: str) -> None:
super().__init__(message)


class InterpreterNotFound(EnvError):
def __init__(self, python: str) -> None:
message = f"Python interpreter not found for input ({python})"

super().__init__(message)


class EnvManager:
"""
Environments manager
Expand Down Expand Up @@ -551,23 +559,7 @@ def _detect_active_python(self, io: IO) -> str | None:
)
return executable

def activate(self, python: str, io: IO) -> Env:
venv_path = self._poetry.config.virtualenvs_path
cwd = self._poetry.file.parent

envs_file = TOMLFile(venv_path / self.ENVS_FILE)

try:
python_version = Version.parse(python)
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
pass

python = self._full_python_path(python)

def _get_python_versions(self, python: str) -> tuple[str, str]:
try:
python_version_string = decode(
subprocess.check_output(
Expand All @@ -581,6 +573,52 @@ def activate(self, python: str, io: IO) -> Env:
python_version = Version.parse(python_version_string.strip())
minor = f"{python_version.major}.{python_version.minor}"
patch = python_version.text
return minor, patch

def _find_python(self, python: str) -> tuple[str, str, str]:
# Handle full path
path = Path(python)
if path.is_absolute():
if path.exists():
minor, patch = self._get_python_versions(python)
return python, minor, patch
else:
raise InterpreterNotFound(python)

# python is either <major>[.<minor>] or python[<major>[.<minor>]]
# both are supported as first parameter to Finder.find_python_version
finder = Finder(sort_by_path=True)
try:
result = finder.find_python_version(python)
if result:
minor = f"{result.py_version.major}.{result.py_version.minor}"
patch = f"{minor}.{result.py_version.patch}"
return str(result.path), minor, patch
except ValueError:
pass

raise InterpreterNotFound(python)

def activate(self, python: str, io: IO) -> Env:
venv_path = self._poetry.config.virtualenvs_path
cwd = self._poetry.file.parent

envs_file = TOMLFile(venv_path / self.ENVS_FILE)

experimental_finder = self._poetry.config.get("experimental.python-finder")
if experimental_finder:
python, minor, patch = self._find_python(python)
else:
try:
python_version = Version.parse(python)
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
pass
python = self._full_python_path(python)
minor, patch = self._get_python_versions(python)

create = False
is_root_venv = self._poetry.config.get("virtualenvs.in-project")
Expand Down
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

from _pytest.config import Config as PyTestConfig
from _pytest.config.argparsing import Parser
from _pytest.fixtures import FixtureRequest
from pytest_mock import MockerFixture

from poetry.poetry import Poetry
Expand Down Expand Up @@ -486,3 +487,16 @@ def load_required_fixtures(
) -> None:
for fixture in required_fixtures:
fixture_copier(fixture)


@pytest.fixture(params=[True, False])
def find_python_mode(config: Config, request: FixtureRequest) -> None:
config.merge(
{
"experimental": {
# param is an optional attribute present
# when fixtures are parameterized
"python-finder": request.param # type: ignore
}
}
)
10 changes: 10 additions & 0 deletions tests/console/commands/env/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ def check_output(cmd: str, *_: Any, **__: Any) -> str:
return str(Path("/prefix"))

return check_output


def find_python_wrapper(
version: PEP440Version = VERSION_3_7_1,
) -> Callable[[str], tuple[str, str, str]]:
def find_python_output(python: str) -> tuple[str, str, str]:
path = f"/usr/bin/python{version.major}.{version.minor}"
return path, f"{version.major}.{version.minor}", version.text

return find_python_output
15 changes: 13 additions & 2 deletions tests/console/commands/env/test_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from poetry.utils.env import MockEnv
from tests.console.commands.env.helpers import build_venv
from tests.console.commands.env.helpers import check_output_wrapper
from tests.console.commands.env.helpers import find_python_wrapper


if TYPE_CHECKING:
Expand All @@ -34,18 +35,25 @@ def setup(mocker: MockerFixture) -> None:
def mock_subprocess_calls(
setup: None, current_python: tuple[int, int, int], mocker: MockerFixture
) -> None:
version = Version.from_parts(*current_python)
mocker.patch(
"subprocess.check_output",
side_effect=check_output_wrapper(Version.from_parts(*current_python)),
side_effect=check_output_wrapper(version),
)
mocker.patch(
"subprocess.Popen.communicate",
side_effect=[("/prefix", None), ("/prefix", None), ("/prefix", None)],
)
mocker.patch(
"poetry.utils.env.EnvManager._find_python",
side_effect=find_python_wrapper(version),
)


@pytest.fixture
def tester(command_tester_factory: CommandTesterFactory) -> CommandTester:
def tester(
command_tester_factory: CommandTesterFactory, find_python_mode: None
) -> CommandTester:
return command_tester_factory("env use")


Expand All @@ -60,6 +68,9 @@ def test_activate_activates_non_existing_virtualenv_no_envs_file(
"subprocess.check_output",
side_effect=check_output_wrapper(),
)
mocker.patch(
"poetry.utils.env.EnvManager._find_python", side_effect=find_python_wrapper()
)

mock_build_env = mocker.patch(
"poetry.utils.env.EnvManager.build_venv", side_effect=build_venv
Expand Down
3 changes: 3 additions & 0 deletions tests/console/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def test_list_displays_default_value_if_not_set(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.python-finder = true
experimental.system-git-client = false
installer.max-workers = null
installer.no-binary = null
Expand Down Expand Up @@ -81,6 +82,7 @@ def test_list_displays_set_get_setting(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.python-finder = true
experimental.system-git-client = false
installer.max-workers = null
installer.no-binary = null
Expand Down Expand Up @@ -134,6 +136,7 @@ def test_list_displays_set_get_local_setting(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.python-finder = true
experimental.system-git-client = false
installer.max-workers = null
installer.no-binary = null
Expand Down
Loading