Skip to content

Commit 93528aa

Browse files
schlamarschlaich
andauthored
Handle architecture configuration in base_python on Windows (#178)
* Handle architecture configuration in base_python on Windows * Fix race condition in test with sys.executable --------- Co-authored-by: schlaich <[email protected]>
1 parent 36fec8a commit 93528aa

File tree

2 files changed

+101
-3
lines changed

2 files changed

+101
-3
lines changed

src/tox_uv/_venv.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def _get_python(self, base_python: list[str]) -> PythonInfo | None: # noqa: PLR
156156
version=str(spec),
157157
is_64=spec.architecture == 64, # noqa: PLR2004
158158
platform=sys.platform,
159-
extra={},
159+
extra={"architecture": spec.architecture},
160160
)
161161

162162
return None # pragma: no cover
@@ -255,16 +255,24 @@ def env_version_spec(self) -> str:
255255
base = self.base_python.version_info
256256
imp = self.base_python.impl_lower
257257
executable = self.base_python.extra.get("executable")
258+
architecture = self.base_python.extra.get("architecture")
258259
if executable:
259260
version_spec = str(executable)
260-
elif (base.major, base.minor) == sys.version_info[:2] and (sys.implementation.name.lower() == imp):
261+
elif (
262+
architecture is None
263+
and (base.major, base.minor) == sys.version_info[:2]
264+
and (sys.implementation.name.lower() == imp)
265+
):
261266
version_spec = sys.executable
262267
else:
263268
uv_imp = imp or ""
264269
if not base.major:
265270
version_spec = f"{uv_imp}"
266271
elif not base.minor:
267272
version_spec = f"{uv_imp}{base.major}"
273+
elif architecture is not None and self.base_python.platform == "win32":
274+
uv_arch = {32: "x86", 64: "x86_64"}[architecture]
275+
version_spec = f"{uv_imp}-{base.major}.{base.minor}-windows-{uv_arch}-none"
268276
else:
269277
version_spec = f"{uv_imp}{base.major}.{base.minor}"
270278
return version_spec

tests/test_tox_uv_venv.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
from configparser import ConfigParser
1111
from importlib.metadata import version
1212
from typing import TYPE_CHECKING, get_args
13+
from unittest import mock
1314

1415
import pytest
1516
import tox.tox_env.errors
17+
from tox.tox_env.python.api import PythonInfo, VersionInfo
1618

17-
from tox_uv._venv import PythonPreference
19+
from tox_uv._venv import PythonPreference, UvVenv
1820

1921
if TYPE_CHECKING:
2022
from tox.pytest import ToxProjectCreator
@@ -420,3 +422,91 @@ def test_uv_pip_constraints_no(tox_project: ToxProjectCreator) -> None:
420422
"Found PIP_CONSTRAINTS defined, you may want to also define UV_CONSTRAINT to match pip behavior."
421423
not in result.out
422424
)
425+
426+
427+
class _TestUvVenv(UvVenv):
428+
@staticmethod
429+
def id() -> str:
430+
return "uv-venv-test" # pragma: no cover
431+
432+
def set_base_python(self, python_info: PythonInfo) -> None:
433+
self._base_python_searched = True
434+
self._base_python = python_info
435+
436+
def get_python_info(self, base_python: str) -> PythonInfo | None:
437+
return self._get_python([base_python])
438+
439+
440+
@pytest.mark.parametrize(
441+
("base_python", "architecture"), [("python3.11", None), ("python3.11-32", 32), ("python3.11-64", 64)]
442+
)
443+
def test_get_python_architecture(base_python: str, architecture: int | None) -> None:
444+
uv_venv = _TestUvVenv(create_args=mock.Mock())
445+
python_info = uv_venv.get_python_info(base_python)
446+
assert python_info is not None
447+
assert python_info.extra["architecture"] == architecture
448+
449+
450+
def test_env_version_spec_no_architecture() -> None:
451+
uv_venv = _TestUvVenv(create_args=mock.MagicMock())
452+
python_info = PythonInfo(
453+
implementation="cpython",
454+
version_info=VersionInfo(
455+
major=3,
456+
minor=11,
457+
micro=9,
458+
releaselevel="",
459+
serial=0,
460+
),
461+
version="",
462+
is_64=True,
463+
platform="win32",
464+
extra={"architecture": None},
465+
)
466+
uv_venv.set_base_python(python_info)
467+
with mock.patch("sys.version_info", (0, 0, 0)): # prevent picking sys.executable
468+
assert uv_venv.env_version_spec() == "cpython3.11"
469+
470+
471+
@pytest.mark.parametrize("architecture", [32, 64])
472+
def test_env_version_spec_architecture_configured(architecture: int) -> None:
473+
uv_venv = _TestUvVenv(create_args=mock.MagicMock())
474+
python_info = PythonInfo(
475+
implementation="cpython",
476+
version_info=VersionInfo(
477+
major=3,
478+
minor=11,
479+
micro=9,
480+
releaselevel="",
481+
serial=0,
482+
),
483+
version="",
484+
is_64=architecture == 64,
485+
platform="win32",
486+
extra={"architecture": architecture},
487+
)
488+
uv_venv.set_base_python(python_info)
489+
uv_arch = {32: "x86", 64: "x86_64"}[architecture]
490+
assert uv_venv.env_version_spec() == f"cpython-3.11-windows-{uv_arch}-none"
491+
492+
493+
@pytest.mark.skipif(sys.platform != "win32", reason="architecture configuration only on Windows")
494+
def test_env_version_spec_architecture_configured_overwrite_sys_exe() -> None: # pragma: win32 cover
495+
uv_venv = _TestUvVenv(create_args=mock.MagicMock())
496+
(major, minor) = sys.version_info[:2]
497+
python_info = PythonInfo(
498+
implementation="cpython",
499+
version_info=VersionInfo(
500+
major=major,
501+
minor=minor,
502+
micro=0,
503+
releaselevel="",
504+
serial=0,
505+
),
506+
version="",
507+
is_64=False,
508+
platform="win32",
509+
extra={"architecture": 32},
510+
)
511+
uv_venv.set_base_python(python_info)
512+
assert uv_venv.env_version_spec() == f"cpython-{major}.{minor}-windows-x86-none"

0 commit comments

Comments
 (0)