Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2c5712b

Browse files
authored
Use uv for installing dynamic dependencies in mypy_test.py and regr_test.py (#11517)
1 parent 176c89c commit 2c5712b

6 files changed

Lines changed: 60 additions & 77 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
if [ -n "$DEPENDENCIES" ]; then
142142
source .venv/bin/activate
143143
echo "Installing packages: $DEPENDENCIES"
144-
uv pip install $DEPENDENCIES --system
144+
uv pip install $DEPENDENCIES
145145
fi
146146
- name: Activate the isolated venv for the rest of the job
147147
run: echo "$PWD/.venv/bin" >> $GITHUB_PATH

requirements-tests.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ termcolor>=2.3
2020
tomli==2.0.1
2121
tomlkit==0.12.3
2222
typing_extensions>=4.9.0rc1
23+
uv
2324

2425
# Type stubs used to type check our scripts.
2526
types-pyyaml>=6.0.12.7

tests/mypy_test.py

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,14 @@
3030
from utils import (
3131
PYTHON_VERSION,
3232
VERSIONS_RE as VERSION_LINE_RE,
33-
VenvInfo,
3433
colored,
3534
get_gitignore_spec,
3635
get_mypy_req,
37-
make_venv,
3836
print_error,
3937
print_success_msg,
4038
spec_matches_path,
4139
strip_comments,
40+
venv_python,
4241
)
4342

4443
# Fail early if mypy isn't installed
@@ -235,7 +234,7 @@ def run_mypy(
235234
*,
236235
testing_stdlib: bool,
237236
non_types_dependencies: bool,
238-
venv_info: VenvInfo,
237+
venv_dir: Path | None,
239238
mypypath: str | None = None,
240239
) -> MypyResult:
241240
env_vars = dict(os.environ)
@@ -279,7 +278,8 @@ def run_mypy(
279278
flags.append("--no-site-packages")
280279

281280
mypy_args = [*flags, *map(str, files)]
282-
mypy_command = [venv_info.python_exe, "-m", "mypy", *mypy_args]
281+
python_path = sys.executable if venv_dir is None else str(venv_python(venv_dir))
282+
mypy_command = [python_path, "-m", "mypy", *mypy_args]
283283
if args.verbose:
284284
print(colored(f"running {' '.join(mypy_command)}", "blue"))
285285
result = subprocess.run(mypy_command, capture_output=True, text=True, env=env_vars)
@@ -291,7 +291,7 @@ def run_mypy(
291291
print_error(result.stderr)
292292
if non_types_dependencies and args.verbose:
293293
print("Ran with the following environment:")
294-
subprocess.run([venv_info.pip_exe, "freeze", "--all"])
294+
subprocess.run(["uv", "pip", "freeze"], env={**os.environ, "VIRTUAL_ENV": str(venv_dir)})
295295
print()
296296
else:
297297
print_success_msg()
@@ -324,7 +324,7 @@ class TestResult(NamedTuple):
324324

325325

326326
def test_third_party_distribution(
327-
distribution: str, args: TestConfig, venv_info: VenvInfo, *, non_types_dependencies: bool
327+
distribution: str, args: TestConfig, venv_dir: Path | None, *, non_types_dependencies: bool
328328
) -> TestResult:
329329
"""Test the stubs of a third-party distribution.
330330
@@ -353,7 +353,7 @@ def test_third_party_distribution(
353353
args,
354354
configurations,
355355
files,
356-
venv_info=venv_info,
356+
venv_dir=venv_dir,
357357
mypypath=mypypath,
358358
testing_stdlib=False,
359359
non_types_dependencies=non_types_dependencies,
@@ -377,9 +377,8 @@ def test_stdlib(args: TestConfig) -> TestResult:
377377
return TestResult(MypyResult.SUCCESS, 0)
378378

379379
print(f"Testing stdlib ({len(files)} files)... ", end="", flush=True)
380-
# We don't actually need pip for the stdlib testing
381-
venv_info = VenvInfo(pip_exe="", python_exe=sys.executable)
382-
result = run_mypy(args, [], files, venv_info=venv_info, testing_stdlib=True, non_types_dependencies=False)
380+
# We don't actually need to install anything for the stdlib testing
381+
result = run_mypy(args, [], files, venv_dir=None, testing_stdlib=True, non_types_dependencies=False)
383382
return TestResult(result, len(files))
384383

385384

@@ -409,22 +408,30 @@ def merge(self, other: TestSummary) -> None:
409408

410409

411410
_PRINT_LOCK = Lock()
412-
_DISTRIBUTION_TO_VENV_MAPPING: dict[str, VenvInfo] = {}
411+
_DISTRIBUTION_TO_VENV_MAPPING: dict[str, Path | None] = {}
413412

414413

415-
def setup_venv_for_external_requirements_set(requirements_set: frozenset[str], tempdir: Path) -> tuple[frozenset[str], VenvInfo]:
414+
def setup_venv_for_external_requirements_set(
415+
requirements_set: frozenset[str], tempdir: Path, args: TestConfig
416+
) -> tuple[frozenset[str], Path]:
416417
venv_dir = tempdir / f".venv-{hash(requirements_set)}"
417-
return requirements_set, make_venv(venv_dir)
418+
uv_command = ["uv", "venv", str(venv_dir)]
419+
if not args.verbose:
420+
uv_command.append("--quiet")
421+
subprocess.run(uv_command, check=True)
422+
return requirements_set, venv_dir
418423

419424

420-
def install_requirements_for_venv(venv_info: VenvInfo, args: TestConfig, external_requirements: frozenset[str]) -> None:
425+
def install_requirements_for_venv(venv_dir: Path, args: TestConfig, external_requirements: frozenset[str]) -> None:
421426
# Use --no-cache-dir to avoid issues with concurrent read/writes to the cache
422-
pip_command = [venv_info.pip_exe, "install", get_mypy_req(), *sorted(external_requirements), "--no-cache-dir"]
427+
uv_command = ["uv", "pip", "install", get_mypy_req(), *sorted(external_requirements), "--no-cache-dir"]
423428
if args.verbose:
424429
with _PRINT_LOCK:
425-
print(colored(f"Running {pip_command}", "blue"))
430+
print(colored(f"Running {uv_command}", "blue"))
431+
else:
432+
uv_command.append("--quiet")
426433
try:
427-
subprocess.run(pip_command, check=True, capture_output=True, text=True)
434+
subprocess.run(uv_command, check=True, text=True, env={**os.environ, "VIRTUAL_ENV": str(venv_dir)})
428435
except subprocess.CalledProcessError as e:
429436
print(e.stderr)
430437
raise
@@ -437,9 +444,6 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
437444

438445
# STAGE 1: Determine which (if any) stubs packages require virtual environments.
439446
# Group stubs packages according to their external-requirements sets
440-
441-
# We don't actually need pip if there aren't any external dependencies
442-
no_external_dependencies_venv = VenvInfo(pip_exe="", python_exe=sys.executable)
443447
external_requirements_to_distributions: defaultdict[frozenset[str], list[str]] = defaultdict(list)
444448
num_pkgs_with_external_reqs = 0
445449

@@ -449,7 +453,7 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
449453
external_requirements = frozenset(requirements.external_pkgs)
450454
external_requirements_to_distributions[external_requirements].append(distribution_name)
451455
else:
452-
_DISTRIBUTION_TO_VENV_MAPPING[distribution_name] = no_external_dependencies_venv
456+
_DISTRIBUTION_TO_VENV_MAPPING[distribution_name] = None
453457

454458
# Exit early if there are no stubs packages that have non-types dependencies
455459
if num_pkgs_with_external_reqs == 0:
@@ -458,7 +462,7 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
458462
return
459463

460464
# STAGE 2: Setup a virtual environment for each unique set of external requirements
461-
requirements_sets_to_venvs: dict[frozenset[str], VenvInfo] = {}
465+
requirements_sets_to_venvs: dict[frozenset[str], Path] = {}
462466

463467
if args.verbose:
464468
num_venvs = len(external_requirements_to_distributions)
@@ -472,13 +476,13 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
472476
venv_start_time = time.perf_counter()
473477

474478
with concurrent.futures.ProcessPoolExecutor() as executor:
475-
venv_info_futures = [
476-
executor.submit(setup_venv_for_external_requirements_set, requirements_set, tempdir)
479+
venv_futures = [
480+
executor.submit(setup_venv_for_external_requirements_set, requirements_set, tempdir, args)
477481
for requirements_set in external_requirements_to_distributions
478482
]
479-
for venv_info_future in concurrent.futures.as_completed(venv_info_futures):
480-
requirements_set, venv_info = venv_info_future.result()
481-
requirements_sets_to_venvs[requirements_set] = venv_info
483+
for venv_future in concurrent.futures.as_completed(venv_futures):
484+
requirements_set, venv_dir = venv_future.result()
485+
requirements_sets_to_venvs[requirements_set] = venv_dir
482486

483487
venv_elapsed_time = time.perf_counter() - venv_start_time
484488

@@ -492,8 +496,8 @@ def setup_virtual_environments(distributions: dict[str, PackageDependencies], ar
492496
# Limit workers to 10 at a time, since this makes network requests
493497
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
494498
pip_install_futures = [
495-
executor.submit(install_requirements_for_venv, venv_info, args, requirements_set)
496-
for requirements_set, venv_info in requirements_sets_to_venvs.items()
499+
executor.submit(install_requirements_for_venv, venv_dir, args, requirements_set)
500+
for requirements_set, venv_dir in requirements_sets_to_venvs.items()
497501
]
498502
concurrent.futures.wait(pip_install_futures)
499503

@@ -561,10 +565,10 @@ def test_third_party_stubs(args: TestConfig, tempdir: Path) -> TestSummary:
561565
assert _DISTRIBUTION_TO_VENV_MAPPING.keys() >= distributions_to_check.keys()
562566

563567
for distribution in distributions_to_check:
564-
venv_info = _DISTRIBUTION_TO_VENV_MAPPING[distribution]
565-
non_types_dependencies = venv_info.python_exe != sys.executable
568+
venv_dir = _DISTRIBUTION_TO_VENV_MAPPING[distribution]
569+
non_types_dependencies = venv_dir is not None
566570
mypy_result, files_checked = test_third_party_distribution(
567-
distribution, args, venv_info=venv_info, non_types_dependencies=non_types_dependencies
571+
distribution, args, venv_dir=venv_dir, non_types_dependencies=non_types_dependencies
568572
)
569573
summary.register_result(mypy_result, files_checked)
570574

tests/regr_test.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@
2525
from utils import (
2626
PYTHON_VERSION,
2727
PackageInfo,
28-
VenvInfo,
2928
colored,
3029
get_all_testcase_directories,
3130
get_mypy_req,
32-
make_venv,
3331
print_error,
3432
testcase_dir_from_package_name,
33+
venv_python,
3534
)
3635

3736
ReturnCode: TypeAlias = int
@@ -148,13 +147,16 @@ def setup_testcase_dir(package: PackageInfo, tempdir: Path, verbosity: Verbosity
148147
shutil.copytree(Path("stubs", requirement), new_typeshed / "stubs" / requirement)
149148

150149
if requirements.external_pkgs:
151-
pip_exe = make_venv(tempdir / VENV_DIR).pip_exe
150+
venv_location = str(tempdir / VENV_DIR)
151+
subprocess.run(["uv", "venv", venv_location], check=True, capture_output=True)
152152
# Use --no-cache-dir to avoid issues with concurrent read/writes to the cache
153-
pip_command = [pip_exe, "install", get_mypy_req(), *requirements.external_pkgs, "--no-cache-dir"]
153+
uv_command = ["uv", "pip", "install", get_mypy_req(), *requirements.external_pkgs, "--no-cache-dir"]
154154
if verbosity is Verbosity.VERBOSE:
155-
verbose_log(f"{package.name}: Setting up venv in {tempdir / VENV_DIR}. {pip_command=}\n")
155+
verbose_log(f"{package.name}: Setting up venv in {venv_location}. {uv_command=}\n")
156156
try:
157-
subprocess.run(pip_command, check=True, capture_output=True, text=True)
157+
subprocess.run(
158+
uv_command, check=True, capture_output=True, text=True, env=os.environ | {"VIRTUAL_ENV": venv_location}
159+
)
158160
except subprocess.CalledProcessError as e:
159161
_PRINT_QUEUE.put(f"{package.name}\n{e.stderr}")
160162
raise
@@ -193,7 +195,7 @@ def run_testcases(
193195
env_vars["MYPYPATH"] = os.pathsep.join(map(str, custom_typeshed.glob("stubs/*")))
194196
has_non_types_dependencies = (tempdir / VENV_DIR).exists()
195197
if has_non_types_dependencies:
196-
python_exe = VenvInfo.of_existing_venv(tempdir / VENV_DIR).python_exe
198+
python_exe = str(venv_python(tempdir / VENV_DIR))
197199
else:
198200
python_exe = sys.executable
199201
flags.append("--no-site-packages")

tests/stubtest_third_party.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import NoReturn
1414

1515
from parse_metadata import NoSuchStubError, get_recursive_requirements, read_metadata
16-
from utils import PYTHON_VERSION, colored, get_mypy_req, make_venv, print_error, print_success_msg
16+
from utils import PYTHON_VERSION, colored, get_mypy_req, print_error, print_success_msg
1717

1818

1919
def run_stubtest(
@@ -43,11 +43,13 @@ def run_stubtest(
4343

4444
with tempfile.TemporaryDirectory() as tmp:
4545
venv_dir = Path(tmp)
46-
try:
47-
pip_exe, python_exe = make_venv(venv_dir)
48-
except Exception:
49-
print_error("fail")
50-
raise
46+
subprocess.run(["uv", "venv", venv_dir, "--seed"], capture_output=True, check=True)
47+
if sys.platform == "win32":
48+
pip_exe = str(venv_dir / "Scripts" / "pip.exe")
49+
python_exe = str(venv_dir / "Scripts" / "python.exe")
50+
else:
51+
pip_exe = str(venv_dir / "bin" / "pip")
52+
python_exe = str(venv_dir / "bin" / "python")
5153
dist_extras = ", ".join(stubtest_settings.extras)
5254
dist_req = f"{dist_name}[{dist_extras}]=={metadata.version}"
5355

tests/utils.py

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
import os
66
import re
7-
import subprocess
87
import sys
9-
import venv
108
from functools import lru_cache
119
from pathlib import Path
1210
from typing import Any, Final, NamedTuple
13-
from typing_extensions import Annotated
1411

1512
import pathspec
1613

@@ -51,34 +48,11 @@ def print_success_msg() -> None:
5148
# ====================================================================
5249

5350

54-
class VenvInfo(NamedTuple):
55-
pip_exe: Annotated[str, "A path to the venv's pip executable"]
56-
python_exe: Annotated[str, "A path to the venv's python executable"]
57-
58-
@staticmethod
59-
def of_existing_venv(venv_dir: Path) -> VenvInfo:
60-
if sys.platform == "win32":
61-
pip = venv_dir / "Scripts" / "pip.exe"
62-
python = venv_dir / "Scripts" / "python.exe"
63-
else:
64-
pip = venv_dir / "bin" / "pip"
65-
python = venv_dir / "bin" / "python"
66-
67-
return VenvInfo(str(pip), str(python))
68-
69-
70-
def make_venv(venv_dir: Path) -> VenvInfo:
71-
try:
72-
venv.create(venv_dir, with_pip=True, clear=True)
73-
except subprocess.CalledProcessError as e:
74-
if "ensurepip" in e.cmd and b"KeyboardInterrupt" not in e.stdout.splitlines():
75-
print_error(
76-
"stubtest requires a Python installation with ensurepip. "
77-
"If on Linux, you may need to install the python3-venv package."
78-
)
79-
raise
80-
81-
return VenvInfo.of_existing_venv(venv_dir)
51+
@cache
52+
def venv_python(venv_dir: Path) -> Path:
53+
if sys.platform == "win32":
54+
return venv_dir / "Scripts" / "python.exe"
55+
return venv_dir / "bin" / "python"
8256

8357

8458
@cache

0 commit comments

Comments
 (0)