Skip to content

Commit 1ee4a33

Browse files
authored
Fix absolute base python paths conflicting (#3325)
1 parent add99ed commit 1ee4a33

File tree

8 files changed

+67
-29
lines changed

8 files changed

+67
-29
lines changed

.github/workflows/check.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on:
33
workflow_dispatch:
44
push:
55
branches: ["main"]
6-
tags-ignore: [ "**" ]
6+
tags-ignore: ["**"]
77
pull_request:
88
schedule:
99
- cron: "0 8 * * *"
@@ -76,6 +76,7 @@ jobs:
7676
- windows-latest
7777
exclude:
7878
- { os: windows-latest, tox_env: pkg_meta }
79+
- { os: windows-latest, tox_env: docs }
7980
steps:
8081
- uses: actions/checkout@v4
8182
with:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929

3030
release:
3131
needs:
32-
- build
32+
- build
3333
runs-on: ubuntu-latest
3434
environment:
3535
name: release

.pre-commit-config.yaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repos:
88
rev: 0.29.1
99
hooks:
1010
- id: check-github-workflows
11-
args: [ "--verbose" ]
11+
args: ["--verbose"]
1212
- repo: https://github.com/codespell-project/codespell
1313
rev: v2.3.0
1414
hooks:
@@ -24,7 +24,7 @@ repos:
2424
hooks:
2525
- id: pyproject-fmt
2626
- repo: https://github.com/astral-sh/ruff-pre-commit
27-
rev: "v0.5.6"
27+
rev: "v0.5.7"
2828
hooks:
2929
- id: ruff-format
3030
- id: ruff
@@ -33,11 +33,18 @@ repos:
3333
rev: 1.18.0
3434
hooks:
3535
- id: blacken-docs
36-
additional_dependencies: [black==24.4.2]
36+
additional_dependencies: [black==24.8]
3737
- repo: https://github.com/pre-commit/pygrep-hooks
3838
rev: v1.10.0
3939
hooks:
4040
- id: rst-backticks
41+
- repo: https://github.com/rbubley/mirrors-prettier
42+
rev: "v3.3.3" # Use the sha / tag you want to point at
43+
hooks:
44+
- id: prettier
45+
additional_dependencies:
46+
47+
- "@prettier/[email protected]"
4148
- repo: local
4249
hooks:
4350
- id: changelogs-rst

docs/changelog/3325.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix absolute base python paths conflicting - by :user:`gaborbernat`.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ lint.ignore = [
128128
"D", # ignore documentation for now
129129
"D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
130130
"D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible
131-
"DOC201", # broken with sphinx docs
131+
"DOC201", # no restructuredtext support yet
132+
"DOC402", # no restructuredtext support yet
132133
"DOC501", # broken with sphinx docs
133134
"INP001", # no implicit namespaces here
134135
"ISC001", # conflicts with formatter

src/tox/tox_env/python/api.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,14 @@ def _validate_base_python(
168168
if env_base_python is not None:
169169
spec_name = PythonSpec.from_string_spec(env_base_python)
170170
for base_python in base_pythons:
171-
if Path(base_python).is_absolute():
172-
return [base_python]
173171
spec_base = PythonSpec.from_string_spec(base_python)
172+
if spec_base.path is not None:
173+
path = Path(spec_base.path).absolute()
174+
if str(spec_base.path) == sys.executable:
175+
ver, is_64 = sys.version_info, sys.maxsize != 2**32
176+
spec_base = PythonSpec.from_string_spec(f"{sys.implementation}{ver.major}{ver.minor}-{is_64}")
177+
else:
178+
spec_base = cls.python_spec_for_path(path)
174179
if any(
175180
getattr(spec_base, key) != getattr(spec_name, key)
176181
for key in ("implementation", "major", "minor", "micro", "architecture")
@@ -183,6 +188,17 @@ def _validate_base_python(
183188
raise Fail(msg)
184189
return base_pythons
185190

191+
@classmethod
192+
@abstractmethod
193+
def python_spec_for_path(cls, path: Path) -> PythonSpec:
194+
"""
195+
Get the spec for an absolute path to a Python executable.
196+
197+
:param path: the path investigated
198+
:return: the found spec
199+
"""
200+
raise NotImplementedError
201+
186202
@abstractmethod
187203
def env_site_package_dir(self) -> Path:
188204
"""

src/tox/tox_env/python/virtual_env/api.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
import os
66
import sys
7+
from abc import ABC
78
from pathlib import Path
89
from typing import TYPE_CHECKING, Any, cast
910

1011
from virtualenv import __version__ as virtualenv_version
11-
from virtualenv import session_via_cli
12+
from virtualenv import app_data, session_via_cli
13+
from virtualenv.discovery import cached_py_info
14+
from virtualenv.discovery.py_spec import PythonSpec
1215

1316
from tox.config.loader.str_convert import StrConvert
1417
from tox.execute.local_sub_process import LocalSubProcessExecutor
@@ -17,13 +20,14 @@
1720

1821
if TYPE_CHECKING:
1922
from virtualenv.create.creator import Creator
23+
from virtualenv.discovery.py_info import PythonInfo as VirtualenvPythonInfo
2024
from virtualenv.run.session import Session
2125

2226
from tox.execute.api import Execute
2327
from tox.tox_env.api import ToxEnvCreateArgs
2428

2529

26-
class VirtualEnv(Python):
30+
class VirtualEnv(Python, ABC):
2731
"""A python executor that uses the virtualenv project with pip."""
2832

2933
def __init__(self, create_args: ToxEnvCreateArgs) -> None:
@@ -167,3 +171,30 @@ def environment_variables(self) -> dict[str, str]:
167171
environment_variables = super().environment_variables
168172
environment_variables["VIRTUAL_ENV"] = str(self.conf["env_dir"])
169173
return environment_variables
174+
175+
@classmethod
176+
def python_spec_for_path(cls, path: Path) -> PythonSpec:
177+
"""
178+
Get the spec for an absolute path to a Python executable.
179+
180+
:param path: the path investigated
181+
:return: the found spec
182+
"""
183+
info = cls.get_virtualenv_py_info(path)
184+
return PythonSpec.from_string_spec(
185+
f"{info.implementation}{info.version_info.major}{info.version_info.minor}-{info.architecture}"
186+
)
187+
188+
@staticmethod
189+
def get_virtualenv_py_info(path: Path) -> VirtualenvPythonInfo:
190+
"""
191+
Get the version info for an absolute path to a Python executable.
192+
193+
:param path: the path investigated
194+
:return: the found information (cached)
195+
"""
196+
return cached_py_info.from_exe(
197+
cached_py_info.PythonInfo,
198+
app_data.make_app_data(None, read_only=False, env=os.environ),
199+
str(path),
200+
)

tests/tox_env/python/test_python_api.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import os
43
import sys
54
from typing import TYPE_CHECKING, Callable
65
from unittest.mock import patch
@@ -126,24 +125,6 @@ def test_base_python_env_no_conflict(env: str, base_python: list[str], ignore_co
126125
assert result is base_python
127126

128127

129-
@pytest.mark.parametrize(
130-
("env", "base_python", "platform"),
131-
[
132-
("py312-unix", ["/opt/python312/bin/python"], "posix"),
133-
("py312-win", [r"C:\Program Files\Python312\python.exe"], "nt"),
134-
("py311-win", [r"\\a\python311\python.exe"], "nt"),
135-
("py310-win", [r"\\?\UNC\a\python310\python.exe"], "nt"),
136-
("py310", ["//a/python310/bin/python"], None),
137-
],
138-
ids=lambda a: "|".join(a) if isinstance(a, list) else str(a),
139-
)
140-
def test_base_python_absolute(env: str, base_python: list[str], platform: str | None) -> None:
141-
if platform and platform != os.name:
142-
pytest.skip(f"Not applicable to this platform. ({platform} != {os.name})")
143-
result = Python._validate_base_python(env, base_python, False) # noqa: SLF001
144-
assert result == base_python
145-
146-
147128
@pytest.mark.parametrize("ignore_conflict", [True, False])
148129
@pytest.mark.parametrize(
149130
("env", "base_python", "expected", "conflict"),

0 commit comments

Comments
 (0)